`,这都导致重新构建。
31 |
32 | 当卸载原先的树时,之前的DOM节点将销毁。实例组件执行`componentWillUnmount()`。当构建新的一个树,新的DOM元素将会插入DOM中。组件将会执行`componentWillMount()`以及`componentDidMount()`。与之前旧的树相关的状态都会丢失。
33 |
34 | 树的根节点以下的任何组件都会被卸载(unmount),其状态(state)都会丢失。例如,当比较:
35 |
36 | ```xml
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ```
45 |
46 | `Counter`将会被销毁,重新赋予新的实例。
47 |
48 | ### DOM元素类型相同
49 |
50 | 当React比较两个React DOM元素是相同类型时,React观察两者属性,保持底层DOM节点相同,并且仅更新已经改变的属性,例如:
51 |
52 | ```xml
53 |
54 |
55 |
56 | ```
57 |
58 | 通过比较两个元素,React会仅修改底层DOM节点的`className`属性。
59 |
60 | 当更新`style`属性,React也会仅仅只更新已经改变的属性,例如:
61 |
62 | ```xml
63 |
64 |
65 |
66 | ```
67 |
68 | 当React对两个元素进行转化的时候,仅会修改`color`,而不会修改`fontWeight`。
69 |
70 | 在处理完当前DOM节点后,React会递归处理子节点。
71 |
72 | ### 相同类型的组件
73 |
74 | 当一个组件更新的时候,组件实例保持不变,以便在渲染过程中保存state。React会更新组件实例的属性来匹配新的元素,并在元素实例上调用`componentWillReceiveProps()` and `componentWillUpdate()`。
75 |
76 | 接下来,`render()`方法会被调用并且`diff`算法对上一次的结果和新的结果进行递归。
77 |
78 | ### 子元素递归
79 |
80 | 当React递归DOM元素的子节点时,默认地在同一时刻迭代这两个子元素列表,并在有差异时生成一个变量。
81 |
82 | 例如,当给子元素末尾添加一个元素,在两棵树之间转化中性能就不错:
83 |
84 | ```xml
85 |
86 | - first
87 | - second
88 |
89 |
90 |
91 | - first
92 | - second
93 | - third
94 |
95 | ```
96 |
97 | React会比较`
first`树与`
second`树,然后插入`
third`树。
98 |
99 | 如果在开始处插入一个节点也是这样简单地实现,那么性能将会很差。例如,在下面两棵树的转化中性能就不佳。
100 |
101 | ```xml
102 |
103 | - Duke
104 | - Villanova
105 |
106 |
107 |
108 | - Connecticut
109 | - Duke
110 | - Villanova
111 |
112 | ```
113 |
114 | 如果React改变每一个元素,而不是知道应该保持`
Duke`和`
Villanova`子树,那么性能将是很大的问题。
115 |
116 | ### Keys
117 |
118 | 为了解决这个问题,React支持`key`属性。当组件拥有keys时,React用key比较原始树子节点与后续树子节点。如下所示,添加`key`属性给我们的低效率实例代码可以使得两个树的转化更加高效。
119 |
120 | ```xml
121 |
122 | - Duke
123 | - Villanova
124 |
125 |
126 |
127 | - Connecticut
128 | - Duke
129 | - Villanova
130 |
131 | ```
132 |
133 | 现在React知道拥有key属性为`2014`节点是新的。key为`2015`和`2016`的两个元素仅仅只是被移动而已。
134 |
135 | 事实上,查找一个key属性并不困难。你所将要展示的组件一般都有唯一的ID,因此你的数据可以作为key的来源。
136 |
137 | ```js
138 |
{item.name}
139 | ```
140 |
141 | 当情况不同时,你可以添加一个新的ID属性给你的model或者部分内容的hash值来作为key。key仅仅只需要在其兄弟节点中是唯一的,并非全局唯一。
142 |
143 | 作为最后一种方案,你可以将元素的下标作为key属性。如果元素永远不会被重新排序的情况下这样也是不错的,但是如果存在重新排序,性能将会很差。
144 |
145 | ## 折衷
146 |
147 | 需要记住的是一致化算法(reconciliation algorithm)仅仅只是一个实现细节。React会在每个操作上重新渲染整个应用,最终的结果可能是相同的。我们经常细化启发式算法,以便优化性能。
148 |
149 | 在最近的实现中,你能确定子树会在兄弟节点中移动,但你不能确定它可以移动到别的地方去。算法会重新渲染整个树。
150 |
151 | 因为React依赖于启发式算法,如果下面的假设没有实现,性能将会大大的损失。
152 |
153 | 1. 算法不会尝试匹配不同节点类型的子树。如果你发现在有输出类似,但两个节点类型不同,你可能需要将其转化成同种类型,事实上,我们没有在其中发现问题。
154 |
155 | 2. keys应该是稳定的、可预测的并且是唯一的。不稳定的key(类似于`Math.random()`函数的结果)可能会产生非常多的组件实例并且DOM节点也会非必要性的重新创建。这将会造成极大的性能损失和组件内state的丢失。
156 |
--------------------------------------------------------------------------------
/doc/Typechecking With PropTypes.md:
--------------------------------------------------------------------------------
1 | # Typechecking With PropTypes
2 |
3 | 随着应用规模的提升,你可以通过类型检测捕捉更多的bug。对于部分应用,你可能需要使用类似于[Flow](https://flowtype.org/)或者[TypeScript](https://www.typescriptlang.org/)的JavaScript扩展来对你整个应用类型进行类型检测。但即使你不使用这些,React内置了类型检测的功能。要在组件中进行类型检测,你可以赋值`propTypes`属性。
4 |
5 | ```javascript
6 | class Greeting extends React.Component {
7 | render() {
8 | return (
9 |
Hello, {this.props.name}
10 | );
11 | }
12 | }
13 |
14 | Greeting.propTypes = {
15 | name: React.PropTypes.string
16 | };
17 | ```
18 |
19 | `React.PropTypes` 输出了一系列的验证器,可以用来确保接收到的参数是有效的。例如,我们可以使用`React.PropTypes.string`语句。当给prop传递了一个不正确的值时,JavaScript控制台将会显示一条警告。出于性能的原因,`propTypes`仅在开发模式中检测。
20 |
21 | ### React.PropTypes
22 |
23 | 下面给出不同验证器的示例:
24 |
25 | ```javascript
26 | MyComponent.propTypes = {
27 | // 声明prop一个特定的基本类型,默认情况,是可选的。
28 | optionalArray: React.PropTypes.array,
29 | optionalBool: React.PropTypes.bool,
30 | optionalFunc: React.PropTypes.func,
31 | optionalNumber: React.PropTypes.number,
32 | optionalObject: React.PropTypes.object,
33 | optionalString: React.PropTypes.string,
34 | optionalSymbol: React.PropTypes.symbol,
35 |
36 | // 任何可以被渲染:numbers, strings, elements,或者是包含这些类型的数组(或者是片段)
37 | optionalNode: React.PropTypes.node,
38 |
39 | // A React element.
40 | optionalElement: React.PropTypes.element,
41 |
42 | // 声明props是一个类,使用JS的instanceof操作符
43 | optionalMessage: React.PropTypes.instanceOf(Message),
44 |
45 | // 声明prop是特定的值,类似于枚举
46 | optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
47 |
48 | // 多种类型其中之一
49 | optionalUnion: React.PropTypes.oneOfType([
50 | React.PropTypes.string,
51 | React.PropTypes.number,
52 | React.PropTypes.instanceOf(Message)
53 | ]),
54 |
55 | // 包含测定类型的数组
56 | optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
57 |
58 | // 值为特定类型的对象
59 | optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
60 |
61 | // 特定形式的对象
62 | optionalObjectWithShape: React.PropTypes.shape({
63 | color: React.PropTypes.string,
64 | fontSize: React.PropTypes.number
65 | }),
66 |
67 | // 可以为上面的声明后添加`isRequired`使得如果没有提供props会给出warning
68 | // You can chain any of the above with `isRequired` to make sure a warning
69 | // is shown if the prop isn't provided.
70 | requiredFunc: React.PropTypes.func.isRequired,
71 |
72 | // 任何值
73 | requiredAny: React.PropTypes.any.isRequired,
74 |
75 | // 可以声明自定义的验证器,如果验证失败返回Error对象。不要使用`console.warn`或者throw
76 | // 因为这不会在`oneOfType`类型的验证器中起作用。
77 | customProp: function(props, propName, componentName) {
78 | if (!/matchme/.test(props[propName])) {
79 | return new Error(
80 | 'Invalid prop `' + propName + '` supplied to' +
81 | ' `' + componentName + '`. Validation failed.'
82 | );
83 | }
84 | },
85 |
86 | // 也可以声明`arrayOf`和`objectOf`类型的验证器,如果验证失败需要返回Error对象。
87 | // 会在数组或者对象的每一个元素上调用验证器。验证器的前两个参数分别是数组或者对象本身,
88 | // 以及当前元素的键值。
89 | customArrayProp: React.PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
90 | if (!/matchme/.test(propValue[key])) {
91 | return new Error(
92 | 'Invalid prop `' + propFullName + '` supplied to' +
93 | ' `' + componentName + '`. Validation failed.'
94 | );
95 | }
96 | })
97 | };
98 | ```
99 |
100 | ### Requiring Single Child
101 |
102 | 你可以使用`React.PropTypes.element`指定仅可以将单一子元素作为子节点传递给组件。
103 |
104 | ```javascript
105 | class MyComponent extends React.Component {
106 | render() {
107 | // This must be exactly one element or it will warn.
108 | const children = this.props.children;
109 | return (
110 |
111 | {children}
112 |
113 | );
114 | }
115 | }
116 |
117 | MyComponent.propTypes = {
118 | children: React.PropTypes.element.isRequired
119 | };
120 | ```
121 |
122 | ### 默认Prop值
123 |
124 | 通过赋值特殊的`defaultProps`属性,你可以为`props`定义默认值:
125 |
126 | ```javascript
127 | class Greeting extends React.Component {
128 | render() {
129 | return (
130 |
Hello, {this.props.name}
131 | );
132 | }
133 | }
134 |
135 | // Specifies the default values for props:
136 | Greeting.defaultProps = {
137 | name: 'Stranger'
138 | };
139 |
140 | // Renders "Hello, Stranger":
141 | ReactDOM.render(
142 |
,
143 | document.getElementById('example')
144 | );
145 | ```
146 |
147 | 如果父组件没有为`this.props.name`传值,`defaultProps`会给其一个默认值。`propTypes`的类型检测是在`defaultProps`解析之后发生的,因此也会对默认属性`defaultProps`进行类型检测。
148 |
--------------------------------------------------------------------------------
/doc/React Without ES6.md:
--------------------------------------------------------------------------------
1 | # React Without ES6
2 |
3 | 通常情况下你可以用纯JavaScript类定义一个组件:
4 |
5 | ```javascript
6 | class Greeting extends React.Component {
7 | render() {
8 | return
Hello, {this.props.name}
;
9 | }
10 | }
11 | ```
12 |
13 | 如果你不使用ES6,你可以使用`React.createClass`来定义:
14 |
15 | ```javascript
16 | var Greeting = React.createClass({
17 | render: function() {
18 | return
Hello, {this.props.name}
;
19 | }
20 | });
21 | ```
22 | 除了部分特性,ES6中类的API非常类似于函数`React.createClass`
23 |
24 | ## 声明属性类型和默认属性
25 |
26 | 在函数和class类中,`propTypes`和`defaultProps`被定义为组件内部的属性:
27 |
28 | ```javascript
29 | class Greeting extends React.Component {
30 | // ...
31 | }
32 |
33 | Greeting.propTypes = {
34 | name: React.PropTypes.string
35 | };
36 |
37 | Greeting.defaultProps = {
38 | name: 'Mary'
39 | };
40 | ```
41 |
42 | 在`React.createClass()`函数中,你需要在所传递的对象中定义`propTypes`属性和`getDefaultProps()`方法:
43 |
44 | ```javascript
45 | var Greeting = React.createClass({
46 | propTypes: {
47 | name: React.PropTypes.string
48 | },
49 |
50 | getDefaultProps: function() {
51 | return {
52 | name: 'Mary'
53 | };
54 | },
55 |
56 | // ...
57 |
58 | });
59 | ```
60 |
61 | ## 设置初始化状态
62 |
63 | 在ES6类中,你可以在构造函数通过给`this.state`辅助定义初始状态:
64 |
65 | ```javascript
66 | class Counter extends React.Component {
67 | constructor(props) {
68 | super(props);
69 | this.state = {count: props.initialCount};
70 | }
71 | // ...
72 | }
73 | ```
74 |
75 | 在`React.createClass()`函数中,你可以使用`getInitialState`方法返回初始状态:
76 |
77 | ```javascript
78 | var Counter = React.createClass({
79 | getInitialState: function() {
80 | return {count: this.props.initialCount};
81 | },
82 | // ...
83 | });
84 | ```
85 |
86 | ## 自动绑定
87 |
88 | 在以ES6 class方式声明的React组件中,方法遵循与普通ES6的class中相同的语义。也就是说方法不会自动绑定到实例中,你必须在构造函数中显式的使用`.bind(this)`:
89 |
90 | ```javascript
91 | class SayHello extends React.Component {
92 | constructor(props) {
93 | super(props);
94 | this.state = {message: 'Hello!'};
95 | // This line is important!
96 | this.handleClick = this.handleClick.bind(this);
97 | }
98 |
99 | handleClick() {
100 | alert(this.state.message);
101 | }
102 |
103 | render() {
104 | // Because `this.handleClick` is bound, we can use it as an event handler.
105 | return (
106 |
109 | );
110 | }
111 | }
112 | ```
113 |
114 | 在`React.createClass()`方式中,并不需要这么做,因为方法可以自动绑定。
115 |
116 | ```javascript
117 | var SayHello = React.createClass({
118 | getInitialState: function() {
119 | return {message: 'Hello!'};
120 | },
121 |
122 | handleClick: function() {
123 | alert(this.state.message);
124 | },
125 |
126 | render: function() {
127 | return (
128 |
131 | );
132 | }
133 | });
134 | ```
135 |
136 | 这意味着在使用ES6 class方式下对于事件处理函数你需要编写更多的样本代码,但是在大型应用中具有更好的性能。
137 |
138 | 如果你不想使用样本代码,你可以使用Babel中**实验性***的[提案](https://babeljs.io/docs/plugins/transform-class-properties/)
139 |
140 | ```javascript
141 | class SayHello extends React.Component {
142 | constructor(props) {
143 | super(props);
144 | this.state = {message: 'Hello!'};
145 | }
146 | // WARNING: this syntax is experimental!
147 | // Using an arrow here binds the method:
148 | handleClick = () => {
149 | alert(this.state.message);
150 | }
151 |
152 | render() {
153 | return (
154 |
157 | );
158 | }
159 | }
160 | ```
161 | 请注意,上述语法是**实验性**的,可能将来会发生改变,或者这个提案可能不会纳入语言范畴。
162 |
163 | 如果你想更稳妥的方法,你有一下的选择:
164 |
165 | * 在构造函数中绑定方法
166 | * 使用箭头函数 `onClick={(e) => this.handleClick(e)}`.
167 | * 使用 `React.createClass()`.
168 |
169 | ## Mixins
170 |
171 | >**注意:**
172 | >
173 | > ES6是不支持mixin的,因此,当你用ES6 class编写React程序时是不支持mixins的
174 | >
175 | >**我们也在使用mixins的情况下发现了部分问题,所以我们不推荐目前使用**
176 | >
177 | >以下部分仅用来参考
178 |
179 | 有时不同的组件可能会共用部分方法,这些方法会被称为[横切关注点(cross-cutting concerns)](https://en.wikipedia.org/wiki/Cross-cutting_concern)
180 |
181 | [`React.createClass`](/react/docs/top-level-api.html#react.createclass) 可以允许你使用mixins。
182 |
183 | 一个常见的使用场景是组件间隔一段时间自我更新。使用`setInterval()`很容易实现,但是为了节省内存空间必须在不使用时取消。React提供了[生命周期方法](/react/docs/working-with-the-browser.html#component-lifecycle),可以通知你组件创建和销毁。我们编写一个简单的mixin,执行方法可以提供`setInterval()`方法,并且在组件销毁时可以自动被清除。
184 |
185 | ```javascript
186 | var SetIntervalMixin = {
187 | componentWillMount: function() {
188 | this.intervals = [];
189 | },
190 | setInterval: function() {
191 | this.intervals.push(setInterval.apply(null, arguments));
192 | },
193 | componentWillUnmount: function() {
194 | this.intervals.forEach(clearInterval);
195 | }
196 | };
197 |
198 | var TickTock = React.createClass({
199 | mixins: [SetIntervalMixin], // Use the mixin
200 | getInitialState: function() {
201 | return {seconds: 0};
202 | },
203 | componentDidMount: function() {
204 | this.setInterval(this.tick, 1000); // Call a method on the mixin
205 | },
206 | tick: function() {
207 | this.setState({seconds: this.state.seconds + 1});
208 | },
209 | render: function() {
210 | return (
211 |
212 | React has been running for {this.state.seconds} seconds.
213 |
214 | );
215 | }
216 | });
217 |
218 | ReactDOM.render(
219 |
,
220 | document.getElementById('example')
221 | );
222 | ```
223 |
224 | 如果一个组件使用多个mixin,不同的mixin中定义了相同的生命周期方法(例如,不容的mixin中都想要在组件销毁时做相应的清理),这些生命周期函数都会被调用。在组件内部的生命周期方法执行完毕后,mixin中的方法将会按照mixin的顺序依次执行。
225 |
--------------------------------------------------------------------------------
/doc/Context.md:
--------------------------------------------------------------------------------
1 | # Context
2 |
3 | 在React中,在React组件中很容易追踪数据流。当你观察组件时,你可以找出哪些属性(props)被传递,这使得你的应用非常容易理解。
4 |
5 | 在某些场景下,你想在整个组件树中传递数据,但却不想手动地在每一层传递属性。你可以直接在React中使用强大的`context` API解决上述问题。
6 |
7 | ## 为什么不要使用Context
8 |
9 | 绝大多数的应用程序不需要使用`context`。
10 |
11 | 如果你希望使用应用程序更加稳定就不要使用context。这只是一个实验性的API并且可能在未来的React版本中移除。
12 |
13 | 如果你不熟悉[React](https://github.com/reactjs/redux)或者[Mobx](https://github.com/mobxjs/mobx)这类state管理库,就不要使用`context`。对于许多应用程序,上述库和`state`绑定是管理`state`不错的选择。`Redux`相比`context`是更好的解决方法。
14 |
15 | 如果你不是一个有经验的React开发者,就不要使用`context`。更好的方式是使用`props`和`state`。
16 |
17 | 如果你不顾这些警告仍然坚持使用`context`,尝试着将`context`的使用隔离在一个将小的范围内,并且在可能的情况下直接使用`context`,以便在API改变的时候进行升级。
18 |
19 | ## 如何使用Context
20 |
21 | 假定有下面的结构:
22 |
23 | ```javascript
24 | class Button extends React.Component {
25 | render() {
26 | return (
27 |
30 | );
31 | }
32 | }
33 |
34 | class Message extends React.Component {
35 | render() {
36 | return (
37 |
38 | {this.props.text}
39 |
40 | );
41 | }
42 | }
43 |
44 | class MessageList extends React.Component {
45 | render() {
46 | const color = "purple";
47 | const children = this.props.messages.map((message) =>
48 |
49 | );
50 | return
{children}
;
51 | }
52 | }
53 | ```
54 |
55 | 在这个例子中,我们手动地传递`color`属性使得`Button`和`Message`设置正确的样式。使用`context`,我们可以自动在组件树中传递属性。
56 |
57 | ```javascript{4,11-13,19,26-28,38-40}
58 | class Button extends React.Component {
59 | render() {
60 | return (
61 |
64 | );
65 | }
66 | }
67 |
68 | Button.contextTypes = {
69 | color: React.PropTypes.string
70 | };
71 |
72 | class Message extends React.Component {
73 | render() {
74 | return (
75 |
76 | {this.props.text}
77 |
78 | );
79 | }
80 | }
81 |
82 | class MessageList extends React.Component {
83 | getChildContext() {
84 | return {color: "purple"};
85 | }
86 |
87 | render() {
88 | const children = this.props.messages.map((message) =>
89 |
90 | );
91 | return
{children}
;
92 | }
93 | }
94 |
95 | MessageList.childContextTypes = {
96 | color: React.PropTypes.string
97 | };
98 | ```
99 |
100 | 通过给`MessageList`添加`childContextTypes`和`childContextTypes`(`context`提供者),React自动地向下传递信息,任何子树(例如:`Button`)可以通过定义`contextTypes`访问到属性。
101 |
102 | 如果没有定义`contextTypes`,`context`将是一个空的object。
103 |
104 | ## 父子耦合
105 |
106 | Context可以构建API使得父组价和子组件进行相互通信。例如:[React Router V4](https://reacttraining.com/react-router)工作机制如下:
107 |
108 | ```javascript
109 | import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
110 |
111 | const BasicExample = () => (
112 |
113 |
114 |
115 | - Home
116 | - About
117 | - Topics
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | );
128 | ```
129 |
130 | 通过从`Router`中传递相关信息,`Router`中的每一个`Link`和`Route`都可以与之通信。
131 |
132 | 在你构建包含类似于上述的API的组件之前,考虑是否有其他的更清晰的选择。例如,你可以传递整个React组件作为props传递。
133 |
134 | ## 在生命周期函数中使用`Context`
135 |
136 | 如果`contextTypes`在组件中定义,下列的[生命周期函数](https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle)将接受一个额外的参数:`context`对象
137 |
138 | - [`constructor(props, context)`](https://facebook.github.io/react/docs/react-component.html#constructor)
139 | - [`componentWillReceiveProps(nextProps, nextContext)`](https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops)
140 | - [`shouldComponentUpdate(nextProps, nextState, nextContext)`](https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate)
141 | - [`componentWillUpdate(nextProps, nextState, nextContext)`](https://facebook.github.io/react/docs/react-component.html#componentwillupdate)
142 | - [`componentDidUpdate(prevProps, prevState, prevContext)`](https://facebook.github.io/react/docs/react-component.html#componentdidupdate)
143 |
144 | ## 在无状态的函数式组件中使用`Context`
145 |
146 | 如果`contextType`被定义为函数的属性,无状态函数式组件也能够引用`context`。下面的代码演示了一个`Button`状态的函数式组件。
147 |
148 | ```javascript
149 | const Button = ({children}, context) =>
150 |
;
153 |
154 | Button.contextTypes = {color: React.PropTypes.string};
155 | ```
156 |
157 | ## 更新Context
158 |
159 | 别这么做!
160 |
161 | React有一个API更新context,但是它打破了基本流程,不应该使用。
162 |
163 | `getChildContext`函数将会在每次`state`或者`props`改变时调用。为了更新`context`中的数据,使用`this.setState`触发本地状态的更新。这将触发一个的`context`并且数据的改变可以被子元素收到。
164 |
165 | ```javascript
166 | class MediaQuery extends React.Component {
167 | constructor(props) {
168 | super(props);
169 | this.state = {type:'desktop'};
170 | }
171 |
172 | getChildContext() {
173 | return {type: this.state.type};
174 | }
175 |
176 | componentDidMount() {
177 | const checkMediaQuery = () => {
178 | const type = window.matchMedia("(min-width: 1025px)").matches ? 'desktop' : 'mobile';
179 | if (type !== this.state.type) {
180 | this.setState({type});
181 | }
182 | };
183 |
184 | window.addEventListener('resize', checkMediaQuery);
185 | checkMediaQuery();
186 | }
187 |
188 | render() {
189 | return this.props.children;
190 | }
191 | }
192 |
193 | MediaQuery.childContextTypes = {
194 | type: React.PropTypes.string
195 | };
196 | ```
197 |
198 | 问题在于,组件提供的`context`值改变,后代元素如果`shouldComponentUpdate`返回`false`那么`context`的将不会更新。这使得使用`context`的组件完全失控,所以基本上没有办法可靠的更新`context`。[这篇blog](https://medium.com/@mweststrate/how-to-safely-use-react-context-b7e343eff076)很好的解释了为什么这是一个问题并如果绕过它。
199 |
--------------------------------------------------------------------------------
/doc/JSX In Depth.md:
--------------------------------------------------------------------------------
1 | # JSX In Depth
2 |
3 | 从根本上讲,JSX只是`React.createElement(component, props, ...children)`函数的语法糖。JSX代码:
4 |
5 | ```js
6 |
7 | Click Me
8 |
9 | ```
10 |
11 | 会被编译为:
12 |
13 | ```js
14 | React.createElement(
15 | MyButton,
16 | {color: 'blue', shadowSize: 2},
17 | 'Click Me'
18 | )
19 | ```
20 |
21 | 如果不存在子节点,你可以使用自闭合格式的标签。例如:
22 |
23 | ```js
24 |
25 | ```
26 |
27 | 会被编译为:
28 |
29 | ```js
30 | React.createElement(
31 | 'div',
32 | {className: 'sidebar'},
33 | null
34 | )
35 | ```
36 | 如果你想要了解JSX是如何编译为JavaScript,可以尝试[在线Babel编译器](https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=es2015%2Creact%2Cstage-0&code=function%20hello()%20%7B%0A%20%20return%20%3Cdiv%3EHello%20world!%3C%2Fdiv%3E%3B%0A%7D).
37 |
38 | ## 指定React元素类型
39 |
40 | JSX标签的开始部分决定了React元素的类型。
41 |
42 | 首字母大写的标签指示JSX标签是一个React组件。这些标签会被编译成命名变量的直接引用。所以如果你使用JSX的`
`表达式,`Foo`必须在作用域中。
43 |
44 | ### React必须在作用域中存在
45 |
46 | 因为JSX被编译为调用`React.createElement`的形式,所以`React`库必须在代码的作用域中。
47 |
48 | 例如,在下面的代码中,虽然`React`和`CustomButton`并没有在JavaScript代码中直接使用,但是必须二者都需要在代码中引用。
49 |
50 | ```js{1,2,5}
51 | import React from 'react';
52 | import CustomButton from './CustomButton';
53 |
54 | function WarningButton() {
55 | // return React.createElement(CustomButton, {color: 'red'}, null);
56 | return
;
57 | }
58 | ```
59 |
60 | 如果你没有选择打包JavaScript,而是通过在script标签中添加了React,那么全局中已经存在`React`。
61 |
62 | ### 对JSX类型使用点表示法
63 |
64 | 在JSX中,你可以通过点表示法引用React组件。如果仅有一个单一模块(module),但却对外提供多个React组件时,点表示法就非常的方便。
65 |
66 | ```js{10}
67 | import React from 'react';
68 |
69 | const MyComponents = {
70 | DatePicker: function DatePicker(props) {
71 | return
Imagine a {props.color} datepicker here.
;
72 | }
73 | }
74 |
75 | function BlueDatePicker() {
76 | return
;
77 | }
78 | ```
79 |
80 | ### 自定义组件必须以大写字母开头
81 |
82 | 对于以小写字母开头的元素类型,其表示类似于`
`或者`
`的内置组件,会给`React.createElement`方法传递字符串`div`或者`span`。以大写字母开头的类型,类似于``,将会被编译成`React.createElement(Foo)`,对应于自定义组件或者在JavaScript文件中引入的组件。
83 |
84 | 我们建议给组件以大写字母开头的方式命名。如果你已经有以小写字母开头的组件,需要在JSX中使用前将其赋值给以大写字母开头的变量。例如下面代码无法按照预期运行:
85 |
86 | ```js{3,4,10,11}
87 | import React from 'react';
88 |
89 | // Wrong! This is a component and should have been capitalized:
90 | function hello(props) {
91 | // Correct! This use of is legitimate because div is a valid HTML tag:
92 | return
Hello {props.toWhat}
;
93 | }
94 |
95 | function HelloWorld() {
96 | // Wrong! React thinks
is an HTML tag because it's not capitalized:
97 | return
;
98 | }
99 | ```
100 |
101 | 为了修复这个问题,我们将`hello`重命名为`Hello`,然后在引用时使用`
`
102 |
103 | ```js{3,4,10,11}
104 | import React from 'react';
105 |
106 | // Correct! This is a component and should be capitalized:
107 | function Hello(props) {
108 | // Correct! This use of
is legitimate because div is a valid HTML tag:
109 | return
Hello {props.toWhat}
;
110 | }
111 |
112 | function HelloWorld() {
113 | // Correct! React knows
is a component because it's capitalized.
114 | return
;
115 | }
116 | ```
117 |
118 | ### 运行时决定React类型
119 |
120 | 不能使用普通的表达式作为React元素类型。如果你想使用通用表达式来表示元素类型,首先你需要将其赋值给大写的变量。这通常会出现在根据不同的props渲染不同的组件:
121 |
122 | ```js{10,11}
123 | import React from 'react';
124 | import { PhotoStory, VideoStory } from './stories';
125 |
126 | const components = {
127 | photo: PhotoStory,
128 | video: VideoStory
129 | };
130 |
131 | function Story(props) {
132 | // Wrong! JSX type can't be an expression.
133 | return
;
134 | }
135 | ```
136 |
137 | 为了解决这个问题,首先需要将其赋值给一个以大写字母开头的变量。
138 |
139 | ```js{9-11}
140 | import React from 'react';
141 | import { PhotoStory, VideoStory } from './stories';
142 |
143 | const components = {
144 | photo: PhotoStory,
145 | video: VideoStory
146 | };
147 |
148 | function Story(props) {
149 | // Correct! JSX type can be a capitalized variable.
150 | const SpecificStory = components[props.storyType];
151 | return
;
152 | }
153 | ```
154 |
155 | ## JSX中的props
156 |
157 | 在JSX中有下面几种不同的方式赋值props。
158 |
159 | ### JavaScript 表达式
160 |
161 | 你可以给props传递一个用`{}`包裹的JavaScript表达式,例如:
162 |
163 | ```js
164 |
165 | ```
166 |
167 | 对`MyComponent`对讲,`props.foo`的值为`10`,因为表达式`1 + 2 + 3 + 4`会被计算。
168 |
169 | 对于JavaScript,`if`语句和`for`循环不是表达式,因此不能在JSX中直接使用。但你可以将其写入代码块中,例如:
170 |
171 | ```js{3-7}
172 | function NumberDescriber(props) {
173 | let description;
174 | if (props.number % 2 == 0) {
175 | description =
even;
176 | } else {
177 | description =
odd;
178 | }
179 | return
{props.number} is an {description} number
;
180 | }
181 | ```
182 |
183 | ### 字符串
184 |
185 | 你可以给prop传入字符串,下面两种JSX表达式是等价的:
186 |
187 | ```js
188 |
189 |
190 |
191 | ```
192 |
193 | 当给props传递字符串时,其值是未转义的HTML(HTML-unescaped)。下面两种JSX表达式是等价的:
194 |
195 | ```js
196 |
197 |
198 |
199 | ```
200 |
201 | 这种行为通常讲并不重要,这里所提到的只是为了完整性。
202 |
203 | ### Props Default to "True"
204 |
205 | 如果prop没有传入值,默认为`true`。下面两种JSX表达式是等价的:
206 |
207 | ```js
208 |
209 |
210 |
211 | ```
212 |
213 | 通常情况下,我们不建议使用这种类型,因为这会与[ES6中的对象shorthand](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015)混淆。ES6 shorthand中`{foo}`指的是`{foo: foo}`而不是`{foo: true}`。
214 |
215 | ### 属性展开
216 |
217 | 如果存在一个object类型的`props`并且想在JSX中传入,你可以使用展开符`...`传入整个props对象。下面两种组件是等价的:
218 |
219 | ```js{7}
220 | function App1() {
221 | return
;
222 | }
223 |
224 | function App2() {
225 | const props = {firstName: 'Ben', lastName: 'Hector'};
226 | return
;
227 | }
228 | ```
229 |
230 | 当你构建一个container时,属性展开非常有用。然而,这可能会使得你的代码非常混乱,因为这使得非常多不相关的props传递给组件,但组件并不需要这些props。因此我们建议谨慎使用该语法。
231 |
232 | ## JSX中的Children
233 |
234 | 在包括开标签和闭标签在内的JSX表示式中,标签中的内容会被传递一个特殊的props:`props.children`,下面有好几种方式传递`children`
235 |
236 | ### 字符串
237 |
238 | 您可以在开标签和闭合标签中写入字符串,这对于内置HTML元素非常有用,例如:
239 |
240 | ```js
241 |
Hello world!
242 | ```
243 | 这是有效的JSX,`MyComponent`的`props.children`属性值为`"Hello world!"`。HTML是非转义的,因此你可以像写HTML一样来写JSX。
244 |
245 | ```html
246 |
This is valid HTML & JSX at the same time.
247 | ```
248 |
249 | JSX会删除每行开头和结尾的空格,并且也会删除空行。邻接标签的空行也会被移除,字符串之间的空格会被压缩成一个空格,因此下面的渲染效果都是相同的:
250 |
251 | ```js
252 |
Hello World
253 |
254 |
255 | Hello World
256 |
257 |
258 |
259 | Hello
260 | World
261 |
262 |
263 |
264 |
265 | Hello World
266 |
267 | ```
268 |
269 | ### JSX Children
270 |
271 | 你可以传递多个JSX元素作为子元素,这对显示嵌套组件非常有用:
272 |
273 | ```js
274 |
275 |
276 |
277 |
278 | ```
279 |
280 | 你可以混合不同类型的子元素,因此你可以混用字符串和JSX子元素。这是JSX与HTML另一点相似的地方,因此下面是HTML和JSX均是有效的:
281 |
282 | ```html
283 |
284 | Here is a list:
285 |
286 | - Item 1
287 | - Item 2
288 |
289 |
290 | ```
291 | React组件不能返回多个React元素,但是单个JSX表达式可以有多个子元素,因此如果你想要渲染多个元素,你可以像上面一样,将其包裹在`div`中
292 |
293 | ### JavaScript 表达式
294 |
295 | 通过使用`{}`包裹,你可以将任何的JavaScript元素而作为子元素传递,下面表达式是等价的:
296 |
297 | ```js
298 |
foo
299 |
300 |
{'foo'}
301 | ```
302 |
303 | 这对于渲染长度不定的JSX表达式列表非常有用,例如,下面会渲染HTML list:
304 |
305 | ```js{2,9}
306 | function Item(props) {
307 | return
{props.message};
308 | }
309 |
310 | function TodoList() {
311 | const todos = ['finish doc', 'submit pr', 'nag dan to review'];
312 | return (
313 |
314 | {todos.map((message) => )}
315 |
316 | );
317 | }
318 | ```
319 |
320 | JavaScript表达式可以和其他类型的子元素混用,这对于字符串模板非常有用:
321 |
322 | ```js{2}
323 | function Hello(props) {
324 | return
Hello {props.addressee}!
;
325 | }
326 | ```
327 |
328 | ### Function类型的子元素
329 |
330 | 通常情况下,嵌入JSX中的JavaScript表达式会被认为是字符串、React元素或者是这些内容的列表。然而,`props.children`类似于其他的props,可以被传入任何数据,而不是仅仅只是React可以渲染的数据。例如,如果有自定义组件,其`props.children`的值可以是回调函数:
331 |
332 | ```js{4,13}
333 | // Calls the children callback numTimes to produce a repeated component
334 | function Repeat(props) {
335 | let items = [];
336 | for (let i = 0; i < props.numTimes; i++) {
337 | items.push(props.children(i));
338 | }
339 | return
{items}
;
340 | }
341 |
342 | function ListOfTenThings() {
343 | return (
344 |
345 | {(index) => This is item {index} in the list
}
346 |
347 | );
348 | }
349 | ```
350 |
351 | 传递给自定义组件的子元素可以是任何类型,只要在渲染之前组件可以将其转化为React能够处理的东西即可。这种用法并不常见,但是如果你需要扩展JSX的话,则会非常有用。
352 |
353 | ### Booleans, Null和Undefined都会被忽略
354 |
355 | `false`, `null`, `undefined`, 和 `true`都是有效的子类型。但是并不会被渲染,下面的JSX表达式渲染效果是相同的:
356 |
357 | ```js
358 |
359 |
360 |
361 |
362 |
{false}
363 |
364 |
{null}
365 |
366 |
{undefined}
367 |
368 |
{true}
369 | ```
370 |
371 | 在有条件性渲染React元素时非常有用。如果`showHeader`为`true`时,`
`会被渲染:
372 |
373 | ```js{2}
374 |
375 | {showHeader && }
376 |
377 |
378 | ```
379 |
380 | 需要注意的是,React仍然提供了["falsy"值](https://developer.mozilla.org/en-US/docs/Glossary/Falsy),例如数值`0`,仍然会被React渲染。例如:下面这个例子将不会表现为你所预期的那样(为空数组时不渲染组件),因为当`props.messages`为空数组时,将会渲染`0`
381 | ```js{2}
382 |
383 | {props.messages.length &&
384 |
385 | }
386 |
387 | ```
388 | 为了解决这个问题,你只要确保&&`前的表达式是boolean类型:
389 |
390 | ```js{2}
391 |
392 | {props.messages.length > 0 &&
393 |
394 | }
395 |
396 | ```
397 | 反过来,如果在输出中想要渲染`false`、`true`、`null`或者`undefined`,你必须先将其[转化为字符串](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#String_conversion):
398 |
399 |
400 | ```js{2}
401 |
402 | My JavaScript variable is {String(myVariable)}.
403 |
404 | ```
405 |
--------------------------------------------------------------------------------
/doc/Optimizing Performance.md:
--------------------------------------------------------------------------------
1 | # 性能优化
2 |
3 | 在更新UI时,React内部使用了多种技术最小化必要的DOM操作。对于大多数应用,使用React不需要额外的特定性能优化的情况下,就可以达到一个更快的用户交互。然而,下面有几种方式能够加快你的React应用。
4 |
5 | ## 使用生产环境
6 |
7 | 如果你在React应用中遇到性能的瓶颈,请确保你是在生产环境下测试。
8 |
9 | 默认地,React包含众多的帮助性的警告(warning)。这些警告在开发模式中非常有用。而然它们使得React体积庞大并性能下降,因此,你需要确保你是在生产模式下部署应用。
10 |
11 | 如果你不确定你部署的模式是否正确,你可以在Chrome中安装[React Developer Tools for Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)。如果你访问的网站是React生产模式,图标背景是深色:
12 |
13 | 
14 |
15 | 如果你访问的网站是React开发模式,图标的背景将会是红色:
16 |
17 | 
18 |
19 | 正常情况下,你会在开发过程中使用开发模式,当给用户部署应用使用生产模式。
20 |
21 | ### Create React App
22 |
23 | 如果你的工程使用[Create React App](https://github.com/facebookincubator/create-react-app),运行:
24 |
25 | ```
26 | npm run build
27 | ```
28 |
29 |
30 | 这将会在你的工程中`build/`目录下创建生产模式的应用。
31 |
32 | 记住,这是指针对于部署产品。对于普通的开发者,使用 `npm start`。
33 |
34 | ### Single-File Builds
35 |
36 | 我们提供了生产模式的React和React DOM的文件:
37 |
38 | ```html
39 |
40 |
41 | ```
42 |
43 | 需要记住的是以`.min.js`结尾的React文件仅适用于生产模式。
44 |
45 | ### Brunch
46 |
47 | 如果使用的高效地Brunch构建,安装[uglify-js-brunch](https://github.com/brunch/uglify-js-brunch)插件:
48 |
49 | ```
50 | # 使用npm
51 | npm install --save-dev uglify-js-brunch
52 |
53 | # 使用yarn
54 | yarn add --dev uglify-js-brunch
55 | ```
56 |
57 | 然后,通过给`build`命令添加`-p`来创建生产模式应用。在开发模式下不要传递`-p`标志或者使用上述插件,因为其隐藏了有用的React警告(warning)并是构建速度降低。
58 |
59 | ### Browserify
60 |
61 | 对于使用的高效地Browserify构建,安装下列插件:
62 |
63 | ```
64 | # 使用npm
65 | npm install --save-dev bundle-collapser envify uglify-js uglifyify
66 |
67 | # 使用Yarn
68 | yarn add --dev bundle-collapser envify uglify-js uglifyify
69 | ```
70 |
71 | 为了创建生产模式的应用,确实你添加了下列的转化规则(顺序很重要):
72 |
73 | * [`envify`](https://github.com/hughsk/envify)确保能设置正确的构建环境。全局安装(`-g`)。
74 | * [`uglifyify`](https://github.com/hughsk/uglifyify)能移除开发环境引入的文件。全局安装(`-g`)。
75 | * [`bundle-collapser`](https://github.com/substack/bundle-collapser)插件可以用数字替换模块ID。
76 | * 最后,使用[`uglify-js`](https://github.com/mishoo/UglifyJS2)压缩打包结果([查看为什么](https://github.com/hughsk/uglifyify#motivationusage))
77 |
78 | 例如:
79 |
80 | ```
81 | browserify ./index.js \
82 | -g [ envify --NODE_ENV production ] \
83 | -g uglifyify \
84 | -p bundle-collapser/plugin \
85 | | uglifyjs --compress --mangle > ./bundle.js
86 | ```
87 |
88 | >**注意:**
89 | >包名为`uglify-js`, 但提供名为`uglifyjs`。
90 | >并不是排版错误
91 |
92 | ### Rollup
93 |
94 | 对于使用的高效地Rollupy构建,安装下列插件:
95 |
96 | ```
97 | # 如果使用的是npm
98 | npm install --save-dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-uglify
99 |
100 | # 如果使用的yarn
101 | yarn add --dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-uglify
102 | ```
103 |
104 | 为了创建生产模式的应用,确实你添加了下列插件(顺序很重要):
105 |
106 | * [`replace`](https://github.com/rollup/rollup-plugin-replace) 插件确保构建正确的构建环境。
107 | * [`commonjs`](https://github.com/rollup/rollup-plugin-commonjs) 插件使得Rollup支持CommonJS。
108 | * [`uglify`](https://github.com/TrySound/rollup-plugin-uglify) 插件压缩最终打包的文件。
109 |
110 | ```js
111 | plugins: [
112 | // ...
113 | require('rollup-plugin-replace')({
114 | 'process.env.NODE_ENV': JSON.stringify('production')
115 | }),
116 | require('rollup-plugin-commonjs')(),
117 | require('rollup-plugin-uglify')(),
118 | // ...
119 | ]
120 | ```
121 |
122 | 完整的例子查看[gist](https://gist.github.com/Rich-Harris/cb14f4bc0670c47d00d191565be36bf0)
123 |
124 | 记住你仅需要在生产模式下使用,你不应该在开发模式下使用`uglify`或`replace`插件,因为它隐藏了有用的React警告并使得构建速度变慢。
125 |
126 | ### Webpack
127 |
128 | >**注意:**
129 | >
130 | >如果你使用的是Create React App, 查看[这个例子](#create-react-app).
131 | >这个小节针对于你直接配置Webpack
132 |
133 | 对于使用最高效地Rollupy构建,确保在生产配置下添加下面插件:
134 |
135 | ```js
136 | new webpack.DefinePlugin({
137 | 'process.env': {
138 | NODE_ENV: JSON.stringify('production')
139 | }
140 | }),
141 | new webpack.optimize.UglifyJsPlugin()
142 | ```
143 |
144 | 更多的信息可以了解[Webpack文档](https://webpack.js.org/guides/production-build/)
145 |
146 | 记住你仅需要在生产模式下使用。你不应该在开发模式下使用`UglifyJsPlugin`或`DefinePlugin`插件,因为它隐藏了有用的React警告并使得构建速度变慢。
147 |
148 | ## 使用Chrome Timeline分析组件性能
149 |
150 | 在开发模式中,你可以在支持相关功能的浏览器中使用性能工具来可视化组件安装(mount),更新(update)和卸载(unmount)的各个过程。例如:
151 |
152 |

153 |
154 | 在Chrome操作如下:
155 |
156 | 1. 通过添加`?react_perf`查询字段加载你的应用(例如:`http://localhost:3000/?react_perf`)
157 |
158 | 2. 打开Chrome DevTools **[Timeline](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/timeline-tool)** 并点击**Record**。
159 |
160 | 3. 执行你想要分析的操作,不要超过20秒,否则Chrome可能会挂起。
161 |
162 | 4. 停止记录。
163 |
164 | 5. 在**User Timing**下,React事件将会分组列出。
165 |
166 | 注意,上述数据是相对的,组件会在生产环境中有更好的性能。然而,这对你分析由于错误导致不相关的组件的更新、分析组件更新的深度和频率很有帮助。
167 |
168 | 目前Chrome,Edge和IE支持该特性,但是我们使用了标准的 [User Timing API](https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API),因此我们期待将来会有更多的浏览器支持。
169 |
170 | ## 避免Reconciliation
171 |
172 | React创建和维护了渲染UI的内部状态。其包括了组件返回的React元素。这些内部状态使得React只有在必要的情况下才会创建DOM节点和访问存在DOM节点,因为对JavaScript对象的操作是比DOM操作更快。这被称为"虚拟DOM",React Native也基于上述原理。
173 |
174 | 当组件的`props`和`state`更新时,React通过比较新返回的元素和之前渲染的元素来决定是否有必要更新DOM元素。如果二者不相等,则更新DOM元素。
175 |
176 | 在部分场景下,组件可以通过重写生命周期函数`shouldComponentUpdate`来优化性能。`shouldComponentUpdate`函数会在重新渲染流程前触发。`shouldComponentUpdate`的默认实现中返回的是`true`,使得React执行更新操作。
177 |
178 | ```javascript
179 | shouldComponentUpdate(nextProps, nextState) {
180 | return true;
181 | }
182 | ```
183 |
184 | 如果你的组件在部分场景下不需要更行,你可以在`shouldComponentUpdate`返回`false`来跳过整个渲染流程(包括调用`render`和之后流程)。
185 |
186 | ## shouldComponentUpdate
187 |
188 | 下面有一个组件子树,其中`SCU`代表`shouldComponentUpdate`函数返回结果。`vDOMEq`代表渲染的React元素是否相等。最后,圆圈内的颜色代表组件是否需要reconcile(译者注:reconcile代表React在每次需要渲染时,会先比较当前DOM内容和待渲染内容的差异, 然后再决定如何最优地更新DOM)
189 |
190 |

191 |
192 | 因为以C2为根节点的子树`shouldComponentUpdate`返回的是`false`,React不会尝试重新渲染C2,并且也不会尝试调用C4和C5的`shouldComponentUpdate`。
193 |
194 | 对于C1和C3,`shouldComponentUpdate`返回`false`,所以React需要向下遍历,对于C6,`shouldComponentUpdate`返回`false`,并且需要渲染的元素不相同,因此React需要更新DOM节点。
195 |
196 | 最后一个值得注意的例子是C8.React必须渲染这个组件,但是由于返回的React元素与之前渲染的元素相比是相同的,因此不需要更新DOM节点。
197 |
198 | 注意,React仅仅需要修改C6的DOM,这是必须的。对于C8来讲,通过比较渲染元素被剔除,对于C2子树和C7,因为`shouldComponentUpdate`被剔除,甚至都不需要比较React元素,也不会调用`render`方法。
199 |
200 | ## 例子
201 |
202 | 仅当`props.color`和`state.count`发生改变时,组件需要更新,你可以通过`shouldComponentUpdate`函数设置:
203 |
204 | ```javascript
205 | class CounterButton extends React.Component {
206 | constructor(props) {
207 | super(props);
208 | this.state = {count: 1};
209 | }
210 |
211 | shouldComponentUpdate(nextProps, nextState) {
212 | if (this.props.color !== nextProps.color) {
213 | return true;
214 | }
215 | if (this.state.count !== nextState.count) {
216 | return true;
217 | }
218 | return false;
219 | }
220 |
221 | render() {
222 | return (
223 |
228 | );
229 | }
230 | }
231 | ```
232 |
233 | 在上面的代码中,`shouldComponentUpdate`函数仅仅检查`props.color` 或者 `state.count`是否发生改变。如果这些值没有发生变化,则组件不会进行更新。如果你的组件更复杂,你可以使用类似于对`props`和`state`的所有属性进行"浅比较"这种模式来决定组件是否需要更新。这种模式非常普遍,因此React提供了一个helper实现上面的逻辑:继承`React.PureComponent`。因此,下面的代码是一种更简单的方式实现了相同的功能:
234 |
235 | ```js
236 | class CounterButton extends React.PureComponent {
237 | constructor(props) {
238 | super(props);
239 | this.state = {count: 1};
240 | }
241 |
242 | render() {
243 | return (
244 |
249 | );
250 | }
251 | }
252 | ```
253 |
254 | 大多数情况下,你可以使用`React.PureComponent`而不是自己编写`shouldComponentUpdate`。但`React.PureComponent`仅会进项浅比较,因此如果在props和state会突变(译者注:就是引用不发生变化,但指向的内容发生变化)导致浅比较失败的情况下就不能使用`React.PureComponent`。
255 |
256 | 如果props和state属性存在更复杂的数据结构,这可能是一个问题。例如,我们编写一个`ListOfWords`组件展现一个以逗号分隔的单词列表,在父组件`WordAdder`,当你点击一个按钮时会给列表添加一个单词。下面的代码是**不正确**的:
257 |
258 | ```javascript
259 | class ListOfWords extends React.PureComponent {
260 | render() {
261 | return
{this.props.words.join(',')}
;
262 | }
263 | }
264 |
265 | class WordAdder extends React.Component {
266 | constructor(props) {
267 | super(props);
268 | this.state = {
269 | words: ['marklar']
270 | };
271 | this.handleClick = this.handleClick.bind(this);
272 | }
273 |
274 | handleClick() {
275 | // This section is bad style and causes a bug
276 | const words = this.state.words;
277 | words.push('marklar');
278 | this.setState({words: words});
279 | }
280 |
281 | render() {
282 | return (
283 |
284 |
285 |
286 |
287 | );
288 | }
289 | }
290 | ```
291 |
292 | 问题是`PureComponent`只进行在旧的`this.props.words`与新的`this.props.words`之间进行前比较。因此在`WordAdder`组件中`handleClick`的代码会突变`words`数组。虽然数组中实际的值发生了变化,但旧的`this.props.words`和新的`this.props.words`值是相同的,即使`ListOfWords`需要渲染新的值,但是还是不会进行更新。
293 |
294 | ## 不可变数据的力量
295 |
296 | 避免这类问题最简单的方法是不要突变(mutate)props和state的值。例如,上述`handleClick`方法可以通过使用`concat`重写:
297 |
298 | ```javascript
299 | handleClick() {
300 | this.setState(prevState => ({
301 | words: prevState.words.concat(['marklar'])
302 | }));
303 | }
304 | ```
305 |
306 | ES6对于数组支持[展开语法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) ,使得解决上述问题更加简单。如果你使用的是Create React App,默认支持该语法。
307 |
308 | ```js
309 | handleClick() {
310 | this.setState(prevState => ({
311 | words: [...prevState.words, 'marklar'],
312 | }));
313 | };
314 | ```
315 |
316 | 你可以以一种简单的方式重写上述代码,使得改变对象的同时不会突变对象,例如,如果有一个`colormap`的对象并且编写一个函数将`colormap.right`的值改为`blue`:
317 |
318 | ```js
319 | function updateColorMap(colormap) {
320 | colormap.right = 'blue';
321 | }
322 | ```
323 |
324 | 在不突变原来的对象的条件下实现上面的要求,我们可以使用[Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)方法:
325 |
326 | ```js
327 | function updateColorMap(colormap) {
328 | return Object.assign({}, colormap, {right: 'blue'});
329 | }
330 | ```
331 |
332 | `updateColorMap`方法返回一个新的对象,而不是修改原来的对象。`Object.assign`属于ES6语法,需要polyfill。
333 |
334 | JavaScript提案添加了[对象展开符](https://github.com/sebmarkbage/ecmascript-rest-spread),能够更简单地更新对象而不突变对象。
335 |
336 | ```js
337 | function updateColorMap(colormap) {
338 | return {...colormap, right: 'blue'};
339 | }
340 | ```
341 | 如果你使用的是Create React App,`Object.assign`和对象展开符默认都是可用的。
342 |
343 | ## 使用Immutable 数据结构
344 |
345 | [Immutable.js](https://github.com/facebook/immutable-js) 是解决上述问题的另外一个方法,其提供了通过结构共享实现(Structural Sharing)地不可变的(Immutable)、持久的(Persistent)集合:
346 |
347 | * *不可变(Immutable)*: 一个集合一旦创建,在其他时间是不可更改的。
348 | * *持久的(Persistent)*: 新的集合可以基于之前的结合创建并产生突变,例如:set。原来的集合在新集合创建之后仍然是可用的。
349 | * *结构共享(Structural Sharing)*: 新的集合尽可能通过之前集合相同的结构创建,最小程度地减少复制操作来提高性能。
350 |
351 | 不可变性使得追踪改变非常容易。改变会产生新的对象,因此我们仅需要检查对象的引用是否改变。例如,下面是普通的JavaScript代码:
352 |
353 | ```javascript
354 | const x = { foo: 'bar' };
355 | const y = x;
356 | y.foo = 'baz';
357 | x === y; // true
358 | ```
359 | 虽然`y`被编辑了,但是因为引用的是相同的对象`x`,所以比较返回`true`。
360 |
361 | ```javascript
362 | const SomeRecord = Immutable.Record({ foo: null });
363 | const x = new SomeRecord({ foo: 'bar' });
364 | const y = x.set('foo', 'baz');
365 | x === y; // false
366 | ```
367 |
368 | 在这个例子中,因为当改变x时返回新的引用,我们可以确信地判定`x`被改变了。
369 |
370 | 其他两个可以帮助我们使用不可变数据的库分别是:[seamless-immutable](https://github.com/rtfeldman/seamless-immutable)和[immutability-helper](https://github.com/kolodny/immutability-helper).
371 |
372 | 不可变数据提供了一种更简单的方式来追踪对象的改变,这正是我们实现`shouldComponentUpdate`所需要的。这将会提供可观的性能提升。
373 |
--------------------------------------------------------------------------------
/doc/Integrating with Other Libraries.md:
--------------------------------------------------------------------------------
1 | # React与其他库的集成
2 |
3 | React可以在任何web应用中使用。React可以嵌入其他的应用中,也可以将其他的应用嵌入React中,不过需要多加小心。本篇教程将介绍部分常见的使用场景,主要包括集成jQuery和Backbone,但是同样的思想可以用来集成组件到其他任何现有的代码。
4 |
5 | ## 与DOM操作插件的集成
6 |
7 | React 无法感知到React之外的DOM变化。这决定了更新只能基于React内部的表示,如果相同的DOM节点被其他库所操作,React会对此产生疑惑并无法恢复。
8 |
9 | 这并不意味着很难或者无法将React于其他影响DOM的方式相结合,你需要更加注意两者各自的行为。
10 |
11 | 避免冲突最简单的方式就是阻止React的更新。你可以通过渲染React无法更新的元素来实现,例如空的`
`。
12 |
13 | ### 如何处理这个问题
14 |
15 | 为了展示这个问题,我们来为通用的jQuery插件绘制一个包装器(wrapper)。
16 |
17 | 我们给根DOM元素添加[ref](https://facebook.github.io/react/docs/refs-and-the-dom.html)。在`componentDidMount`中,我们将获得引用(`reference`),因此将其传递给jQuery插件。
18 |
19 | 为了防止React在mount之后处理DOM元素,我们将在`render`方法中返回空的`
`。
元素没有属性或者子元素,因此React不会更新它,使得jQuery插件可以自由地管理这部分的DOM节点。
20 |
21 |
22 | ```js{3,4,8,12}
23 | class SomePlugin extends React.Component {
24 | componentDidMount() {
25 | this.$el = $(this.el);
26 | this.$el.somePlugin();
27 | }
28 |
29 | componentWillUnmount() {
30 | this.$el.somePlugin('destroy');
31 | }
32 |
33 | render() {
34 | return
this.el = el} />;
35 | }
36 | }
37 | ```
38 |
39 | 注意,我们定义了`componentDidMount`和`componentWillUnmount`[生命周期函数](https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle)。很多jQuery插件为DOM元素添加了监听器(listener),因此在`componentWillUnmount`中退订监听器是非常重要的。如果插件本身不提供清除(cleanup)的方法,你可能需要自己提供,牢记一定要移除注册在插件中的事件监听者(event listener)以防止内存泄露。
40 |
41 | ### 与jQuery的Chosen插件集成
42 |
43 | 为了更具体地描述这个概念,让我们写一个最小化的[Chosen](https://harvesthq.github.io/chosen/)插件的包装器(wrapper),其中插件Chosen接受`