性能分析工具
最后更新于:2022-04-01 20:48:18
通常情况下,React在沙箱中是非常快的。但是,在你应用的一些情景中,你需要仔细推敲每一个性能点。React提供了一个函数[shouldComponentUpdate](http://reactjs.cn/react/docs/component-specs.html#updating-shouldcomponentupdate),通过这个函数,你能够给React的差异检查添加优化代码。
为了给你一个你的应用总体的性能概览,ReactPerf是一个分析工具,告诉你需要把这些钩子函数放在哪里。
> 注意:
>
> 开发的构建过程比生产的构建过程要慢,是因为所有额外逻辑的提供,例如,友好的控制台警告(生产构建时会去掉)。因此,分析工具仅用于指出你应用中相对影响性能的部分。
## 通用API
当使用`react-with-addons.js`在开发模式下构建的时候,这里描述的`Perf`对象是以`React.addons.Perf`的形式暴露出来的。
### `Perf.start()`和`Perf.stop()`
开始/停止检测。React的中间操作被记录下来,用于下面的分析。花费一段微不足道的时间的操作会被忽略。
### `Perf.printInclusive(measurements)`
打印花费的总时间。如果不传递任何参数,默认打印最后的所有检测记录。它会在控制台中打印一个漂亮的格式化的表格,像这样:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-07-20_55acbeeb2d924.png)
### `Perf.printExclusive(measurements)`
“独占(Exclusive)”时间不包含挂载组件的时间:处理props,`getInitialState`,调用`componentWillMount`和`componentDidMount`,等等。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-07-20_55acbeeb4bd59.png)
### `Perf.printWasted(measurements)`
**分析器最有用的部分**。
“浪费”的时间被花在根本没有渲染任何东西的组件上,例如界面渲染后保持不变,没有操作DOM元素。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-07-20_55acbeeb7a81b.png)
### `Perf.printDOM(measurements)`
打印底层DOM操作,例如,“设置innerHTML”和“移除节点”。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-07-20_55acbeebaf479.png)
## 高级API
上面的打印方法使用`Perf.getLastMeasurements()`打印好看的结果。
### `Perf.getLastMeasurements()`
从最后的开始-停止会话中得到检测结果数组。该数组包含对象,每个对象看起来像这样:
~~~
{
// The term "inclusive" and "exclusive" are explained below
"exclusive": {},
// '.0.0' is the React ID of the node
"inclusive": {".0.0": 0.0670000008540228, ".0": 0.3259999939473346},
"render": {".0": 0.036999990697950125, ".0.0": 0.010000003385357559},
// Number of instances
"counts": {".0": 1, ".0.0": 1},
// DOM touches
"writes": {},
// Extra debugging info
"displayNames": {
".0": {"current": "App", "owner": ""},
".0.0": {"current": "Box", "owner": "App"}
},
"totalTime": 0.48499999684281647
}
~~~
';
PureRenderMixin
最后更新于:2022-04-01 20:48:16
如果你的React组件的渲染函数是“纯粹的”(换句话说,当传给它同样的props和state,它渲染出同样的效果),在某些场景下,你可以利用这个插件来极大地提升性能。
例如:
~~~
var PureRenderMixin = require('react').addons.PureRenderMixin;
React.createClass({
mixins: [PureRenderMixin],
render: function() {
return
';
foo
;
}
});
~~~
在底层,该插件实现了[shouldComponentUpdate](http://reactjs.cn/react/docs/component-specs.html#updating-shouldcomponentupdate),在这里面,它比较当前的props、state和接下来的props、state,当两者相等的时候返回`false`。
> 注意:
>
> 仅仅是浅比较对象。如果对象包含了复杂的数据结构,深层次的差异可能会产生误判。仅用于拥有简单props和state的组件,或者当你知道很深的数据结构已经变化了的时候使用`forceUpdate()`。或者,考虑使用[immutable objects](http://facebook.github.io/immutable-js/)来帮助嵌套数据快速比较。
>
> 此外,`shouldComponentUpdate`会跳过更新整个组件子树。确保所有的子组件也是“纯粹的”。
不可变数据的辅助工具(Immutability Helpers)
最后更新于:2022-04-01 20:48:13
React让你可以使用任何你想要的数据管理风格,包括数据可变风格。然而,如果你能够在你应用中讲究性能的部分使用不可变数据,就可以很方便地实现一个快速的`shouldComponentUpdate()`方法来显著提升你应用的速度。
在JavaScript中处理不可变数据比在语言层面上就设计好要难,像[Clojure](http://clojure.org/)。但是,我们提供了一个简单的不可变辅助工具,`update()`,这就让处理这种类型的数据更加简单了,根本_不会_改变你数据的表示的形式。(Dealing with immutable data in JavaScript is more difficult than in languages designed for it, like [Clojure](http://clojure.org/). However, we've provided a simple immutability helper, `update()`, that makes dealing with this type of data much easier, _without_fundamentally changing how your data is represented.)
## 主要思想(The main idea)
如果你像这样改变数据:
~~~
myData.x.y.z = 7;
// or...
myData.a.b.push(9);
~~~
你无法确定哪个数据改变了,因为之前的副本被覆盖了。相反,你需要创建一个新的`myDate`副本,仅仅改变需要改变的部分。然后你就能够在`shouldComponentUpdate()`中使用第三方的相等判断来比较`myData`的旧副本和新对象:
~~~
var newData = deepCopy(myData);
newData.x.y.z = 7;
newData.a.b.push(9);
~~~
不幸的是,深拷贝是很昂贵的,而且某些时候还不可能完成。你可以通过仅拷贝需要改变的对象,重用未改变的对象来缓解这个问题。不幸的是,在当今的JavaScript里面,这会变得很笨拙:
~~~
var newData = extend(myData, {
x: extend(myData.x, {
y: extend(myData.x.y, {z: 7}),
}),
a: extend(myData.a, {b: myData.a.b.concat(9)})
});
~~~
虽然这能够非常好地提升性能(因为仅仅浅复制`log n`个对象,重用余下的),但是写起来很痛苦。看看所有的重复书写!这不仅仅是恼人,也提供了一个巨大的出bug的区域。
`update()`在这种情形下提供了简单的语法糖,使得写这种代码变得更加简单。代码变为:
~~~
var newData = React.addons.update(myData, {
x: {y: {z: {$set: 7}}},
a: {b: {$push: [9]}}
});
~~~
虽然这种语法糖需要花点精力适应(尽管这是受[MongoDB's query language](http://docs.mongodb.org/manual/core/crud-introduction/#query)的启发),但是它没有冗余,是静态可分析的,并且比可变的版本少打了很多字。(While the syntax takes a little getting used to (though it's inspired by [MongoDB's query language](http://docs.mongodb.org/manual/core/crud-introduction/#query)) there's no redundancy, it's statically analyzable and it's not much more typing than the mutative version.)
以`$`为前缀的键被称作_命令_。他们“改变”的数据结构被称为_目标_。( The `$`-prefixed keys are called _commands_. The data structure they are "mutating" is called the _target_.)
## 可用的命令(Available commands)
* `{$push: array}` 利用`push()`把目标上所有的元素放进`数组`(`push()` all the items in `array` on the target.)。
* `{$unshift: array}` 利用`unshift()`把目标上所有的元素放进`数组`(`unshift()` all the items in `array` on the target.)。
* `{$splice: array of arrays}` 对于`array`中的每一个元素,用元素提供的参数在目标上调用`splice()`(for each item in `arrays` call `splice()` on the target with the parameters provided by the item.)。
* `{$set: any}` 整体替换目标(replace the target entirely.)。
* `{$merge: object}` 合并目标和`object`的键。
* `{$apply: function}` 传入当前的值到函数,然后用新返回的值更新它(passes in the current value to the function and updates it with the new returned value.)。
## 示例
### 简单的入栈
~~~
var initialArray = [1, 2, 3];
var newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]
~~~
`initialArray`仍然是`[1, 2, 3]`。
### 嵌入的集合
~~~
var collection = [1, 2, {a: [12, 17, 15]}];
var newCollection = update(collection, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
// => [1, 2, {a: [12, 13, 14, 15]}]
~~~
获取`collection`中索引是`2`的对象,然后取得该对象键为`a`的值,删掉索引从`1`开始的一个元素(即移除`17`),插入`13`和`14`。(This accesses `collection`'s index `2`, key `a`, and does a splice of one item starting from index `1` (to remove `17`) while inserting `13` and`14`.)
### 根据现有的值更新
~~~
var obj = {a: 5, b: 3};
var newObj = update(obj, {b: {$apply: function(x) {return x * 2;}}});
// => {a: 5, b: 6}
// This is equivalent, but gets verbose for deeply nested collections:
var newObj2 = update(obj, {b: {$set: obj.b * 2}});
~~~
### (浅)合并
~~~
var obj = {a: 5, b: 3};
var newObj = update(obj, {$merge: {b: 6, c: 7}}); // => {a: 5, b: 6, c: 7}
~~~
';
克隆组件
最后更新于:2022-04-01 20:48:11
在极少数应用场景中,一个组件可能想改变另一个它不拥有的组件的props(就像改变一个组件的`className`,这个组件又作为`this.props.children`传入)。其它的时候,可能想生成传进来的一个组件的多个拷贝。`cloneWithProps()`使其成为可能。
### ReactComponent React.addons.cloneWithProps(ReactComponent component, object? extraProps)
做一个`component`的浅复制,合并`extraProps`提供的每一个props。`className`和`style`props将会被智能合并。
> 注意:
>
> `cloneWithProps`并不传递`key`到克隆的组件中。如果你希望保留key,将其添加到`extraProps`对象: `js var clonedComponent = cloneWithProps(originalComponent, { key : originalComponent.key }); ``ref`也一样不会保留。
';
测试工具集
最后更新于:2022-04-01 20:48:09
`React.addons.TestUtils`使得在你选择的测试框架中测试React组件变得简单(我们使用[Jest](http://facebook.github.io/jest/))。
### 模拟
~~~
Simulate.{eventName}(DOMElement element, object eventData)
~~~
模拟事件在DOM节点上派发,附带可选的`eventData`事件数据。**这可能是在`ReactTestUtils`中最有用的工具。**
使用示例:
~~~
var node = this.refs.input.getDOMNode();
React.addons.TestUtils.Simulate.click(node);
React.addons.TestUtils.Simulate.change(node, {target: {value: 'Hello, world'}});
React.addons.TestUtils.Simulate.keyDown(node, {key: "Enter"});
~~~
`Simulate`有一个方法适用于每个事件,这些事件都是React能识别的。
### renderIntoDocument
~~~
ReactComponent renderIntoDocument(ReactComponent instance)
~~~
把一个组件渲染成一个在文档中分离的DOM节点。**这个函数需要DOM。**
### mockComponent
~~~
object mockComponent(function componentClass, string? mockTagName)
~~~
传递一个虚拟的组件模块给这个方法,给这个组件扩充一些有用的方法,让组件能够被当成一个React组件的仿制品来使用。这个组件将会变成一个简单的``(或者是其它标签,如果`mockTagName`提供了的话),包含任何提供的子节点,而不是像往常一样渲染出来。
### isElementOfType
~~~
boolean isElementOfType(ReactElement element, function componentClass)
~~~
如果`element`是一个类型为React `componentClass`的React元素,则返回true。
### isDOMComponent
~~~
boolean isDOMComponent(ReactComponent instance)
~~~
如果`instance`是一个DOM组件(例如``或者``),则返回true。
### isCompositeComponent
~~~
boolean isCompositeComponent(ReactComponent instance)`
~~~
如果`instance`是一个合成的组件(通过`React.createClass()`创建),则返回true。
### isCompositeComponentWithType
~~~
boolean isCompositeComponentWithType(ReactComponent instance, function componentClass)
~~~
如果`instance`是一个合成的组件(通过`React.createClass()`创建),此组件的类型是React `componentClass`,则返回true。
### findAllInRenderedTree
~~~
array findAllInRenderedTree(ReactComponent tree, function test)
~~~
遍历`tree`中所有组件,搜集`test(component)`返回true的所有组件。就这个本身来说不是很有用,但是它可以为其它测试提供原始数据。
### scryRenderedDOMComponentsWithClass
~~~
array scryRenderedDOMComponentsWithClass(ReactComponent tree, string className)
~~~
查找组件的所有实例,这些实例都在渲染后的树中,并且是带有`className`类名的DOM组件。
### findRenderedDOMComponentWithClass
~~~
ReactComponent findRenderedDOMComponentWithClass(ReactComponent tree, string className)
~~~
类似于`scryRenderedDOMComponentsWithClass()`,但是它只返回一个结果,如果有其它满足条件的,则会抛出异常。
### scryRenderedDOMComponentsWithTag
~~~
array scryRenderedDOMComponentsWithTag(ReactComponent tree, string tagName)
~~~
在渲染后的树中找出所有组件实例,并且是标签名字符合`tagName`的DOM组件。
### findRenderedDOMComponentWithTag
~~~
ReactComponent findRenderedDOMComponentWithTag(ReactComponent tree, string tagName)
~~~
类似于`scryRenderedDOMComponentsWithTag()`,但是它只返回一个结果,如果有其它满足条件的,则会抛出异常。
### scryRenderedComponentsWithType
~~~
array scryRenderedComponentsWithType(ReactComponent tree, function componentClass)
~~~
找出所有组件实例,这些组件的类型为`componentClass`。
### findRenderedComponentWithType
~~~
ReactComponent findRenderedComponentWithType(ReactComponent tree, function componentClass)
~~~
类似于`scryRenderedComponentsWithType()`,但是它只返回一个结果,如果有其它满足条件的,则会抛出异常。
';
类名操作
最后更新于:2022-04-01 20:48:07
`classSet()`是一个简洁的工具,用于简单操作DOM中的`class`字符串。
这里是一个常见的场景,处理方式中没有使用`classSet()`:
~~~
// inside some ` ` React component
render: function() {
var classString = 'message';
if (this.props.isImportant) {
classString += ' message-important';
}
if (this.props.isRead) {
classString += ' message-read';
}
// 'message message-important message-read'
return
';
Great, I'll be there.
;
}
~~~
这会很快变得单调乏味,因为指定类名的代码很难阅读,并且容易出错。`classSet()`解决了这个问题:
~~~
render: function() {
var cx = React.addons.classSet;
var classes = cx({
'message': true,
'message-important': this.props.isImportant,
'message-read': this.props.isRead
});
// same final string, but much cleaner
return Great, I'll be there.
;
}
~~~
当使用`classSet()`的时候,传递一个对象,对象上的键是你需要或者不需要的CSS类名。对应真值的键将会成为结果字符串的一部分。
`classSet`也允许传递一些类名作为参数,然后拼接这些类名:
~~~
render: function() {
var cx = React.addons.classSet;
var importantModifier = 'message-important';
var readModifier = 'message-read';
var classes = cx('message', importantModifier, readModifier);
// Final string is 'message message-important message-read'
return Great, I'll be there.
;
}
~~~
没有更多需要钻研的字符串拼接!
双向绑定辅助工具
最后更新于:2022-04-01 20:48:04
`ReactLink`是一种简单表达React双向绑定的方式。
> 注意:
>
> 如果你是这个框架的初学者,记住`ReactLink`对于大多数应用来说都是不需要的,应该谨慎使用。
在React里面,数据流是一个方向的:从拥有者到子节点。这是因为根据[the Von Neumann model of computing](http://en.wikipedia.org/wiki/Von_Neumann_architecture),数据仅向一个方向传递。你可以认为它是`单向数据绑定`。
然而,有很多应用需要你读取一些数据,然后传回给你的程序。例如,在开发表单的时候,当你接收到用户输入时,你将会频繁地想更新某些React `state`。或者你想在JavaScript中演算布局,然后反应到某些DOM元素的尺寸上。
在React中,你可以通过监听一个“change”事件来实现这个功能,从你的数据源(通常是DOM)读取,然后在你某个组件上调用`setState()`。"关闭数据流循环"明显会引导写出更加容易理解的和维护的程序。查看[我们的表单文档](http://reactjs.cn/react/docs/forms.html)来获取更多信息。
双向绑定 -- 隐式地强制在DOM里面的数据总是和某些React `state`保持一致 -- 是简明的,并且支持非常多的应用。我们已经提供了`ReactLink`:如上所述,是一种设置通用数据流循环模型的语法糖,或者说“关联”某些数据到React `state`。
> 注意:
>
> ReactLink仅仅是一个`onChange`/`setState()`模式的简单包装和约定。它不会从根本上改变数据在你的React应用中如何流动。
## ReactLink: 前后对比
这是一个简单的表单示例,没有使用`ReactLink`:
~~~
var NoLink = React.createClass({
getInitialState: function() {
return {message: 'Hello!'};
},
handleChange: function(event) {
this.setState({message: event.target.value});
},
render: function() {
var message = this.state.message;
return ;
}
});
~~~
这段代码运行地很好,数据如何流动是非常清晰的,但是,如果表单有大量的字段,代码就会很冗长了。让我们使用`ReactLink`来减少打字输入:
~~~
var WithLink = React.createClass({
mixins: [React.addons.LinkedStateMixin],
getInitialState: function() {
return {message: 'Hello!'};
},
render: function() {
return ;
}
});
~~~
`LinkedStateMixin`给你的React组件添加一个叫做`linkState()`的方法。`linkState()`返回一个`ReactLink`对象,包含React state当前的值和一个用来改变它的回调函数。
`ReactLink`对象可以在树中作为props被向上传递或者向下传递,so it's easy (and explicit) to set up two-way binding between a component deep in the hierarchy and state that lives higher in the hierarchy.
注意,对于checkbox的`value`属性,有一个特殊的行为,如果checkbox被选中(默认是`on`),`value`属性值将会在表单提交的时候发送出去。当checkbox被选中或者取消选中的时候,`value`属性是不会更新的。对于checkbox,你应该使用`checkLink`而不是`valueLink`:
~~~
~~~
## 底层原理(Under the Hood)
对于`ReactLink`,有两块儿:你创建`ReactLink`实例的地方和你使用它的地方。为了证明`ReactLink`是多么的简单,让我们单独地重写每一块儿,以便显得更加明了。
### 不带ReactLink的LinkedStateMixin(ReactLink Without LinkedStateMixin)
~~~
var WithoutMixin = React.createClass({
getInitialState: function() {
return {message: 'Hello!'};
},
handleChange: function(newValue) {
this.setState({message: newValue});
},
render: function() {
var valueLink = {
value: this.state.message,
requestChange: this.handleChange
};
return ;
}
});
~~~
如你所见,`ReactLink`对象是非常简单的,仅仅有一个`value`和`requestChange`属性。`LinkedStateMixin`也同样简单:它仅占据这些字段,用来自于`this.state`的值和一个调用`this.setState()`的回调函数。(And `LinkedStateMixin` is similarly simple: it just populates those fields with a value from `this.state` and a callback that calls`this.setState()`.)
### 不带valueLink的ReactLink(ReactLink Without valueLink)
~~~
var WithoutLink = React.createClass({
mixins: [React.addons.LinkedStateMixin],
getInitialState: function() {
return {message: 'Hello!'};
},
render: function() {
var valueLink = this.linkState('message');
var handleChange = function(e) {
valueLink.requestChange(e.target.value);
};
return ;
}
});
~~~
`valueLink`属性也很简单。它简单地处理`onChange`事件,然后调用`this.props.valueLink.requestChange()`,同时也用`this.props.valueLink.value`替换`this.props.value`。就这么简单!
';
动画
最后更新于:2022-04-01 20:48:02
React为动画提供一个`ReactTransitonGroup`插件组件作为一个底层的API,一个`ReactCSSTransitionGroup`来简单地实现基本的CSS动画和过渡。
## 高级API:`ReactCSSTransitionGroup`
`ReactCSSTransitionGroup`是基于`ReactTransitionGroup`的,在React组件进入或者离开DOM的时候,它是一种简单地执行CSS过渡和动画的方式。这个的灵感来自于优秀的[ng-animate](http://www.nganimate.org/)库。
### 快速开始
`ReactCSSTransitionGroup`是`ReactTransitions`的接口。这是一个简单的元素,包含了所有你对其动画感兴趣的组件。这里是一个例子,例子中我们让列表项淡入淡出。
~~~
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
var TodoList = React.createClass({
getInitialState: function() {
return {items: ['hello', 'world', 'click', 'me']};
},
handleAdd: function() {
var newItems =
this.state.items.concat([prompt('Enter some text')]);
this.setState({items: newItems});
},
handleRemove: function(i) {
var newItems = this.state.items;
newItems.splice(i, 1);
this.setState({items: newItems});
},
render: function() {
var items = this.state.items.map(function(item, i) {
return (
{items}
);
}
});
~~~
> 注意:
>
> 你必须为`ReactCSSTransitionGroup`的所有子级提供[`键`属性](http://reactjs.cn/react/docs/multiple-components.html#dynamic-children),即使只渲染一项。这就是React确定哪一个子级插入了,移除了,或者停留在那里。
在这个组件当中,当一个新的项被添加到`ReactCSSTransitionGroup`,它将会被添加`example-enter`类,然后在下一时刻被添加`example-enter-active` CSS类。这是一个基于`transitionName` prop的约定。
你可以使用这些类来触发一个CSS动画或者过渡。例如,尝试添加这段CSS代码,然后插入一个新的列表项:
~~~
.example-enter {
opacity: 0.01;
transition: opacity .5s ease-in;
}
.example-enter.example-enter-active {
opacity: 1;
}
~~~
你将注意到,当你尝试移除一项的时候,`ReactCSSTransitionGroup`保持该项在DOM里。如果你正使用一个带有插件的未压缩的React构建版本,你将会看到一条警告:React期待一次动画或者过渡发生。那是因为`ReactCSSTransitionGroup`保持你的DOM元素一直在页面上,直到动画完成。尝试添加这段CSS代码:
~~~
.example-leave {
opacity: 1;
transition: opacity .5s ease-in;
}
.example-leave.example-leave-active {
opacity: 0.01;
}
~~~
### 一组动画必须要挂载了才能生效
为了能够给它的子级应用过渡效果,`ReactCSSTransitionGroup`必须已经挂载到了DOM。下面的例子不会生效,因为`ReactCSSTransitionGroup`被挂载到新项,而不是新项被挂载到`ReactCSSTransitionGroup`里。将这个与上面的[快速开始](http://reactjs.cn/react/docs/animation.html#getting-started)部分比较一下,看看有什么差异。
~~~
render: function() {
var items = this.state.items.map(function(item, i) {
return (
{item}
);
}, this);
return (
);
}
});
~~~
### 禁用动画
如果你想,你可以禁用`入场`或者`出场`动画。例如,有些时候,你可能想要一个`入场`动画,不要`出场`动画,但是`ReactCSSTransitionGroup`会在移除DOM节点之前等待一个动画完成。你可以给`ReactCSSTransitionGroup`添加`transitionEnter={false}`或者`transitionLeave={false}` props来禁用这些动画。
> 注意:
>
> 当使用`ReactCSSTransitionGroup`的时候,没有办法通知你在过渡效果结束或者在执行动画的时候做一些复杂的运算。如果你想要更多细粒度的控制,你可以使用底层的`ReactTransitionGroup` API,该API提供了你自定义过渡效果所需要的函数。
## 底层的API:`ReactTransitionGroup`
`ReactTransitionGroup`是动画的基础。它可以通过`React.addons.TransitionGroup`得到。当子级被添加或者从其中移除(就像上面的例子)的时候,特殊的生命周期函数就会在它们上面被调用。
### `componentWillEnter(callback)`
在组件被添加到已有的`TransitionGroup`中的时候,该函数和`componentDidMount()`被同时调用。这将会阻塞其它动画触发,直到`callback`被调用。该函数不会在`TransitionGroup`初始化渲染的时候调用。
### `componentDidEnter()`
该函数在传给`componentWillEnter`的`callback`函数被调用之后调用。
### `componentWillLeave(callback)`
该函数在子级从`ReactTransitionGroup`中移除的时候调用。虽然子级被移除了,`ReactTransitionGroup`将会使它继续在DOM中,直到`callback`被调用。
### `componentDidLeave()`
该函数在`willLeave` `callback`被调用的时候调用(与`componentWillUnmount`是同一时间)。
### 渲染一个不同的组件
默认情况下`ReactTransitionGroup`渲染一个`span`。你可以通过提供一个`component` prop来改变这种行为。例如,以下是你将如何渲染一个``:
~~~
...
~~~
每一个React能渲染的DOM组件都是可用的。但是,`组件`并不需要是一个DOM组件。它可以是任何你想要的React组件;甚至是你自己已经写好的!
> 注意:
>
> v0.12之前,当使用DOM组件的时候,`组件` prop需要是一个指向`React.DOM.*`的引用。既然组件简单地传递到了`React.createElement`,它必须是一个字符串。组装的组件必须传递构造函数。
任何额外的、用户定义的属性将会成为已渲染的组件的属性。例如,以下是你将如何渲染一个带有css类的``:
~~~
...
~~~
';
{item}
);
}.bind(this));
return (
{items}
);
}
~~~
### 让一项或者零项动起来(Animating One or Zero Items)
虽然在上面的例子中,我们渲染了一个项目列表到`ReactCSSTransitionGroup`里,`ReactCSSTransitionGroup`的子级可以是一个或零个项目。这使它能够让一个元素实现进入和离开的动画。同样,你可以通过移动一个新的元素来替换当前元素。随着新元素的移入,当前元素移出。例如,我们可以由此实现一个简单的图片轮播器:
~~~
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
var ImageCarousel = React.createClass({
propTypes: {
imageSrc: React.PropTypes.string.isRequired
},
render: function() {
return (
插件
最后更新于:2022-04-01 20:48:00
`React.addons` 是为了构建 React 应用而放置的一些有用工具的地方。**此功能应当被视为实验性的**,但最终将会被添加进核心代码中或者有用的工具库中:
* [`TransitionGroup`和`CSSTransitionGroup`](http://reactjs.cn/react/docs/animation.html),用于处理动画和过渡,这些通常实现起来都不简单,例如在一个组件移除之前执行一段动画。
* [`LinkedStateMixin`](http://reactjs.cn/react/docs/two-way-binding-helpers.html),用于简化用户表单输入数据和组件 state 之间的双向数据绑定。
* [`classSet`](http://reactjs.cn/react/docs/class-name-manipulation.html),用于更加干净简洁地操作 DOM 中的 `class` 字符串。
* [`cloneWithProps`](http://reactjs.cn/react/docs/clone-with-props.html),用于实现 React 组件浅复制,同时改变它们的 props 。
* [`update`](http://reactjs.cn/react/docs/update.html),一个辅助方法,使得在 JavaScript 中处理不可变数据更加容易。
* [`PureRednerMixin`](http://reactjs.cn/react/docs/pure-render-mixin.html),在某些场景下的性能检测器。
以下插件只存在于 React 开发版(未压缩):
* [`TestUtils`](http://reactjs.cn/react/docs/test-utils.html), 简单的辅助工具,用于编写测试用例(仅存在于未压缩版).
* [`Perf`](http://reactjs.cn/react/docs/perf.html),用于性能测评,并帮助你检查出可优化的功能点。
要使用这些插件,需要用 `react-with-addons.js` (和它的最小化副本)替换常规的`React.js`。
当通过npm使用react包的时候,只要简单地用 `require('react/addons')` 替换`require('react')` 来得到带有所有插件的React。
';
工具集成(Tooling Integration)
最后更新于:2022-04-01 20:47:58
每个项目使用不同的系统来构建和部署JavaScript。我们尝试尽量让React环境无关。
## React
### CDN托管的React
我们在我们的[下载页面](http://reactjs.cn/react/downloads.html)提供了React的CDN托管版本。这些预构建的文件使用UMD模块格式。直接简单地把它们放在``标签中将会给你环境的全局作用域引入一个`React`对象。React也可以在CommonJS和AMD环境下正常工作。
### 使用主分支
我们在[GitHub仓库](https://github.com/facebook/react)的主分支上有一些构建指令。我们在`build/modules`下构建了符合CommonJS模块规范的树形目录,你可以放置在任何环境或者使用任何打包工具,只要支持CommonJS规范。
## JSX
### 浏览器中的JSX转换
如果你喜欢使用JSX,我们[在我们的下载页面](http://reactjs.cn/react/downloads.html)提供了一个用于开发的浏览器中的JSX转换器。简单地用一个``标签来触发JSX转换器。
> 注意:
>
> 浏览器中的JSX转换器是相当大的,并且会在客户端导致无谓的计算,这些计算是可以避免的。不要在生产环境使用 - 参考下一节。
### 生产环境化:预编译JSX
如果你有[npm](http://npmjs.org/),你可以简单地运行`npm install -g react-tools`来安装我们的命令行`jsx`工具。这个工具会把使用JSX语法的文件转换成纯的可以直接在浏览器里面运行起来的JavaScript文件。它也会为你监视目录,然后自动转换变化的文件;例如:`jsx --watch src/ build/`。运行`jsx --help`来查看更多关于如何使用这个工具的信息。
### 有用的开源项目
开源社区开发了在几款编辑器中集成JSX的插件和构建系统。点击[JSX集成](https://github.com/facebook/react/wiki/Complementary-Tools#jsx-integrations)查看所有内容。
';
关于Refs的更多内容
最后更新于:2022-04-01 20:47:55
浏览器中的工作原理
最后更新于:2022-04-01 20:47:53
React提供了强大的抽象,让你在大多数应用场景中不再直接操作DOM,但是有时你需要简单地调用底层的API,或者借助于第三方库或已有的代码。
## 虚拟DOM
React是很快的,因为它从不直接操作DOM。React在内存中维护一个快速响应的DOM描述。`render()`方法返回一个DOM的_描述_,React能够利用内存中的描述来快速地计算出差异,然后更新浏览器中的DOM。
另外,React实现了一个完备的虚拟事件系统,尽管各个浏览器都有自己的怪异行为,React确保所有事件对象都符合W3C规范,并且持续冒泡,用一种高性能的方式跨浏览器(and everything bubbles consistently and in a performant way cross-browser)。你甚至可以在IE8中使用一些HTML5的事件!
大多数时候你应该呆在React的“虚拟浏览器”世界里面,因为它性能更加好,并且容易思考。但是,有时你简单地需要调用底层的API,或许借助于第三方的类似于jQuery插件这种库。React为你提供了直接使用底层DOM API的途径。
## Refs和getDOMNode()
为了和浏览器交互,你将需要对DOM节点的引用。每一个挂载的React组件有一个`getDOMNode()`方法,你可以调用这个方法来获取对该节点的引用。
> 注意:
>
> `getDOMNode()`仅在挂载的组件上有效(也就是说,组件已经被放进了DOM中)。如果你尝试在一个未被挂载的组件上调用这个函数(例如在创建组件的`render()`函数中调用`getDOMNode()`),将会抛出异常。
为了获取一个到React组件的引用,你可以使用`this`来得到当前的React组件,或者你可以使用refs来指向一个你拥有的组件。它们像这样工作:
~~~
var MyComponent = React.createClass({
handleClick: function() {
// Explicitly focus the text input using the raw DOM API.
this.refs.myTextInput.getDOMNode().focus();
},
render: function() {
// The ref attribute adds a reference to the component to
// this.refs when the component is mounted.
return (
);
}
});
React.render(
,
document.getElementById('example')
);
~~~
## 更多关于Refs
为了学习更多有关Refs的内容,包括如何有效地使用它们,参考我们的[更多关于Refs](http://reactjs.cn/react/docs/more-about-refs.html)文档。
## 组件生命周期
组件的生命周期包含三个主要部分:
* **挂载:** 组件被插入到DOM中。
* **更新:** 组件被重新渲染,查明DOM是否应该刷新。
* **移除:** 组件从DOM中移除。
React提供生命周期方法,你可以在这些方法中放入自己的代码。我们提供**will**方法,会在某些行为发生之前调用,和**did**方法,会在某些行为发生之后调用。
### 挂载
* `getInitialState(): object`在组件被挂载之前调用。状态化的组件应该实现这个方法,返回初始的state数据。
* `componentWillMount()`在挂载发生之前立即被调用。
* `componentDidMount()`在挂载结束之后马上被调用。需要DOM节点的初始化操作应该放在这里。
### 更新
* `componentWillReceiveProps(object nextProps)`当一个挂载的组件接收到新的props的时候被调用。该方法应该用于比较`this.props`和`nextProps`,然后使用`this.setState()`来改变state。
* `shouldComponentUpdate(object nextProps, object nextState): boolean`当组件做出是否要更新DOM的决定的时候被调用。实现该函数,优化`this.props`和`nextProps`,以及`this.state`和`nextState`的比较,如果不需要React更新DOM,则返回false。
* `componentWillUpdate(object nextProps, object nextState)`在更新发生之前被调用。你可以在这里调用`this.setState()`。
* `componentDidUpdate(object prevProps, object prevState)`在更新发生之后调用。
### 移除
* `componentWillUnmount()`在组件移除和销毁之前被调用。清理工作应该放在这里。
### 挂载的方法(Mounted Methods)
_挂载的_复合组件也支持如下方法:
* `getDOMNode(): DOMElement`可以在任何挂载的组件上面调用,用于获取一个指向它的渲染DOM节点的引用。
* `forceUpdate()`当你知道一些很深的组件state已经改变了的时候,可以在该组件上面调用,而不是使用`this.setState()`。
## 跨浏览器支持和兼容代码(Browser Support and Polyfills)
在Facebook,我们支持低版本的浏览器,包括IE8。我们已经写好兼容代码很长时间了,这能让我们写有远见的JS。这意味着我们没有零散的骇客代码充斥在我们的代码库里面,并且我们依然能够预计我们的代码“正常工作起来”。例如,不使用`+new Date()`,我们能够写`Date.now()`。 At Facebook, we support older browsers, including IE8\. We've had polyfills in place for a long time to allow us to write forward-thinking JS. This means we don't have a bunch of hacks scattered throughout our codebase and we can still expect our code to "just work". For example, instead of seeing `+new Date()`, we can just write `Date.now()`. Since the open source React is the same as what we use internally, we've carried over this philosophy of using forward thinking JS.
In addition to that philosophy, we've also taken the stance that we, as authors of a JS library, should not be shipping polyfills as a part of our library. If every library did this, there's a good chance you'd be sending down the same polyfill multiple times, which could be a sizable chunk of dead code. If your product needs to support older browsers, chances are you're already using something like [es5-shim](https://github.com/kriskowal/es5-shim).
### 支持低版本浏览器的兼容代码
[kriskowal的es5-shim](https://github.com/kriskowal/es5-shim) `es5-shim.js` 提供了以下react需要的api:
* `Array.isArray`
* `Array.prototype.every`
* `Array.prototype.forEach`
* `Array.prototype.indexOf`
* `Array.prototype.map`
* `Date.now`
* `Function.prototype.bind`
* `Object.keys`
* `String.prototype.split`
* `String.prototype.trim`
[kriskowal的es5-shim](https://github.com/kriskowal/es5-shim) `es5-sham.js` 同样提供了以下react需要的api:
* `Object.create`
* `Object.freeze`
The unminified build of React needs the following from [paulmillr's console-polyfill](https://github.com/paulmillr/console-polyfill).
* `console.*`
When using HTML5 elements in IE8 including ``, ``, ``, ``, and ``, it's also necessary to include [html5shiv](https://github.com/aFarkas/html5shiv) or a similar script.
### Cross-browser Issues
Although React is pretty good at abstracting browser differences, some browsers are limited or present quirky behaviors that we couldn't find a workaround for.
#### onScroll event on IE8
On IE8 the `onScroll` event doesn't bubble and IE8 doesn't have an API to define handlers to the capturing phase of an event, meaning there is no way for React to listen to these events. Currently a handler to this event is ignored on IE8.
See the [onScroll doesn't work in IE8](https://github.com/facebook/react/issues/631) GitHub issue for more information. ve carried over this philosophy of using forward thinking JS.
In addition to that philosophy, we've also taken the stance that we, as authors of a JS library, should not be shipping polyfills as a part of our library. If every library did this, there's a good chance you'd be sending down the same polyfill multiple times, which could be a sizable chunk of dead code. If your product needs to support older browsers, chances are you're already using something like [es5-shim](https://github.com/kriskowal/es5-shim).
### Polyfills Needed to Support Older Browsers
`es5-shim.js` from [kriskowal's es5-shim](https://github.com/kriskowal/es5-shim) provides the following that React needs:
* `Array.isArray`
* `Array.prototype.every`
* `Array.prototype.forEach`
* `Array.prototype.indexOf`
* `Array.prototype.map`
* `Date.now`
* `Function.prototype.bind`
* `Object.keys`
* `String.prototype.split`
* `String.prototype.trim`
`es5-sham.js`, also from [kriskowal's es5-shim](https://github.com/kriskowal/es5-shim), provides the following that React needs:
* `Object.create`
* `Object.freeze`
The unminified build of React needs the following from [paulmillr's console-polyfill](https://github.com/paulmillr/console-polyfill).
* `console.*`
When using HTML5 elements in IE8 including ``, ``, ``, ``, and ``, it's also necessary to include [html5shiv](https://github.com/aFarkas/html5shiv) or a similar script.
### Cross-browser Issues
Although React is pretty good at abstracting browser differences, some browsers are limited or present quirky behaviors that we couldn't find a workaround for.
#### onScroll event on IE8
On IE8 the `onScroll` event doesn't bubble and IE8 doesn't have an API to define handlers to the capturing phase of an event, meaning there is no way for React to listen to these events. Currently a handler to this event is ignored on IE8.
See the [onScroll doesn't work in IE8](https://github.com/facebook/react/issues/631) GitHub issue for more information.
';
表单组件
最后更新于:2022-04-01 20:47:51
诸如 ``、`
';
传递 Props
最后更新于:2022-04-01 20:47:48
React 里有一个非常常用的模式就是对组件做一层抽象。组件对外公开一个简单的属性(Props)来实现功能,但内部细节可能有非常复杂的实现。
可以使用 [JSX 展开属性](http://reactjs.cn/react/docs/jsx-spread-zh-CN.html) 来合并现有的 props 和其它值:
~~~
return ;
~~~
如果不使用 JSX,可以使用一些对象辅助方法如 ES6 的 `Object.assign` 或 Underscore`_.extend`。
~~~
return Component(Object.assign({}, this.props, { more: 'values' }));
~~~
下面的教程介绍一些最佳实践。使用了 JSX 和 ES7 的还在试验阶段的特性。
## 手动传递
大部分情况下你应该显式地向下传递 props。这样可以确保只公开你认为是安全的内部 API 的子集。
~~~
var FancyCheckbox = React.createClass({
render: function() {
var fancyClass = this.props.checked ? 'FancyChecked' : 'FancyUnchecked';
return (
Hello world!
,
document.body
);
~~~
但 `name` 这个属性怎么办?还有 `title`、`onMouseOver` 这些 props?
## 在 JSX 里使用 `...` 传递
有时把所有属性都传下去是不安全或啰嗦的。这时可以使用 [解构赋值](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) 中的剩余属性特性来把未知属性批量提取出来。
列出所有要当前使用的属性,后面跟着 `...other`。
~~~
var { checked, ...other } = this.props;
~~~
这样能确保把所有 props 传下去,_除了_ 那些已经被使用了的。
~~~
var FancyCheckbox = React.createClass({
render: function() {
var { checked, ...other } = this.props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
// `other` 包含 { onClick: console.log } 但 checked 属性除外
return (
);
}
});
React.render(
Hello world!
,
document.body
);
~~~
> 注意:
>
> 上面例子中,`checked` 属性也是一个有效的 DOM 属性。如果你没有使用解构赋值,那么可能无意中把它传下去。
在传递这些未知的 `other` 属性时,要经常使用解构赋值模式。
~~~
var FancyCheckbox = React.createClass({
render: function() {
var fancyClass = this.props.checked ? 'FancyChecked' : 'FancyUnchecked';
// 反模式:`checked` 会被传到里面的组件里
return (
);
}
});
~~~
## 使用和传递同一个 Prop
如果组件需要使用一个属性又要往下传递,可以直接使用 `checked={checked}` 再传一次。这样做比传整个 `this.props` 对象要好,因为更利于重构和语法检查。
~~~
var FancyCheckbox = React.createClass({
render: function() {
var { checked, title, ...other } = this.props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
var fancyTitle = checked ? 'X ' + title : 'O ' + title;
return (
);
}
});
~~~
> 注意:
>
> 顺序很重要,把 `{...other}` 放到 JSX props 前面会使它不被覆盖。上面例子中我们可以保证 input 的 type 是 `"checkbox"`。
## 剩余属性和展开属性 `...`
剩余属性可以把对象剩下的属性提取到一个新的对象。会把所有在解构赋值中列出的属性剔除。
这是 [ES7 草案](https://github.com/sebmarkbage/ecmascript-rest-spread) 中的试验特性。
~~~
var { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }
~~~
> 注意:
>
> 使用 [JSX 命令行工具](http://npmjs.org/package/react-tools) 配合 `--harmony` 标记来启用 ES7 语法。
## 使用 Underscore 来传递
如果不使用 JSX,可以使用一些库来实现相同效果。Underscore 提供 `_.omit` 来过滤属性,`_.extend` 复制属性到新的对象。
~~~
javascript var FancyCheckbox = React.createClass({ render: function() { var checked = this.props.checked; var other = _.omit(this.props, 'checked'); var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked'; return ( React.DOM.div(_.extend({}, other, { className: fancyClass })) ); } });
~~~
';
{this.props.children}
);
}
});
React.render(
可复用组件
最后更新于:2022-04-01 20:47:46
设计接口的时候,把通用的设计元素(按钮,表单框,布局组件等)拆成接口良好定义的可复用的组件。这样,下次开发相同界面程序时就可以写更少的代码,也意义着更高的开发效率,更少的 Bug 和更少的程序体积。
## Prop 验证
随着应用不断变大,保证组件被正确使用变得非常有用。为此我们引入`propTypes`。`React.PropTypes` 提供很多验证器 (validator) 来验证传入数据的有效性。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。注意为了性能考虑,只在开发环境验证 `propTypes`。下面用例子来说明不同验证器的区别:
~~~
React.createClass({
propTypes: {
// 可以声明 prop 为指定的 JS 基本类型。默认
// 情况下,这些 prop 都是可传可不传的。
optionalArray: React.PropTypes.array,
optionalBool: React.PropTypes.bool,
optionalFunc: React.PropTypes.func,
optionalNumber: React.PropTypes.number,
optionalObject: React.PropTypes.object,
optionalString: React.PropTypes.string,
// 所有可以被渲染的对象:数字,
// 字符串,DOM 元素或包含这些类型的数组。
optionalNode: React.PropTypes.node,
// React 元素
optionalElement: React.PropTypes.element,
// 用 JS 的 instanceof 操作符声明 prop 为类的实例。
optionalMessage: React.PropTypes.instanceOf(Message),
// 用 enum 来限制 prop 只接受指定的值。
optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
// 指定的多个对象类型中的一个
optionalUnion: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.instanceOf(Message)
]),
// 指定类型组成的数组
optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
// 指定类型的属性构成的对象
optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
// 特定形状参数的对象
optionalObjectWithShape: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number
}),
// 以后任意类型加上 `isRequired` 来使 prop 不可空。
requiredFunc: React.PropTypes.func.isRequired,
// 不可空的任意类型
requiredAny: React.PropTypes.any.isRequired,
// 自定义验证器。如果验证失败需要返回一个 Error 对象。不要直接
// 使用 `console.warn` 或抛异常,因为这样 `oneOfType` 会失效。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error('Validation failed!');
}
}
},
/* ... */
});
~~~
## 默认 Prop 值
React 支持以声明式的方式来定义 `props` 的默认值。
~~~
var ComponentWithDefaultProps = React.createClass({
getDefaultProps: function() {
return {
value: 'default value'
};
}
/* ... */
});
~~~
当父级没有传入 props 时,`getDefaultProps()` 可以保证 `this.props.value` 有默认值,注意 `getDefaultProps` 的结果会被 _缓存_。得益于此,你可以直接使用 props,而不必写手动编写一些重复或无意义的代码。
## 传递 Props:小技巧
有一些常用的 React 组件只是对 HTML 做简单扩展。通常,你想少写点代码来把传入组件的 props 复制到对应的 HTML 元素上。这时 JSX 的 _spread_ 语法会帮到你:
~~~
var CheckLink = React.createClass({
render: function() {
// 这样会把 CheckList 所有的 props 复制到
return {'√ '}{this.props.children};
}
});
React.render(
Click here!
,
document.getElementById('example')
);
~~~
## 单个子级
`React.PropTypes.element` 可以限定只能有一个子级传入。
~~~
var MyComponent = React.createClass({
propTypes: {
children: React.PropTypes.element.isRequired
},
render: function() {
return (
,
document.getElementById('example')
);
~~~
关于 mixin 值得一提的优点是,如果一个组件使用了多个 mixin,并且有多个 mixin 定义了同样的生命周期方法(如:多个 mixin 都需要在组件销毁时做资源清理操作),所有这些生命周期方法都保证会被执行到。方法执行顺序是:首先按 mixin 引入顺序执行 mixin 里方法,最后执行组件内定义的方法。
';
{this.props.children} // 有且仅有一个元素,否则会抛异常。
);
}
});
~~~
## Mixins
组件是 React 里复用代码最佳方式,但是有时一些复杂的组件间也需要共用一些功能。有时会被称为 [跨切面关注点](http://en.wikipedia.org/wiki/Cross-cutting_concern)。React 使用 `mixins` 来解决这类问题。
一个通用的场景是:一个组件需要定期更新。用 `setInterval()` 做很容易,但当不需要它的时候取消定时器来节省内存是非常重要的。React 提供 [生命周期方法](http://reactjs.cn/react/docs/working-with-the-browser.html#component-lifecycle) 来告知组件创建或销毁的时间。下面来做一个简单的 mixin,使用 `setInterval()` 并保证在组件销毁时清理定时器。
~~~
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.map(clearInterval);
}
};
var TickTock = React.createClass({
mixins: [SetIntervalMixin], // 引用 mixin
getInitialState: function() {
return {seconds: 0};
},
componentDidMount: function() {
this.setInterval(this.tick, 1000); // 调用 mixin 的方法
},
tick: function() {
this.setState({seconds: this.state.seconds + 1});
},
render: function() {
return (
React has been running for {this.state.seconds} seconds.
); } }); React.render(复合组件
最后更新于:2022-04-01 20:47:44
目前为止,我们已经学了如何用单个组件来展示数据和处理用户输入。下一步让我们来体验 React 最激动人心的特性之一:可组合性(composability)。
[TOC]
## 动机:关注分离
通过复用那些接口定义良好的组件来开发新的模块化组件,我们得到了与使用函数和类相似的好处。具体来说就是能够通过开发简单的组件把程序的_不同关注面分离_。如果为程序开发一套自定义的组件库,那么就能以最适合业务场景的方式来展示你的用户界面。
## 组合实例
一起来使用 Facebook Graph API 开发显示个人图片和用户名的简单 Avatar 组件吧。
~~~
var Avatar = React.createClass({
render: function() {
return (
);
}
});
var ProfilePic = React.createClass({
render: function() {
return (
);
}
});
var ProfileLink = React.createClass({
render: function() {
return (
{this.props.username}
);
}
});
React.render(
,
document.getElementById('example')
);
~~~
## 从属关系
上面例子中,`Avatar` 拥有 `ProfilePic` 和 `ProfileLink` 的实例。`拥有者` 就是给其它组件设置 `props` 的那个组件。更正式地说, 如果组件 `Y` 在 `render()` 方法是创建了组件 `X`,那么 `Y` 就拥有 `X`。上面讲过,组件不能修改自身的 `props` - 它们总是与它们拥有者设置的保持一致。这是保持用户界面一致性的关键性原则。
把从属关系与父子关系加以区别至关重要。从属关系是 React 特有的,而父子关系简单来讲就是DOM 里的标签的关系。在上一个例子中,`Avatar` 拥有 `div`、`ProfilePic` 和`ProfileLink` 实例,`div` 是 `ProfilePic` 和 `ProfileLink` 实例的**父级**(但不是拥有者)。
## 子级
实例化 React 组件时,你可以在开始标签和结束标签之间引用在React 组件或者Javascript 表达式:
~~~
~~~
`Parent` 能通过专门的 `this.props.children` props 读取子级。**`this.props.children` 是一个不透明的数据结构:** 通过 [React.Children 工具类](http://reactjs.cn/react/docs/top-level-api.html#react.children) 来操作。
### 子级校正(Reconciliation)
**校正就是每次 render 方法调用后 React 更新 DOM 的过程。** 一般情况下,子级会根据它们被渲染的顺序来做校正。例如,下面代码描述了两次渲染的过程:
~~~
// 第一次渲染
// 第二次渲染
~~~
直观来看,只是删除了`Paragraph 1`。事实上,React 先更新第一个子级的内容,然后删除最后一个组件。React 是根据子级的_顺序_来校正的。
### 子组件状态管理
对于大多数组件,这没什么大碍。但是,对于使用 `this.state` 来在多次渲染过程中里维持数据的状态化组件,这样做潜在很多问题。
多数情况下,可以通过隐藏组件而不是删除它们来绕过这些问题。
~~~
// 第一次渲染
// 第二次渲染
~~~
### 动态子级
如果子组件位置会改变(如在搜索结果中)或者有新组件添加到列表开头(如在流中)情况会变得更加复杂。如果子级要在多个渲染阶段保持自己的特征和状态,在这种情况下,你可以通过给子级设置惟一标识的 `key` 来区分。
~~~
render: function() {
var results = this.props.results;
return (
{this.props.data.text} ;
}
});
var MyComponent = React.createClass({
render: function() {
return (
{this.props.data.text} ;
}
});
var MyComponent = React.createClass({
render: function() {
return (
{result.text} ;
});
return (
';
Paragraph 1
Paragraph 2
Paragraph 2
Paragraph 1
Paragraph 2
Paragraph 1
Paragraph 2
-
{results.map(function(result) {
return
- {result.text} ; })}
-
{this.props.results.map(function(result) {
return
-
{this.props.results.map(function(result) {
return
-
{items}
富交互性的动态用户界面
最后更新于:2022-04-01 20:47:42
我们已经学习如何使用 React [呈现数据](http://reactjs.cn/react/docs/displaying-data.html)。现在让我们来学习如何创建交互式界面。
## 简单例子
~~~
var LikeButton = React.createClass({
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked ? 'like' : 'haven\'t liked';
return (
,
document.getElementById('example')
);
~~~
## 事件处理与合成事件(Synthetic Events)
React 里只需把事件处理器(event handler)以骆峰命名(camelCased)形式当作组件的 props 传入即可,就像使用普通 HTML 那样。React 内部创建一套合成事件系统来使所有事件在 IE8 和以上浏览器表现一致。也就是说,React 知道如何冒泡和捕获事件,而且你的事件处理器接收到的 events 参数与 [W3C 规范](http://www.w3.org/TR/DOM-Level-3-Events/) 一致,无论你使用哪种浏览器。
如果需要在手机或平板等触摸设备上使用 React,需要调用`React.initializeTouchEvents(true);` 启用触摸事件处理。
## 幕后原理:自动绑定和事件代理
在幕后,React 做了一些操作来让代码高效运行且易于理解。
**Autobinding:** 在 JavaScript 里创建回调的时候,为了保证 `this` 的正确性,一般都需要显式地绑定方法到它的实例上。有了 React,所有方法被自动绑定到了它的组件实例上。React 还缓存这些绑定方法,所以 CPU 和内存都是非常高效。而且还能减少打字!
**事件代理 :** React 实际并没有把事件处理器绑定到节点本身。当 React 启动的时候,它在最外层使用唯一一个事件监听器处理所有事件。当组件被加载和卸载时,只是在内部映射里添加或删除事件处理器。当事件触发,React 根据映射来决定如何分发。当映射里处理器时,会当作空操作处理。参考 [David Walsh 很棒的文章](http://davidwalsh.name/event-delegate) 了解这样做高效的原因。
## 组件其实是状态机(State Machines)
React 把用户界面当作简单状态机。把用户界面想像成拥有不同状态然后渲染这些状态,可以轻松让用户界面和数据保持一致。
React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。React 来决定如何最高效地更新 DOM。
## State 工作原理
常用的通知 React 数据变化的方法是调用 `setState(data, callback)`。这个方法会合并(merge) `data` 到 `this.state`,并重新渲染组件。渲染完成后,调用可选的 `callback`回调。大部分情况下不需要提供 `callback`,因为 React 会负责把界面更新到最新状态。
## 哪些组件应该有 State?
大部分组件的工作应该是从 `props` 里取数据并渲染出来。但是,有时需要对用户输入、服务器请求或者时间变化等作出响应,这时才需要使用 State。
** 尝试把尽可能多的组件无状态化。** 这样做能隔离 state,把它放到最合理的地方,也能减少冗余,同时易于解释程序运作过程。
常用的模式是创建多个只负责渲染数据的无状态(stateless)组件,在它们的上层创建一个有状态(stateful)组件并把它的状态通过 `props` 传给子级。这个有状态的组件封装了所有用户的交互逻辑,而这些无状态组件则负责声明式地渲染数据。
## 哪些 _应该_ 作为 State?
**State 应该包括那些可能被组件的事件处理器改变并触发用户界面更新的数据。** 真实的应用中这种数据一般都很小且能被 JSON 序列化。当创建一个状态化的组件时,想象一下表示它的状态最少需要哪些数据,并只把这些数据存入 `this.state`。在 `render()` 里再根据 state 来计算你需要的其它数据。你会发现以这种方式思考和开发程序最终往往是正确的,因为如果在 state 里添加冗余数据或计算所得数据,需要你经常手动保持数据同步,不能让 React 来帮你处理。
## 哪些 _不应该_ 作为 State?
`this.state` 应该仅包括能表示用户界面状态所需的最少数据。因此,它不应该包括:
* **计算所得数据:** 不要担心根据 state 来预先计算数据 —— 把所有的计算都放到`render()` 里更容易保证用户界面和数据的一致性。例如,在 state 里有一个数组(listItems),我们要把数组长度渲染成字符串, 直接在 `render()` 里使用`this.state.listItems.length + ' list items'` 比把它放到 state 里好的多。
* **React 组件:** 在 `render()` 里使用当前 props 和 state 来创建它。
* **基于 props 的重复数据:** 尽可能使用 props 来作为惟一数据来源。把 props 保存到 state 的一个有效的场景是需要知道它以前值的时候,因为未来的 props 可能会变化。
';
You {text} this. Click to toggle.
); } }); React.render(JSX 的陷阱
最后更新于:2022-04-01 20:47:39
JSX 与 HTML 非常相似,但是有些关键区别要注意。
> 注意:
>
> 关于 DOM 的区别,如行内样式属性 `style`,参考 [DOM 区别](http://reactjs.cn/react/docs/dom-differences.html)
## HTML 实体
HTML 实体可以插入到 JSX 的文本中。
~~~
';
First · Second
~~~
如果想在 JSX 表达式中显示 HTML 实体,可以会遇到二次转义的问题,因为 React 默认会转义所有字符串,为了防止各种 XSS 攻击。
~~~
// 错误: 会显示 “First · Second”
{'First · Second'}
~~~
有多种绕过的方法。最简单的是直接用 Unicode 字符。这时要确保文件是 UTF-8 编码且网页也指定为 UTF-8 编码。
~~~
{'First · Second'}
~~~
安全的做法是先找到 [实体的 Unicode 编号](http://www.fileformat.info/info/unicode/char/b7/index.htm) ,然后在 JavaScript 字符串里使用。
~~~
{'First \u00b7 Second'}
{'First ' + String.fromCharCode(183) + ' Second'}
~~~
可以在数组里混合使用字符串和 JSX 元素。
~~~
{['First ', ·, ' Second']}
~~~
万不得已,可以直接使用原始 HTML。
~~~
~~~
## 自定义 HTML 属性
如果往原生 HTML 元素里传入 HTML 规范里不存在的属性,React 不会显示它们。如果需要使用自定义属性,要加 `data-` 前缀。
~~~
~~~
以 `aria-` 开头的 [网络无障碍] 属性可以正常使用。
~~~
~~~
JSX的延展属性
最后更新于:2022-04-01 20:47:37
如果你事先知道组件需要的全部 Props(属性),JSX 很容易地这样写:
~~~
var component = ;
~~~
## 修改 Props 是不好的,明白吗
如果你不知道要设置哪些 Props,那么现在最好不要设置它:
~~~
var component = ;
component.props.foo = x; // 不好
component.props.bar = y; // 同样不好
~~~
这样是反模式,因为 React 不能帮你检查属性类型(propTypes)。这样即使你的 属性类型有错误也不能得到清晰的错误提示。
Props 应该被当作禁止修改的。修改 props 对象可能会导致预料之外的结果,所以最好不要去修改 props 对象。
## 延展属性(Spread Attributes)
现在你可以使用 JSX 的新特性 - 延展属性:
~~~
var props = {};
props.foo = x;
props.bar = y;
var component = ;
~~~
传入对象的属性会被复制到组件内。
它能被多次使用,也可以和其它属性一起用。注意顺序很重要,后面的会覆盖掉前面的。
~~~
var props = { foo: 'default' };
var component = ;
console.log(component.props.foo); // 'override'
~~~
## 这个奇怪的 `...` 标记是什么?
这个 `...` 操作符(也被叫做延展操作符 - spread operator)已经被 [ES6 数组](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) 支持。相关的还有 ES7 规范草案中的 [Object 剩余和延展属性(Rest and Spread Properties)](https://github.com/sebmarkbage/ecmascript-rest-spread)。我们利用了这些还在制定中标准中已经被支持的特性来使 JSX 拥有更优雅的语法。
';
深入理解 JSX
最后更新于:2022-04-01 20:47:35
[JSX](http://facebook.github.io/jsx/) 是一个看起来很像 XML 的 JavaScript 语法扩展。React 可以用来做简单的 JSX 句法转换。
[TOC]
## 为什么要使用 JSX?
你不需要为了 React 使用 JSX,可以直接使用纯粹的 JS。但我们更建议使用 JSX , 因为它能定义简洁且我们熟知的包含属性的树状结构语法。
对于非专职开发者(比如设计师)同样比较熟悉。
XML 有固定的标签开启和闭合。这能让复杂的树更易于阅读,优于方法调用和对象字面量的形式。
它没有修改 JavaScript 语义。
## HTML标签 与 React组件 对比
React 可以渲染 HTML 标签 (strings) 或 React 组件 (classes)。
要渲染 HTML 标签,只需在 JSX 里使用小写字母开头的标签名。
~~~
var myDivElement = ;
React.render(myDivElement, document.body);
~~~
要渲染 React 组件,只需创建一个大写字母开头的本地变量。
~~~
var MyComponent = React.createClass({/*...*/});
var myElement = ;
React.render(myElement, document.body);
~~~
React 的 JSX 里约定分别使用首字母大、小写来区分本地组件的类和 HTML 标签。
> 注意:
>
> 由于 JSX 就是 JavaScript,一些标识符像 `class` 和 `for` 不建议作为 XML 属性名。作为替代,React DOM 使用 `className` 和 `htmlFor` 来做对应的属性。
## 转换
JSX 把类 XML 的语法转成纯粹 JavaScript,XML 元素、属性和子节点被转换成`React.createElement` 的参数。
~~~
var Nav;
// 输入 (JSX):
var app = ;
// 输出 (JS):
var app = React.createElement(Nav, {color:"blue"});
~~~
注意,要想使用 ``,`Nav` 变量一定要在作用区间内。
JSX 也支持使用 XML 语法定义子结点:
~~~
var Nav, Profile;
// 输入 (JSX):
var app = ;
// 输出 (JS):
var app = React.createElement(
Nav,
{color:"blue"},
React.createElement(Profile, null, "click")
);
~~~
使用 [JSX 编译器](http://reactjs.cn/react/jsx-compiler.html) 来试用 JSX 并理解它是如何转换到原生 JavaScript,还有 [HTML 到 JSX 转换器](http://reactjs.cn/react/html-jsx.html) 来把现有 HTML 转成 JSX。
如果你要使用 JSX,这篇 [新手入门](http://reactjs.cn/react/docs/getting-started.html) 教程来教你如何搭建环境。
> 注意:
>
> JSX 表达式总是会当作 ReactElement 执行。具体的实际细节可能不同。一种优化 的模式是把 ReactElement 当作一个行内的对象字面量形式来绕过`React.createElement` 里的校验代码。
## JavaScript 表达式
### 属性表达式
要使用 JavaScript 表达式作为属性值,只需把这个表达式用一对大括号 (`{}`) 包起来,不要用引号 (`""`)。
~~~
// 输入 (JSX):
var person = ;
// 输出 (JS):
var person = React.createElement(
Person,
{name: window.isLoggedIn ? window.name : ''}
);
~~~
### 子节点表达式
同样地,JavaScript 表达式可用于描述子结点:
~~~
// 输入 (JSX):
var content = {window.isLoggedIn ? : } ;
// 输出 (JS):
var content = React.createElement(
Container,
null,
window.isLoggedIn ? React.createElement(Nav) : React.createElement(Login)
);
~~~
### 注释
JSX 里添加注释很容易;它们只是 JS 表达式而已。你只需要在一个标签的子节点内(非最外层)小心地用 `{}` 包围要注释的部分。
~~~
var content = (
);
~~~
> 注意:
>
> JSX 类似于 HTML,但不完全一样。参考 [JSX 陷阱](http://reactjs.cn/react/docs/jsx-gotchas.html) 了解主要不同。
';