skip to content
OnionTalk

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

在上一篇文章中,我们谈到了如何从应用层面优化React的性能,在随后的几篇文章中,我们将深入React的底层实现,仔细分析一下为什么React会有如此高的性能。在介绍React底层的reconciliation算法之前,我们需要先了解一些先导知识。

先导知识: 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