Deep In React (二) Component,Element和渲染

先导知识: React Component和React Element

在React中,有着组件(component)和元素(element)两个概念。我们日常可能听到过很多次Component这个名词,但是对于Element这个名词在React中似乎聊得并不多。今天就来给大家讲讲什么是Element,什么是Component,以及他们和React渲染的关系。

React Element

React Element从数据结构上来讲就是简单的plain object, 这个plain object会提供对于整个React组件树的描述。

{
  type: 'button',
  props: {
    classNames: 'foo',
    children: {
      type: 'span',
      props: {
        children: 'click'
      }
    }
}

讲这个Element翻译成HTML就是如下结构

<button class="foo">
  <span>click</span>
</button>

需要注意的是type这个属性,在上面的例子中,type是一个string,而type是string的element React将其定义为HostElement。Host的意思是这个元素会根据不同的平台对应不同的Native View。比如ReactDOM会对应成相应的HTML Tag,而React-Native则会对应成Native View。

而除开HostElement, React还支持Component类型的element。我们在React代码中经常会这样组合我们的组件。

  <Container>
    <Header />
    <Sidebar />
    <div>content</div>
    <Footer />
  </Container>

这段代码对应生成的element的描述如下

{
  type: Container,
  props: {
    children: [
      {
        type: Header,
        props: null
      },
      {
        type: Sidebar,
        props: null,
      },
      {
        type: 'div'
        props: {
          children: 'content'
        }
      },
      {
        type: Footer,
        props: null
      }
    ]
  }
}

这里的Container/Header/Sidebar/Footer对应的可能是函数或者是class, 而这个函数或者class就是我们经常谈到的React Component。

React Component

Component事实上是对于React Element Tree的一种封装。假设我们要渲染Button这个component。

const Button = content => <button>{content}</button>

render(<Button content="foo" />

事实上React会将Button这个component解析成以下element树

{
  type: Button,
  props: {
    content: {
      content: 'foo'
    }
  }
}

而React发现Button其实是一个Component Element,而不是一个Host Element,所以React会递归向下渲染。

{
  type: button,
  props: {
    children: 'foo'
  }
}

React会一直这样递归下去直到所有的Component Element都被翻译成Host Element。

component的function写法和class写法

Component事实上有三种写法,分别是function写法,class without es6以及es6 class写法

// stateless component
const Button = content => ({
  type: 'button',
  props: {
    content
  }
})

//  class without es6
const Button = React.CreateClass({
  render() {
    const { content } = this.props;
    return {
      type: 'button',
      props: {
        content
      }
    }

  }
})

// es6 class
class Button extends React.Component {
  reder() {
    const { content } = this.props;
    return {
      type: 'button',
      props: {
        content
      }
    }
  }
}

由于babel已经成为了开发的标配,所以基本上大家都更习惯于去使用es6 class而不是React.createClass,这种写法当前也不推荐使用了。

在上面的三种Component写法中,最终Component的渲染都会返回一个React Element。但是使用Plain Object去描述React Element会降低开发者开发和阅读的效率,还是借助babel的魔力,我们可以使用JSX来描述我们的React Element。

// function as example
const Button = content => <button>{content}</button>

Component,Element与渲染

让我们回到这个组件树上

const Container = children => <div>{children}</div>
const Header = children => <header>{children}</div>
const Sidebar = children => <nav>{children}</nav>
const Footer = children => <footer>{children}</nav>

 ReactDOM.render(
  (<Container>
    <Header />
    <Sidebar />
    <div>content</div>
    <Footer />
  </Container>), 
  document.querySelector('#container'))

第一层渲染的结果

{
  type: Container,
  props: {
    children: [
      {
        type: Header,
        props: null
      },
      {
        type: Sidebar,
        props: null,
      },
      {
        type: 'div'
        props: {
          children: 'content'
        }
      },
      {
        type: Footer,
        props: null
      }
    ]
  }
}

这时除了div已经是一个Host Element以外,其余的元素都是Component Element,React的渲染机制会让对应的Component会继续递归渲染,直到整个React Element tree最终只剩下Host Element。最终的渲染结果如下。

{
  type: 'div',
  props: {
    children: [
      {
        type: 'header',
        props: null
      },
      {
        type: 'nav',
        props: null,
      },
      {
        type: 'div'
        props: {
          children: 'content'
        }
      },
      {
        type: 'footer',
        props: null
      }
    ]
  }
}

React的渲染机制非常简单但是非常实用,可以大概总结为两点。

  1. 不管是Host Element还是Component Element, React都把他们当成Element用同一种机制去渲染去处理,这也是React的核心思想。
  2. Component会不断进行渲染,直到渲染到children里面没有Component Element为止。

What’s Next?

上面我们提到了React内部其实是递归进行渲染的,你可能会好奇React内部到底是怎么实现这样一套递归渲染机制的。下一篇文章我们就来聊聊这套渲染机制的具体实现–Internal Instances。

参考

https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html