├── .gitignore ├── .idea └── vcs.xml ├── doc ├── React Without JSX.md ├── Web Component.md ├── Uncontrolled Components.md ├── Refs and the DOM.md ├── Reconciliation.md ├── Typechecking With PropTypes.md ├── React Without ES6.md ├── Context.md ├── JSX In Depth.md ├── Optimizing Performance.md ├── Integrating with Other Libraries.md └── Higher Order Components.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.idea 2 | *.DS_Store 3 | idea/ 4 | idea 5 | .idea/ 6 | .idea -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /doc/React Without JSX.md: -------------------------------------------------------------------------------- 1 | # React Without JSX 2 | 3 | 对于React来说,并不一定需要使用JSX. 如果不想在构建环境下设置编译器,使用React而不使用JSX非常的方便。 4 | 5 | 每一个JSX元素都是调用`React.createElement(component, props, ...children)`的语法糖,因此,使用JSX所做的任何事都可以通过纯JavaScript实现。 6 | 7 | 例如,下面代码是通过JSX实现的: 8 | 9 | ```js 10 | class Hello extends React.Component { 11 | render() { 12 | return
Hello {this.props.toWhat}
; 13 | } 14 | } 15 | 16 | ReactDOM.render( 17 | , 18 | document.getElementById('root') 19 | ); 20 | ``` 21 | 可以被编译成不使用JSX的代码: 22 | 23 | ```js 24 | class Hello extends React.Component { 25 | render() { 26 | return React.createElement('div', null, `Hello ${this.props.toWhat}`); 27 | } 28 | } 29 | 30 | ReactDOM.render( 31 | React.createElement(Hello, {toWhat: 'World'}, null), 32 | document.getElementById('root') 33 | ); 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.Component`的子类提供,或者通过普通函数实现的无状态组件。 39 | 40 | 如果你厌倦了使用`React.createElement`,另一个常见的模式是将其赋值给一个缩写: 41 | 42 | ```js 43 | const e = React.createElement; 44 | 45 | ReactDOM.render( 46 | e('div', null, 'Hello World'), 47 | document.getElementById('root') 48 | ); 49 | ``` 50 | 如果你使用`React.createElement`的缩写形式,就可以很方便的在不通过JSX情况下,使用React。 -------------------------------------------------------------------------------- /doc/Web Component.md: -------------------------------------------------------------------------------- 1 | # Web Components 2 | 3 | React和[Web Component](https://developer.mozilla.org/en-US/docs/Web/Web_Components)是为了解决不同的问题建立的。Web Component为可重用组件提供了强大的封装,然而React提供声明库,可以使得DOM和数据保持同步。两者的目标是互补的。作为开发者,你可以在你的Web Component中自由使用React,或者在React中使用Web Component,或者都使用。 4 | 5 | 大多数使用React的开发者不使用Web Component,但是你可能想要使用Web Component,尤其是如果你正在使用Web Component编写的第三方的UI库的情况下。 6 | 7 | ## 在React中使用Web Components 8 | 9 | ```javascript 10 | class HelloMessage extends React.Component { 11 | render() { 12 | return
Hello {this.props.name}!
; 13 | } 14 | } 15 | ``` 16 | 17 | > 注意: 18 | > 19 | > Web Components通常都会对外暴露一个必须的API,例如,对于一个`video` Web Component可能会暴露`play()`和`pause()`为了访问Web Component的命令式API,您需要`ref`与DOM节点直接交互。如果你使用的是第三方的Web Component,最好的解决方案是编写React组件,作为Web Component的包装器。 20 | > 21 | > 由Web Component发出的事件可能不会沿着React渲染树正确传播。 22 | > 23 | > 因此在你的React组件中,你需要手动的添加事件处理程序来处理这些事件。 24 | 25 | 一个常见的困惑是Web Component使用`class`而不是`className` 26 | 27 | ```javascript 28 | function BrickFlipbox() { 29 | return ( 30 | 31 |
front
32 |
back
33 |
34 | ); 35 | } 36 | ``` 37 | 38 | ## 在你的Web Component中使用React 39 | 40 | ```javascript 41 | const proto = Object.create(HTMLElement.prototype, { 42 | attachedCallback: { 43 | value: function() { 44 | const mountPoint = document.createElement('span'); 45 | this.createShadowRoot().appendChild(mountPoint); 46 | 47 | const name = this.getAttribute('name'); 48 | const url = 'https://www.google.com/search?q=' + encodeURIComponent(name); 49 | ReactDOM.render({name}, mountPoint); 50 | } 51 | } 52 | }); 53 | document.registerElement('x-search', {prototype: proto}); 54 | ``` 55 | 您还可以查看[Github完整Web组件示例](https://github.com/facebook/react/tree/master/examples/webcomponents) 56 | -------------------------------------------------------------------------------- /doc/Uncontrolled Components.md: -------------------------------------------------------------------------------- 1 | # Uncontrolled Components 2 | 3 | 在大多数情况下,我们推荐使用[受控组件](https://facebook.github.io/react/docs/forms.html)来实现表单。在受控组件中,表单数据由React组件处理。另外一个可选项是不受控组件,其表单数据由DOM元素本身处理。 4 | 5 | 不同于对每次状态处理都需要编写事件处理函数程序,在不受控组件中,你可以使用[ref](https://facebook.github.io/react/docs/refs-and-the-dom.html)从DOM获得表单数据。 6 | 7 | 例如,在不受控组件中,以下代码可以输入名字: 8 | 9 | ```javascript{8,17} 10 | class NameForm extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | this.handleSubmit = this.handleSubmit.bind(this); 14 | } 15 | 16 | handleSubmit(event) { 17 | alert('A name was submitted: ' + this.input.value); 18 | event.preventDefault(); 19 | } 20 | 21 | render() { 22 | return ( 23 |
24 | 28 | 29 |
30 | ); 31 | } 32 | } 33 | ``` 34 | 35 | [可以尝试在CodePen中打开](https://codepen.io/gaearon/pen/WooRWa?editors=0010) 36 | 37 | 因为不受控组件的数据来源是DOM元素,使用不受控组件时很容易实现React代码与非React代码的集成。如果你能希望的是快速开发,但不要求代码质量,不受控组件可以一定程度上减少代码量。否则。你应该使用受控组件。 38 | 39 | 如果你对在特定的场景下你需要使用哪种组件感到疑惑的话,[this article on controlled versus uncontrolled inputs](http://goshakkk.name/controlled-vs-uncontrolled-inputs-react/)这篇文章会对你有所帮助。 40 | 41 | ### 默认值 42 | 43 | 在React渲染的生命周期中,表单元素的value值将会覆盖DOM中的value值。在不受控组件中,你可能希望React有初始值,但随后不控制更新。在这种情况下,你需要使用`defaultValue`属性而不是`value`属性。 44 | 45 | ```javascript{7} 46 | render() { 47 | return ( 48 |
49 | 56 | 57 |
58 | ); 59 | } 60 | ``` 61 | 62 | 同样,``和``支持`defaultChecked`属性,而` { this.textInput = input; }} /> 42 | 47 | 48 | ); 49 | } 50 | } 51 | ``` 52 | 53 | React将会在组件安装(`mount`)时,用DOM元素作为参数回调`ref`函数,在组件卸载(`unmounts`)时,使用`null`作为参数回调函数。 54 | 55 | 对class设置`ref`回调函数是访问DOM元素的一种常见方法。首选的方式就是像上面的代码一样,对`ref`设置回调函数。还有更简洁的方式:`ref={input => this.textInput = input}`。 56 | 57 | ### 为类(Class)组件添加Ref 58 | 59 | 为用类(class)声明的自定义组件设置`ref`属性时,`ref`回调函数收到的参数是安装(`mounted`)的组件实例。例如,如果我们想包装`CustomTextInput`组件,实现组件在`mounted`后立即点击的效果: 60 | 61 | ```javascript{3,9} 62 | class AutoFocusTextInput extends React.Component { 63 | componentDidMount() { 64 | this.textInput.focus(); 65 | } 66 | 67 | render() { 68 | return ( 69 | { this.textInput = input; }} /> 71 | ); 72 | } 73 | } 74 | ``` 75 | 需要注意的是,这种方法仅对以类(class)声明的`CustomTextInput`有效。 76 | 77 | ```js{1} 78 | class CustomTextInput extends React.Component { 79 | // ... 80 | } 81 | ``` 82 | 83 | ### Refs与函数式Components 84 | 85 | **你不能在函数式组件上使用`ref`**因为函数式组件不会创建实例: 86 | 87 | ```javascript{1,7} 88 | function MyFunctionalComponent() { 89 | return ; 90 | } 91 | 92 | class Parent extends React.Component { 93 | render() { 94 | // 无效! 95 | return ( 96 | { this.textInput = input; }} /> 98 | ); 99 | } 100 | } 101 | ``` 102 | 103 | 如果你需要使用`ref`,你需要将组件转化成class组件,就像需要生命周期函数或者state那样。 104 | 105 | 然而你可以在函数式组件内部使用`ref`来引用一个DOM元素或者class组件。 106 | 107 | ```javascript{2,3,6,13} 108 | function CustomTextInput(props) { 109 | // textInput must be declared here so the ref callback can refer to it 110 | let textInput = null; 111 | 112 | function handleClick() { 113 | textInput.focus(); 114 | } 115 | 116 | return ( 117 |
118 | { textInput = input; }} /> 121 | 126 |
127 | ); 128 | } 129 | ``` 130 | 131 | ### 不要滥用Refs 132 | 133 | 在你应用中,你可能会倾向于使用`ref`使得"事件发生"。如果这种情况下,花一点时间仔细考虑一下在组件层次结构中哪些地方需要state。通常情况下,应该在层次结构中较高级别中拥有state。有关这方面的实例参阅[提升state](https://facebook.github.io/react/docs/lifting-state-up.html)。 134 | 135 | ### 旧版API: String类型的Refs 136 | 137 | 如果你之前使用过React,你可能了解过之前的API:string类型的`ref`属性。类似于`textInput`,可以通过`this.refs.textInput`访问DOM节点。我们不建议使用,因为string类型的`ref`存在[问题](https://github.com/facebook/react/pull/8333#issuecomment-271648615)。已经过时了,**可能会在未来的版本是移除**。如果你目前还在使用`this.refs.textInput`这种方式访问`refs`,我们建议用回调函数的方式代替。 138 | 139 | ### 注意 140 | 141 | 如果`ref`以内联函数的方式定义,在update期间会被调用两次,第一次参数是`null`,之后参数是DOM元素。这是因为在每次渲染中都会创建一个新的函数实例。因此,React需要清理旧的ref并且设置新的。通过将`ref`的回调函数定义成类的绑定函数的方式可以避免上述问题,但是在大多数例子中这都不是很重要。 142 | -------------------------------------------------------------------------------- /doc/Reconciliation.md: -------------------------------------------------------------------------------- 1 | # 一致化处理(Reconciliation) 2 | 3 | 原文: [Reconciliation](https://facebook.github.io/react/docs/reconciliation.html) 4 | 5 | 翻译: [MrErHu(请叫我王磊同学)](https://github.com/MrErHu) 6 | 7 | 邮箱: [wanglei_cs@163.com](mailto:wanglei_cs@163.com) 8 | 9 | React提供声明式API,因此在每次更新中你不需要关心具体的更改内容。这使得编写应用更加容易,但是这样使得你对React内部具体实现并不了解,这篇文章介绍了在React的"diffing"算法中我们所作出地决择,以使得组件的更新是可预测的并且可以适用于高性能应用。 10 | 11 | ## 动机 12 | 13 | 当你使用React的时候,在任何时刻,你可以认为`render()`函数的作用是创建React元素树。当state或者props更新的时候,`render()`函数将会返回一个不同的React元素树。接下来React将会找出如何高效地更新UI来匹配先前的React元素树。 14 | 15 | 目前存在大量通用的方法能够以最少的操作步骤将一个树转化成另外一棵树。然而,[state of the art algorithms](http://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf)的时间复杂度为O(n3),其中n为树中的元素个数。 16 | 17 | 如果我们在React中展示1000个元素,那么每次更新都需要1百万次的比较,这样的代价过于昂贵。然而,React基于以下两个假设实现启发式算法,使得时间复杂度为O(n): 18 | 19 | 1. 不同的两个元素会产生不同的树。 20 | 2. 开发者通过`key`属性可以在不同的渲染中表示那些元素是相同的。 21 | 22 | 事实上,这些假设对于大部分实例都是有效的。 23 | 24 | ## Diffing 算法 25 | 26 | 当React比较(diffing)两棵树时,React首先比较两棵树的根元素。根据根元素的不同,行为也有所不同。 27 | 28 | ### 元素类型不相同 29 | 30 | 无论什么时候,当树的根节点类型不同时,React将会销毁原先的树并重头构建新的树。从``到``,从`
`到``,从` 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 | ![](https://facebook.github.io/react/img/docs/devtools-prod.png) 14 | 15 | 如果你访问的网站是React开发模式,图标的背景将会是红色: 16 | 17 | ![](https://facebook.github.io/react/img/docs/devtools-dev.png) 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 |
    React components in Chrome timeline
    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 |
    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接受``DOM节点上调用Chosen,其会读取原始DOM节点的属性,以内联样式方式隐藏,并在内部的虚拟表达中添加单独的DOM节点。随后触发jQuery事件通知事件改变。 52 | 53 | 让我们了解一下我们所设计的React组件包装器(wrapper)``的API: 54 | 55 | ```javascript 56 | function Example() { 57 | return ( 58 | console.log(value)}> 59 | 60 | 61 | 62 | 63 | ); 64 | } 65 | ``` 66 | 67 | 为了简单起见,我们将其实现为一个[不受控组件](https://facebook.github.io/react/docs/uncontrolled-components.html)。 68 | 69 | 首先我们先创建一个空的组件,其中包含`render()`方法,其返回一个由`
    `包裹的` this.el = el}> 77 | {this.props.children} 78 | 79 |
    80 | ); 81 | } 82 | } 83 | ``` 84 | 85 | 需要注意为什么我们为``节点后添加另一个DOM节点。。然而,就React而言,`
    `总是只有一个子节点。这就是我们如何来确保React更新不会与Chosen插件所添加的额外DOM节点相冲突。值得注意的是,如果在React流之外修改了DOM节点,必须确保React无论如何都不会再接触DOM节点。 86 | 87 | 接下来,我们实现生命周周期函数。我们在`componentDidMount`中对引用的` this.el = el}> 106 | ``` 107 | 108 | 上述对于组件的渲染已经足够,但是我们也想要获得值改变的通知(notifies about the value changes),为了实现这个目的,我们在``标签内的子节点`this.props.children`的更新,我们会在生命周期函数`componentDidUpdate`中向Chosen通知子元素的改变。 136 | 137 | ```javascript 138 | componentDidUpdate(prevProps) { 139 | if (prevProps.children !== this.props.children) { 140 | this.$el.trigger("chosen:updated"); 141 | } 142 | } 143 | ``` 144 | 145 | 这样一来,当React导致` this.el = el}> 178 | {this.props.children} 179 | 180 |
    181 | ); 182 | } 183 | } 184 | ``` 185 | 186 | [在CodePen中尝试](http://codepen.io/gaearon/pen/xdgKOz?editors=0010) 187 | 188 | ## 与其他的视图库集成 189 | 190 | 感谢极具灵活性的方法[`ReactDOM.render()`](https://facebook.github.io/react/docs/react-dom.html#render),使得React可以嵌入其他的应用中。 191 | 192 | 虽然React通常在启动时将单个根节点的React组件加载进DOM节点,但`ReactDOM.render()`也可以被多次调用来生成独立的部分UI,小到一个按钮,大到一个应用。 193 | 194 | 事实上,在Facebook中React就是这么用的。这使得我们可以一步一步地使用React编写程序,并与我们现存的服务器生成的模板与其他客户端代码相结合。 195 | 196 | ### 用React替换字符串渲染 197 | 198 | 在之前的web应用中,一种常见的模式是将DOM块作为字符串描述,并将其插入DOM节点,例如: `$el.html(htmlString)`。这种代码是非常适合引入React的,仅仅需要将渲染的字符串重写为React组件。 199 | 200 | 因此下面的jQuery实现: 201 | 202 | ```js 203 | $('#container').html(''); 204 | $('#btn').click(function() { 205 | alert('Hello!'); 206 | }); 207 | ``` 208 | 209 | 可以用React组件重写成: 210 | 211 | ```js 212 | function Button() { 213 | return ; 214 | } 215 | 216 | ReactDOM.render( 217 | ; 232 | } 233 | 234 | function HelloButton() { 235 | function handleClick() { 236 | alert('Hello!'); 237 | } 238 | return