Skip to content

Commit b6ad5f5

Browse files
committed
添加react 解析
1 parent 9ca4d41 commit b6ad5f5

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
layout: post
3+
category : JavaScript
4+
tagline: ""
5+
tags : [react]
6+
---
7+
{% include JB/setup %}
8+
9+
<a href="http://blog.reverberate.org/2014/02/react-demystified.html" target="_blank">原文链接</a>
10+
11+
这篇文章不像以往blog的文章那样,讲语法分析以及低层编程语言.最近我对一些`javascript`框架比较感兴趣,包括`facebook`的<a href="http://facebook.github.io/react/index.html" target="_blank">React</a>,读了一些网上专门讲解它的文章,特别是<a href="http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/" target="_blank">The Future of JavaScript MVC Frameworks</a>这篇,让我对它的核心思想更加有兴趣,不过没有找到对它核心抽象思想方面的解析,就像我上一篇文章<a href="http://blog.reverberate.org/2013/07/ll-and-lr-parsing-demystified.html" target="_blank">LL and LR Parsing Demystified</a>,它讲解了核心原理,在某种程度上对我非常有意义.
12+
13+
### The 1000-Foot View
14+
15+
在传统的`web`应用里,操作`dom`一般是用`jquery`来实现,像下图这样:
16+
17+
<img src="https://docs.google.com/drawings/d/1sLx_t031l82kP3Gq7547C1sGcQWgIxViPYfxCo-ZJto/pub?w=340&h=205" alt="">
18+
19+
我把`dom`标记为红色,因为`dom`更新通常比较昂贵.有时候,`app`内部有很多`model`类来表示一些状态,对于我来说,这些类应该实现一些内部应用细节.
20+
21+
`react`提供了一个不同的,而又强大的方式来更新`dom`,代替直接更新`dom`的方式,这种方式就是`virtual dom`,`react`利用它来更新匹配的真实`dom`,像下图这样:
22+
23+
<img src="https://docs.google.com/drawings/d/11ugBTwDkqn6p2n5Fkps1p3Elp8ZToIRzXzvM4LJMYaU/pub?w=543&h=229" alt="">
24+
25+
引入一个扩展层怎么样来保持速度的提升?如果在它们的顶部添加一个层能加快速度,岂不是意味着浏览器没有达到最理想的`dom`实现?
26+
27+
这将意味着,除开`virtual dom`有不同的语法跟真实`dom`相比,更显著的是,更新`virtual dom`并不能保证能够马上影响真实`dom`,`react`将会等到事件循环结束,并且利用一个`diff`查找算法来尽可能少的更新真实`dom`.
28+
29+
批量更新`dom`以及一个非常小的`diff`算法都是`react`框架自身提供的,任务应用程序像这样做的话都可以像`react`那样高效,但是手动更新`dom`太单调而且容易出问题,这些问题`react`都会帮你处理很好的.
30+
31+
### Components
32+
33+
我之前提到过`virtual dom`跟真实`dom`在语法有上很大差别,但是`virtual dom``api`方法上也有显著的差别.在真实`dom`树下的节点可以理解为一个`element`,但是在`virtual dom`下的节点它是一个完整抽象的组件.
34+
35+
`react`中使用组件非常重要,因为使用组件,在更新`dom`修改的时候,利用内部的`diff`算法可以避免整个`dom`树的查找,从而提高更新`dom`的效率.
36+
37+
为了找到使用组件的好处,我们来定义一个简单的组件,下面的例子来自于官方提供的`hello world`
38+
39+
```js
40+
41+
/** @jsx React.DOM */
42+
var HelloMessage = React.createClass({
43+
render: function() {
44+
return <div>Hello {this.props.name}</div>;
45+
}
46+
});
47+
48+
React.renderComponent(<HelloMessage name="John" />, mountNode);
49+
50+
```
51+
52+
上面的例子里并没有过多的解释组件的好处,甚至提供了一个独立的组件思想,让我来为大家一一解答这些
53+
54+
上面的例子里首先创建了一个`react`组件类名为`HelloMessage`,然后创建了一个`virtual dom`(`<HelloMessage>`其实是`HelloMessage`组件类的一个实例),并把这个实例添加一个真实的`mountNode`dom节点中去.
55+
56+
首先要注意的是,`react virtual dom`是定制的,由应用程序定义的组件生成的(这里的是`HelloMessage`),而真实的`dom`都是由浏览器内置的组件创建的,像`<p>`,`<ul>`等,它没有任何特殊的应用逻辑,只是被动的接收你添加的事件监听;`react virtual dom`就不一样的,它提供了特殊的应用程序`api`以及相关的内部业务逻辑,它不仅仅是一个`dom update`库,而是一个抽象的构建视图的库.
57+
58+
另一方面,如果你一直关注`html`方面的新特性的话,那么对<a href="http://www.html5rocks.com/en/tutorials/webcomponents/customelements/" target="_blank">HTML custom elements may be coming to browsers soon</a>这篇文章讲的肯定不陌生,这个新特性给真实`dom`提供了一个跟`react virtual dom`相似的功能:定义包含自己业务逻辑的自定义元素.但是`react`无需等待当前所有浏览器支持这些特性,它允许你现在就可以使用这些相似的功能,比如`custom elements`,`shadow dom`.
59+
60+
好了,我们回到上面的例子上,我们已经创建了一个`<HelloMessage>`的组件并添加到一个`mountNode`节点中.我想以图形化的方式来解释`react`组件的运行机制,首先让我们想像一下`virtual dom`与真实`dom`的关系,我们先假定这个`mountNode`节点就是`body`元素,看下图:
61+
62+
<img src="https://docs.google.com/drawings/d/1AexTHVNPYIn5jZ_ysIDnmLWM8HcLdFvioe2RYzjl2H4/pub?w=595&h=344" alt="">
63+
64+
上图中的箭头标示`virtual dom`添加到真实`dom`中,这个动作很快,不过让我们来看看业务描述的视图正确的样子:
65+
66+
<img src="https://docs.google.com/drawings/d/1i0TE4RICkBC9cvwZqCQtz06fWt9r_bfqFj4v5CBmSRc/pub?w=557&h=387" alt="">
67+
68+
也就是说,页面上呈现的是我们自定义的组件`<HelloMessage>`,但是它的真实样子是什么样的呢?
69+
70+
渲染自定义的组件是通过`render()`函数,`react`没有规定什么时候调用或者多长时间调用一次`render`,只在收到合适的改变通知时才执行`render`函数,不管`render`函数返回什么,最终都会呈现在真实的`dom`里.
71+
72+
在我们的例子中,`render`函数返回一个`div`元素并包含内容,`react`调用它得到一个`<div>`元素并更新到真实的`dom`中,所以现在图看起来是这样的:
73+
74+
<img src="https://docs.google.com/drawings/d/1T98oIuZpr6VMgCkL_ZmxGa7pRKWtW-bI3JEEVsYnZ7E/pub?w=595&h=344" alt="">
75+
76+
不仅仅是更新`dom`,而且它会记住这次更新了什么,好方便下次执行`diff`算法的时候快速的获取差异从而提高更新速度.
77+
78+
我刚才忽略了一件事情,为什么`render`函数可以返回`dom`节点,这是因`jsx`语法而掩盖的,它不是简单的`javascript`,下面是编译`jsx`语法之后的原生`javascript`代码:
79+
80+
```js
81+
82+
/** @jsx React.DOM */
83+
var HelloMessage = React.createClass({displayName: 'HelloMessage',
84+
render: function() {
85+
return React.DOM.div(null, "Hello ", this.props.name);
86+
}
87+
});
88+
89+
React.renderComponent(HelloMessage( {name:"John"} ), mountNode);
90+
91+
```
92+
看到没,`react`并没有返回真实的`dom`,而是用跟真实`dom`等价的`react shdow dom`(像上面的`React.DOM.div`)来代替.
93+
94+
95+
### Representing State and Changes
96+
97+
到目前为止,一直都没说组件是怎样来同步更新的,如果`react`不能同步更新的话,那么就跟一些静态渲染库没什么两样了,比如`Mustache`,`HandlebarsJS`.但是`react`是一个高效的同步更新库,为了保证这些,必须允许改变.
98+
99+
`react`里的模型是以组件中的`state`属性来表示的,看看下面的例子,主要用来描述`state`属性的运用:
100+
101+
```js
102+
103+
/** @jsx React.DOM */
104+
var Timer = React.createClass({
105+
getInitialState: function() {
106+
return {secondsElapsed: 0};
107+
},
108+
tick: function() {
109+
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
110+
},
111+
componentDidMount: function() {
112+
this.interval = setInterval(this.tick, 1000);
113+
},
114+
componentWillUnmount: function() {
115+
clearInterval(this.interval);
116+
},
117+
render: function() {
118+
return (
119+
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
120+
);
121+
}
122+
});
123+
124+
React.renderComponent(<Timer />, mountNode);
125+
126+
```
127+
128+
`getInitialState`,`componentDidMount`,`componentWillUnmount`这三个回调函数是`react`自己在适当的时机调用的,第一个是相当于构造函数,第二个是组件添加的时候运行的,第三个组件删除的时候运行的,其实看它们的命名就大概知道这些函数的用法了.
129+
130+
所以我们可以得出下面几个猜想,在组件以及`state`改变的背后:
131+
132+
* `render()`是组件`state``props`中的唯一函数
133+
* `state`只在调用`setState`方法时才会改变
134+
* `props`只在父级组件重新渲染不同的`props`才会改变
135+
136+
(之前没有提及`props`,它是父级组件在渲染的时候传递过来的`attributes`)
137+
138+
所以之前我说`react`不会常常调用`render`函数的,因为它只在你调用`setState`或者父级组件重新渲染不同的`props`时才会重新调用`render`函数.
139+
140+
我们可以把这些信息联系在一起来描述一个数据变化的过程,从`app`创建一个`virtual dom`改变开始(图中用的是`ajax`调用)
141+
142+
<img src="https://docs.google.com/drawings/d/18PZGhVyiHRCX8KX2Jn5XBYIh4hBputrOazI3VRcGsSA/pub?w=616&h=397" alt="">
143+
144+
### Getting Data from the DOM
145+
146+
到目前为止我们只谈到了怎么改变原始`dom`,但是在真实的应用程序里,我们也会想从原始`dom`中获取数据,因为通常我们需要了解用户输入的信息,为了了解它的机制,让我们看看`react`官网提供的第三个例子
147+
148+
```js
149+
150+
/** @jsx React.DOM */
151+
var TodoList = React.createClass({
152+
render: function() {
153+
var createItem = function(itemText) {
154+
return <li>{itemText}</li>;
155+
};
156+
return <ul>{this.props.items.map(createItem)}</ul>;
157+
}
158+
});
159+
var TodoApp = React.createClass({
160+
getInitialState: function() {
161+
return {items: [], text: ''};
162+
},
163+
onChange: function(e) {
164+
this.setState({text: e.target.value});
165+
},
166+
handleSubmit: function(e) {
167+
e.preventDefault();
168+
var nextItems = this.state.items.concat([this.state.text]);
169+
var nextText = '';
170+
this.setState({items: nextItems, text: nextText});
171+
},
172+
render: function() {
173+
return (
174+
<div>
175+
>h3<TODO</h3>
176+
<TodoList items={this.state.items} />
177+
<form onSubmit={this.handleSubmit}>
178+
<input onChange={this.onChange} value={this.state.text} />
179+
<button>{'Add #' + (this.state.items.length + 1)}</button>
180+
</form>
181+
</div>
182+
);
183+
}
184+
});
185+
React.renderComponent(<TodoApp />, mountNode);
186+
187+
```
188+
189+
一眼看上去上面的例子可能是这样运行的,给真实的`dom`添加`onChange`事件,然后触发事件,调用`setState`来改变`ui`,如果你的`app`有模型类的话,有可能在事件处理里去更新你的模型类并且也会调用`setState`方法来告诉`react`属性改变了,如果你用了一些双向绑定的库,模型改变的话会自动更新视图,反之亦然,但是这样看起来其实是一种倒退.
190+
191+
其实这里有其它的解释,并不是看上去那样的,`react`并没有在真实的`dom`上面绑定`onChange`事件,而是在`document`上面绑定监听,让下面真实元素冒泡传递上来,然后分发给适当的`virtual dom`,这种方式能够提供性能(因为在每个真实的dom上面绑定事件是非常慢的)并且跨浏览器(浏览器中的事件本身就没有统一).
192+
193+
所以结合这些信息,我们能够到得一张真实的运行图,当用户输入改变`dom`时,像下面这样
194+
195+
<img src="https://docs.google.com/drawings/d/1BopE4GQuoDr9K_aaoamX4ePial0OF2elM0lHX-5Blcg/pub?w=628&h=475" alt="">
196+
197+
### Conclusions 总结
198+
199+
写完这篇文章之后让我对`react`有了更多的理解,下面列出的是我的一些观点:
200+
201+
* `React是一个视图库`.`react`没有利用任何你的模型类,一个`react`组件是一个视图层的概念,组件`state`也只是`ui`状态的一部分,你可以绑定任务第三方模型库到`react`中去().
202+
203+
* `React组件抽象化有利于推动dom的更新`.组件抽象是有原则的,并且组合的非常好,高效的dom更新来自于好的设计.
204+
205+
* `React组件获取dom更新不是很方便`.`react`编写事件处理来获取`dom`更新要比那种视图自动更新到模型的库要低级的多
206+
207+
* `React过于抽像`.大部分的时候你都是在编写`virtual dom`,但是有时候你需要直接跟原始`dom`交互,这个时候你可以看看官网提供的<a href="http://facebook.github.io/react/docs/working-with-the-browser.html">Working With the Browser</a>章节.
208+
209+
关于我对`js`方面的一些框架的看法,可以点击我的另外一篇文章<a href="http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/">The Future of JavaScript MVC Frameworks</a>
210+
211+
我不是一个`react`方面的专家,所以赶紧在评论里给我指出上面文章里的错误吧:)
212+
213+
214+
215+
216+
217+
218+
219+
220+
221+
222+

0 commit comments

Comments
 (0)