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