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 的渲染机制非常简单但是非常实用,可以大概总结为两点。
- 不管是 Host Element 还是 Component Element, React 都把他们当成 Element 用同一种机制去渲染去处理,这也是 React 的核心思想。
- Component 会不断进行渲染,直到渲染到 children 里面没有 Component Element 为止。
What’s Next?
上面我们提到了 React 内部其实是递归进行渲染的,你可能会好奇 React 内部到底是怎么实现这样一套递归渲染机制的。下一篇文章我们就来聊聊这套渲染机制的具体实现—Internal Instances。
参考
https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html