在上一篇文章中,我们谈到了如何从应用层面优化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组件树的描述。
1
2
3
4
5
6
7
8
9
10
11
|
{
type: 'button',
props: {
classNames: 'foo',
children: {
type: 'span',
props: {
children: 'click'
}
}
}
|
讲这个Element翻译成HTML就是如下结构
1
2
3
|
<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代码中经常会这样组合我们的组件。
1
2
3
4
5
6
|
<Container>
<Header />
<Sidebar />
<div>content</div>
<Footer />
</Container>
|
这段代码对应生成的element的描述如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
{
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。
1
2
3
|
const Button = content => <button>{content}</button>
render(<Button content="foo" />
|
事实上React会将Button这个component解析成以下element树
1
2
3
4
5
6
7
8
|
{
type: Button,
props: {
content: {
content: 'foo'
}
}
}
|
而React发现Button其实是一个Component Element,而不是一个Host Element,所以React会递归向下渲染。
1
2
3
4
5
6
|
{
type: button,
props: {
children: 'foo'
}
}
|
React会一直这样递归下去直到所有的Component Element都被翻译成Host Element。
component的function写法和class写法
Component事实上有三种写法,分别是function写法,class without es6以及es6 class写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// 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。
1
2
|
// function as example
const Button = content => <button>{content}</button>
|
Component,Element与渲染
让我们回到这个组件树上
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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'))
|
第一层渲染的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
{
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。最终的渲染结果如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
{
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