diff --git a/docs/docs/01-why-react.zh-CN.md b/docs/docs/01-why-react.zh-CN.md index 7953668ecc..ca1ae0a0f9 100644 --- a/docs/docs/01-why-react.zh-CN.md +++ b/docs/docs/01-why-react.zh-CN.md @@ -4,7 +4,6 @@ title: 为什么使用 React? permalink: why-react-zh-CN.html next: displaying-data-zh-CN.html --- - React 是一个 Facebook 和 Instagram 用来创建用户界面的 JavaScript 库。很多人选择将 React 认为是 **[MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)** 中的 **V**(视图)。 我们创造 React 是为了解决一个问题:**构建随着时间数据不断变化的大规模应用程序**。 @@ -28,5 +27,3 @@ React挑战了很多传统的知识,第一眼看上去可能很多想法有点 ## 了解更多 你可以从这篇[博客](/react/blog/2013/06/05/why-react.html)了解更多我们创造 React 的动机。 - - diff --git a/docs/docs/10.4-test-utils.zh-CN.md b/docs/docs/10.4-test-utils.zh-CN.md new file mode 100644 index 0000000000..1734739f14 --- /dev/null +++ b/docs/docs/10.4-test-utils.zh-CN.md @@ -0,0 +1,244 @@ +--- +id: test-utils-zh-CN +title: 测试工具 +permalink: test-utils-zh-CN.html +prev: two-way-binding-helpers-zh-CN.html +next: clone-with-props-zh-CN.html +--- + +`ReactTestUtils` 使你在你选择的测试框架中 (我们使用 [Jest](https://facebook.github.io/jest/)) 测试 React 组件变得容易。 + +``` +var ReactTestUtils = require('react-addons-test-utils'); +``` + +### Simulate + +```javascript +Simulate.{eventName}( + DOMElement element, + [object eventData] +) +``` + +模拟一个在 DOM 节点上带有可选 `eventData` 事件数据的事件派遣(event dispatch)。**这可能是 `ReactTestUtils` 里单独最有用的工具。** + +**点击一个元素** + +```javascript +// +var node = this.refs.button; +ReactTestUtils.Simulate.click(node); +``` + +**改变 input 域的值然后点击 回车。** + +```javascript +// +var node = this.refs.input; +node.value = 'giraffe' +ReactTestUtils.Simulate.change(node); +ReactTestUtils.Simulate.keyDown(node, {key: "Enter", keyCode: 13, which: 13}); +``` + +*注意你将必须提供任何你在你的组件里使用的事件属性(例如 keyCode, which, 等等)因为React没有为你创建任何这类东西。* + +`Simulate` has a method for [every event that React understands](/react/docs/events.html#supported-events). + +### renderIntoDocument + +```javascript +ReactComponent renderIntoDocument( + ReactElement instance +) +``` + +渲染一个组件到 document 里的 detached DOM 节点。**这个函数需要一个 DOM。** + +> 注意: +> +> 在你 import React **之前**,你需要让 `window`, `window.document` 和 `window.document.createElement` 全局可用。 +不然 React 会认为它不能访问 DOM 然后类似 `setState` 的方法会不工作。 + +### mockComponent + +```javascript +object mockComponent( + function componentClass, + [string mockTagName] +) +``` + +传入一个 mocked 组件模块到这个方法来给它增加有用的方法,使它可以被用作 dummy React 组件。代替像通常一样的渲染,组件会成为一个简单的包含了任意被提供的子级的 `
` (或者其他 tag 名,如果提供了 `mockTagName`) + +### isElement + +```javascript +boolean isElement( + ReactElement element +) +``` + +返回 `true` 如果 `element` 是任意的 ReactElement。 + +### isElementOfType + +```javascript +boolean isElementOfType( + ReactElement element, + function componentClass +) +``` + +返回 `true` 如果 `element` 是一个类型是 React `componentClass` 的 ReactElement。 + +### isDOMComponent + +```javascript +boolean isDOMComponent( + ReactComponent instance +) +``` + +返回 `true` 如果 `instance` 是一个 DOM 组件 (比如一个 `
` 或者 ``)。 + +### isCompositeComponent + +```javascript +boolean isCompositeComponent( + ReactComponent instance +) +``` + +返回 `true` 如果 `instance` 是一个复合组件 (由 `React.createClass()` 创建)。 + +### isCompositeComponentWithType + +```javascript +boolean isCompositeComponentWithType( + ReactComponent instance, + function componentClass +) +``` + +返回 `true` 如果 `instance` 是一个类型为 React `componentClass` 的复合组件 (由 `React.createClass()` 创建)。 + +### findAllInRenderedTree + +```javascript +array findAllInRenderedTree( + ReactComponent tree, + function test +) +``` + +遍历 `tree` 里所有的组件,并累积所有 `test(component)` 为 `true` 的组件。它本身并没有什么用,但是它被用作其他测试工具的基本元素。 + +### scryRenderedDOMComponentsWithClass + +```javascript +array scryRenderedDOMComponentsWithClass( + ReactComponent tree, string className +) +``` + +在渲染的树中查找所有 DOM组件的类名匹配`className` 的组件实例。 + +### findRenderedDOMComponentWithClass + +```javascript +ReactComponent findRenderedDOMComponentWithClass( + ReactComponent tree, + string className +) +``` + +类似 `scryRenderedDOMComponentsWithClass()` 除了只有一个返回结果,并且要么返回这个结果,要么如果还有其他的匹配项就抛出一个异常。 + +### scryRenderedDOMComponentsWithTag + +```javascript +array scryRenderedDOMComponentsWithTag( + ReactComponent tree, + string tagName +) +``` + +在渲染的树中查找所有 DOM 组件的 tag 名匹配 `tagName` 的组件实例。 + +### findRenderedDOMComponentWithTag + +```javascript +ReactComponent findRenderedDOMComponentWithTag( + ReactComponent tree, + string tagName +) +``` + +类似 `scryRenderedDOMComponentsWithTag()` 除了只有一个返回结果,并且要么返回这个结果,要么如果还有其他的匹配项就抛出一个异常。 + +### scryRenderedComponentsWithType + +```javascript +array scryRenderedComponentsWithType( + ReactComponent tree, + function componentClass +) +``` + +查找所有类型等于 `componentClass` 的组件实例。 + +### findRenderedComponentWithType + +```javascript +ReactComponent findRenderedComponentWithType( + ReactComponent tree, function componentClass +) +``` + +类似 `scryRenderedComponentsWithType()` 除了只有一个返回结果,并且要么返回这个结果,要么如果还有其他的匹配项就抛出一个异常。 + +## Shallow rendering(浅渲染) + +浅渲染是一个实验性特性,让你渲染一个组件为 "one level deep" 并且断言渲染方法返回的内容,不用担心子组件的行为,它们没有被实例化或者渲染。这个方式不需要一个 DOM。 + +```javascript +ReactShallowRenderer createRenderer() +``` + +在你的测试里调用它来创建一个浅渲染器。你可以把它想做是一个你渲染你要测试的组件的 "地方",它可以自己响应事件并更新。 + +```javascript +shallowRenderer.render( + ReactElement element +) +``` + +类似于 `ReactDOM.render`。 + +```javascript +ReactElement shallowRenderer.getRenderOutput() +``` + +在 `render` 被调用后,返回一个浅渲染的输出。你可以接着断言输出的内容。例如,如果你的组件的渲染方法返回: + +```javascript +
+ Title + +
+``` + +然后你可以断言: + +```javascript +var renderer = ReactTestUtils.createRenderer(); +result = renderer.getRenderOutput(); +expect(result.type).toBe('div'); +expect(result.props.children).toEqual([ + Title, + +]); +``` + +浅测试现在有一些限制,即不支持 refs。我们在早期发布这个特性,并感激 React 社区关于它应该如何演化的反馈。 diff --git a/docs/docs/10.5-clone-with-props.zh-CN.md b/docs/docs/10.5-clone-with-props.zh-CN.md new file mode 100644 index 0000000000..ea970ad1bc --- /dev/null +++ b/docs/docs/10.5-clone-with-props.zh-CN.md @@ -0,0 +1,36 @@ +--- +id: clone-with-props-zh-CN +title: 克隆 ReactElements +permalink: clone-with-props-zh-CN.html +prev: test-utils-zh-CN.html +next: create-fragment-zh-CN.html +--- + +> 注意: +> `cloneWithProps` 被弃用了. 用 [React.cloneElement](top-level-api.html#react.cloneelement) 代替. + +在很罕见的情况下,你可能需要创建一个 React 元素的拷贝,它与初始的元素有不同的 props。一个例子是克隆这些传递到 `this.props.children` 的元素并用不同的 props 渲染他们。 + +```js +var cloneWithProps = require('react-addons-clone-with-props'); + +var _makeBlue = function(element) { + return cloneWithProps(element, {style: {color: 'blue'}}); +}; + +var Blue = React.createClass({ + render: function() { + var blueChildren = React.Children.map(this.props.children, _makeBlue); + return
{blueChildren}
; + } +}); + +ReactDOM.render( + +

This text is blue.

+
, + document.getElementById('container') +); +``` + +`cloneWithProps` 不传递 `key` 或者 `ref` 到被克隆的元素。`className` 和 `style` props 被自动合并。 diff --git a/docs/docs/10.6-create-fragment.zh-CN.md b/docs/docs/10.6-create-fragment.zh-CN.md new file mode 100644 index 0000000000..661cef5704 --- /dev/null +++ b/docs/docs/10.6-create-fragment.zh-CN.md @@ -0,0 +1,75 @@ +--- +id: create-fragment-zh-CN +title: Keyed Fragments +permalink: create-fragment-zh-CN.html +prev: clone-with-props-zh-CN.html +next: update-zh-CN.html +--- + +在大多数情况下,你可以使用 `key` prop 指定你从 `render` 返回的元素的 keys。然而,这在一个情况下会失败:如果你有两组你需要记录的子级,将没有办法在不使用包裹元素的情况下放置一个 key 到每组上。 + +即是,如果你有一个像这样的组件: + +```js +var Swapper = React.createClass({ + propTypes: { + // `leftChildren` and `rightChildren` can be a string, element, array, etc. + leftChildren: React.PropTypes.node, + rightChildren: React.PropTypes.node, + + swapped: React.PropTypes.bool + }, + render: function() { + var children; + if (this.props.swapped) { + children = [this.props.rightChildren, this.props.leftChildren]; + } else { + children = [this.props.leftChildren, this.props.rightChildren]; + } + return
{children}
; + } +}); +``` + +这些子级会在当你改变 `swapped` prop 时加载和卸载,因为没有任何的 key 标记在这两组子级上。 + +要解决这个问题,你可以使用 `createFragment` 插件来给予这两组子级 keys. + +#### `Array createFragment(object children)` + +代替创建数组,我们这样写: + +```js +var createFragment = require('react-addons-create-fragment'); + +if (this.props.swapped) { + children = createFragment({ + right: this.props.rightChildren, + left: this.props.leftChildren + }); +} else { + children = createFragment({ + left: this.props.leftChildren, + right: this.props.rightChildren + }); +} +``` + +被传入对象的 keys (即 `left` 和 `right`)被用作为整组子级的 keys,并且对象 keys 的顺序被用于决定渲染子级的顺序。通过这个改变,这两个子级将会恰当的在 DOM 里排序,而不被卸载。 + +`createFragment` 的返回值应该被对待为一个不透明的对象;你可以使用 `React.Children` 来遍历一个 fragment 但是不应该直接访问它。同样注意,我们依赖于 JavaScript 引擎保留了对象的枚举顺序,这点在 spec 上是不保证的,但是所有主要的浏览器和 VMs 都对非数字键的对象实现了这个特性。 + +> **注意:** +> +> 将来,`createFragment` 也许会被替换为如下的API: +> +> ```js +> return ( +>
+> {this.props.rightChildren}, +> {this.props.leftChildren} +>
+> ); +> ``` +> +> 允许你直接在 JSX 里赋值 keys 而不用添加包裹元素。 diff --git a/docs/docs/10.7-update.zh-CN.md b/docs/docs/10.7-update.zh-CN.md new file mode 100644 index 0000000000..ea1b3b7349 --- /dev/null +++ b/docs/docs/10.7-update.zh-CN.md @@ -0,0 +1,102 @@ +--- +id: update-zh-CN +title: immutability 助手 +permalink: update-zh-CN.html +prev: create-fragment-zh-CN.html +next: pure-render-mixin-zh-CN.html +--- + +React 让你可以使用任何你想要的数据管理方式,包括 mutation。然而,如果你可以在你的应用的性能关键性部分里使用 immutable 数据,将会易于实现一个快速的 `shouldComponentUpdate()` 方法来显著加速你的 app。 + +在 JavaScript 里处理 immutable 数据比在为此设计的语言中要难的多,比如 [Clojure](http://clojure.org/)。然而,我们提供了一个简单的 immutability 助手,`update()`,它使处理这类数据容易多了,*不用* 根本性的改变你的数据的表达方式。你也同样可以看一看 Facebook 的 [Immutable-js](https://facebook.github.io/immutable-js/docs/) 和[Advanced Performance](/react/docs/advanced-performance.html) 了解更多关于 Immutable-js 的信息。 + +## 主要的思路 + +如果你像这样变动数据: + +```js +myData.x.y.z = 7; +// or... +myData.a.b.push(9); +``` + +你将没有任何办法决定哪个数据被改变了,因为之前的拷贝已经被覆盖。作为替代,你需要创建一个新的 `myData` 的拷贝并且只修改需要改变的地方。然后你可以用三个等于在 `shouldComponentUpdate()` 里比较旧的 `myData` 拷贝与新的拷贝: + +```js +var newData = deepCopy(myData); +newData.x.y.z = 7; +newData.a.b.push(9); +``` + +不幸的是,深拷贝很昂贵,并且有时候不可能。你可以通过仅仅拷贝需要被改变和重用没有改变的对象来缓解这个情况。不幸的是,在当今的 JavaScript 里这会很笨重: + +```js +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` 的对象进行了浅拷贝并重用了剩下的),但它写起来很痛苦。看看所有重复的代码!这不仅仅是烦人的,同时也提供了一大片 bugs 区域。 + +`update()` 提供了这个模式的简单语法糖来使写这类代码更容易。上面的代码变成: + +```js +var update = require('react-addons-update'); + +var newData = update(myData, { + x: {y: {z: {$set: 7}}}, + a: {b: {$push: [9]}} +}); +``` + +虽然这个语法需要花一些时间来适应(它的灵感来自于 [MongoDB's query language](http://docs.mongodb.org/manual/core/crud-introduction/#query)),但是没有冗余,它可静态分析并且没有 mutative 版本那么多键入。 + +`$`-前缀的 keys 被称为 *命令*。被 "变动的" 数据结构被称为 *目标*。 + +## 有效的命令 + + * `{$push: array}` 在目标上 `push()` 所有 `array` 里的项目。 + * `{$unshift: array}` 在目标上 `unshift()` 所有 `array` 里的项目。 + * `{$splice: array of arrays}` 在目标上对于每一个 `arrays` 里的项目使用项目提供的参数调用 `splice()`。 + * `{$set: any}` 整个替换目标. + * `{$merge: object}` 合并 目标和 `object` 的 keys. + * `{$apply: function}` 传递当前的值给 function 并用返回值更新它。 + +## 例子 + +### 简单的 push + +```js +var initialArray = [1, 2, 3]; +var newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4] +``` +`initialArray` is still `[1, 2, 3]`. + +### 嵌套的 collections + +```js +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`的项目。 + +### 基于当前的值更新新值 + +```js +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}}); +``` + +### (浅) 合并 + +```js +var obj = {a: 5, b: 3}; +var newObj = update(obj, {$merge: {b: 6, c: 7}}); // => {a: 5, b: 6, c: 7} +``` diff --git a/docs/docs/10.8-pure-render-mixin.zh-CN.md b/docs/docs/10.8-pure-render-mixin.zh-CN.md new file mode 100644 index 0000000000..d37e927584 --- /dev/null +++ b/docs/docs/10.8-pure-render-mixin.zh-CN.md @@ -0,0 +1,46 @@ +--- +id: pure-render-mixin-zh-CN +title: PureRenderMixin +permalink: pure-render-mixin-zh-CN.html +prev: update-zh-CN.html +next: perf-zh-CN.html +--- + +如果你的 React 组件的 render 函数是 "纯净" 的(换句话说,它的渲染在给定相同的 props 和 state 时返回相同的结果),你可以使用这个 mixin 在某些情况下进行性能加速。 + +例子: + +```js +var PureRenderMixin = require('react-addons-pure-render-mixin'); +React.createClass({ + mixins: [PureRenderMixin], + + render: function() { + return
foo
; + } +}); +``` + +使用 ES6 class 语法的例子: + +```js +import PureRenderMixin from 'react-addons-pure-render-mixin'; +class FooComponent extends React.Component { + constructor(props) { + super(props); + this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); + } + + render() { + return
foo
; + } +} +``` + +在内部, mixin 实现了 [shouldComponentUpdate](/react/docs/component-specs.html#updating-shouldcomponentupdate), 它对当前及下一步的 props 和 state 进行比较,并当相等时返回 `false`。 + +> 注意: +> +> 它仅仅浅比较对象。如果包含了复杂的对象,可能会对于深层的不同产生 false-negatives。只 mix 到那些含有简单 props 和 state 的组件,或者在你知道深层对象改变时使用 `forceUpdate()` 。或者,考虑使用[immutable objects](https://facebook.github.io/immutable-js/) 来促进快速的嵌套数据比较。 +> +> 此外, `shouldComponentUpdate` 跳过了整个子树的更新。确保所有的子组件都是 "纯净" 的。 diff --git a/docs/docs/10.9-perf.zh-CN.md b/docs/docs/10.9-perf.zh-CN.md new file mode 100644 index 0000000000..b4f314bc37 --- /dev/null +++ b/docs/docs/10.9-perf.zh-CN.md @@ -0,0 +1,74 @@ +--- +id: perf-zh-CN +title: 性能工具 +permalink: perf-zh-CN.html +prev: pure-render-mixin-zh-CN.html +next: shallow-compare-zh-CN.html +--- + +React 通常是相当快的。然而,在你需要压榨你的 app 的每一分性能的情况下,它提供了一个[shouldComponentUpdate](/react/docs/component-specs.html#updating-shouldcomponentupdate) 钩子,在此你可以添加优化提示到 React 的 diff 算法里。 + +除了给予你一个你的 app 的整体性能概览外,ReactPerf 还是一个准确告诉你,你需要在哪里放置这些钩子的分析工具。 + +## General API + +这里陈述的 `Perf` 对象被用 `require('react-addons-perf')` 暴露,并且只能被使用在 React 的开发模式。你不应在生产环境下在你的 app 包含这个包。 + +> 注意: +> +> 开发版的 React 慢于生产版,因为所有额外提供的逻辑,例如,React 的友好的控制台警告 (在生产版中被除去)。因此,分析工具仅服务于指示你的 app _相对_ 昂贵的部分。 + +### `Perf.start()` and `Perf.stop()` +开始/停止测量。其间的React操作被记录用于之后的分析。产生无关紧要时间的操作被忽略。 + +在停止以后,你将需要 `Perf.getLastMeasurements()` (下面将介绍)来获取测量结果。 + +### `Perf.printInclusive(measurements)` +打印总体花费的时间。如果没有传入参数,默认是从上次记录的所有测量数据。它在控制台里打印良好格式化的结果,像这样: + +![](/react/img/docs/perf-inclusive.png) + +### `Perf.printExclusive(measurements)` +"独占的"时间不包括花费于加载组件的时间: 处理 props, `getInitialState`, 调用 `componentWillMount` 及 `componentDidMount`, 等等。 + +![](/react/img/docs/perf-exclusive.png) + +### `Perf.printWasted(measurements)` + +**分析工具里最有用的部分**. + +"垃圾"时间是花费在组件上实际没有绘制任何东西的时间,例如渲染结果总是相同,所以 DOM 没有被触碰到。 + +![](/react/img/docs/perf-wasted.png) + +### `Perf.printDOM(measurements)` +打印底层的 DOM 操纵,例如 "set innerHTML" 和 "remove". + +![](/react/img/docs/perf-dom.png) + +## Advanced API + +以上的打印方法使用 `Perf.getLastMeasurements()` 来美观的打印结果。 + +### `Perf.getLastMeasurements()` +从最后的 start-stop 会话获取测量数据数据。这个数组包含对象,每个看起来像这样: + +```js +{ + // 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 +} +``` diff --git a/docs/docs/tutorial.zh-CN.md b/docs/docs/tutorial.zh-CN.md index 6cb5b5067e..6f09154b7e 100755 --- a/docs/docs/tutorial.zh-CN.md +++ b/docs/docs/tutorial.zh-CN.md @@ -44,6 +44,7 @@ next: thinking-in-react-zh-CN.html +
@@ -96,7 +97,7 @@ ReactDOM.render( #### JSX 语法 -首先你会注意到你的 JavaScript 中 XML 式的语法。我们有一个简单的预编译器,将这种语法糖转换成单纯的 JavaScript : +首先你会注意到你的 JavaScript 中 XML 式的语法。我们有一个简单的预编译器,将语法糖转换成这种纯的JavaScript: ```javascript // tutorial1-raw.js @@ -127,6 +128,10 @@ ReactDOM.render( `ReactDOM.render()` 实例化根组件,启动框架,注入标记到原始的 DOM 元素中,作为第二个参数提供。 +`ReactDOM` 模块暴露了 DOM 相关的方法, 而 `React` 保有被不同平台的 React 共享的核心工具 (例如 [React Native](http://facebook.github.io/react-native/))。 + +对于本教程 `ReactDOM.render` 保持在脚本底部是很重要的。`ReactDOM.render` 应该只在复合组件被定义之后被调用。 + ## 组合组件 让我们为 `CommentList` 和 `CommentForm` 建造骨架,它们将会,再一次的,是一些简单的 `
`。添加这两个组件到你的文件里,保持现存的 `CommentBox` 声明和 `ReactDOM.render` 调用: @@ -219,22 +224,7 @@ var CommentList = React.createClass({ Markdown 是一种简单的内联格式化你的文字的方法。例如,用星号包围文本将会使其强调突出。 -首先,添加第三方库 **marked** 到你的应用。这是一个JavaScript库,接受 Markdown 文本并且转换为原始的 HTML。这需要在你的头部有一个 script 标签(那个我们已经在 React 操场上包含了的标签): - -```html{9} - - - - React Tutorial - - - - - - -``` - -然后,让我们转换评论文本为 Markdown 并输出它: +在本教程中我们使用第三方库 **marked**,它接受 Markdown 文本并且转换为原始的 HTML。我们已经在初始的页面标记里包含了这个库,所以我们可以直接开始使用它,让我们转换评论文本为 Markdown 并输出它: ```javascript{9} // tutorial6.js @@ -290,12 +280,12 @@ var Comment = React.createClass({ ```javascript // tutorial8.js var data = [ - {author: "Pete Hunt", text: "This is one comment"}, - {author: "Jordan Walke", text: "This is *another* comment"} + {id: 1, author: "Pete Hunt", text: "This is one comment"}, + {id: 2, author: "Jordan Walke", text: "This is *another* comment"} ]; ``` -我们需要以一种模块化的方式将这个数据传入到 `CommentList`。修改 `CommentBox` 和 `ReactDOM.render()` 方法,以通过 props 传入数据到 `CommentList`: +我们需要以一种模块化的方式将这个数据传入到 `CommentList`。修改 `CommentBox` 和 `ReactDOM.render()` 方法,以便于通过 props 传入数据到 `CommentList`: ```javascript{7,15} // tutorial9.js @@ -323,9 +313,9 @@ ReactDOM.render( // tutorial10.js var CommentList = React.createClass({ render: function() { - var commentNodes = this.props.data.map(function (comment) { + var commentNodes = this.props.data.map(function(comment) { return ( - + {comment.text} ); @@ -386,7 +376,7 @@ var CommentBox = React.createClass({ `getInitialState()` 在生命周期里只执行一次,并设置组件的初始状态。 #### 更新状态 -当组件第一次创建时,我们想从服务器获取一些 JSON 并且更新状态以反映最新的数据。我们将用 jQuery 来发送一个异步请求到我们刚才启动的服务器以获取我们需要的数据。看起来像这样: +当组件第一次创建时,我们想从服务器获取一些 JSON 并且更新状态以反映最新的数据。我们将用 jQuery 来发送一个异步请求到我们刚才启动的服务器以获取我们需要的数据。这些数据已经被包含在了你已启动的服务器里(基于`comments.json`文件),所以一旦被获取,`this.state.data` 会看起来像这样: ```json [ @@ -490,28 +480,39 @@ var CommentForm = React.createClass({ }); ``` -让我们做一个交互式的表单。当用户提交表单时,我们应该清空它,提交一个请求给服务器,和刷新评论列表。要开始,让我们监听表单的提交事件并清空它。 +#### 受控组件 -```javascript{3-14,16-19} +对于传统的 DOM, `input` 元素被渲染并且浏览器管理它的状态(它的渲染值)。结果是,DOM的实际值会和组件不同。这是不理想的,因为视图的值会和组件的值不同。在React中,组件应该总是表示视图的值而不只是在初始化时。 + +因此,我们将使用 `this.state` 来在用户输入时保存输入。我们定义一个初始 `state`,它带有 `author` 和 `text` 两个属性并将他们设置为空字符串。在我们的 `` 元素里,我们设置 `value` prop 来反映组件的 `state` 并给他们附加 `onChange` 事件处理。这些带有设置了 `value` 的 `` 元素被称为受控组件。更多关于受控组件请阅读 [Forms article](/react/docs/forms.html#controlled-components)。 + +```javascript{3-11,15-26} // tutorial16.js var CommentForm = React.createClass({ - handleSubmit: function(e) { - e.preventDefault(); - var author = React.findDOMNode(this.refs.author).value.trim(); - var text = React.findDOMNode(this.refs.text).value.trim(); - if (!text || !author) { - return; - } - // TODO: send request to the server - React.findDOMNode(this.refs.author).value = ''; - React.findDOMNode(this.refs.text).value = ''; - return; + getInitialState: function() { + return {author: '', text: ''}; + }, + handleAuthorChange: function(e) { + this.setState({author: e.target.value}); + }, + handleTextChange: function(e) { + this.setState({text: e.target.value}); }, render: function() { return ( -
- - + + +
); @@ -521,14 +522,60 @@ var CommentForm = React.createClass({ ##### 事件 -React使用驼峰命名规范(camelCase)给组件绑定事件处理器。我们给表单绑定一个`onSubmit`处理器,它在表单提交了合法的输入后清空表单字段。 +React使用小驼峰命名规范(camelCase)给组件绑定事件处理器。我们附加 `onChange` 给两个 `` 元素。现在,当用户输入文本到 `` 中,被附加的 `onChange` 回调函数被激发并且组件的 `state` 被修改。然后,被渲染的 `input` 元素的值将会更新以反映当前组件的 `state`。 + +#### 提交表单 + +让我们使表单具有交互性。当用户提交表单时,我们应该清除它,提交一个请求到服务器,并刷新评论列表。让我们监听表单的提交事件并清除它。 + +```javascript{12-21,24} +// tutorial17.js +var CommentForm = React.createClass({ + getInitialState: function() { + return {author: '', text: ''}; + }, + handleAuthorChange: function(e) { + this.setState({author: e.target.value}); + }, + handleTextChange: function(e) { + this.setState({text: e.target.value}); + }, + handleSubmit: function(e) { + e.preventDefault(); + var author = this.state.author.trim(); + var text = this.state.text.trim(); + if (!text || !author) { + return; + } + // TODO: send request to the server + this.setState({author: '', text: ''}); + }, + render: function() { + return ( +
+ + + +
+ ); + } +}); +``` + +我们给表单绑定一个`onSubmit`处理器,它在表单提交了合法的输入后清空表单字段。 在事件中调用`preventDefault()`来阻止浏览器提交表单的默认行为。 -##### Refs - -我们利用`ref`属性给子组件赋予名字,`this.refs`-引用组件。我们可以在组件上调用 `React.findDOMNode(component)` 获取原生的浏览器DOM元素。 - ##### 回调函数作为属性 当用户提交评论时,我们需要刷新评论列表来包含这条新评论。在`CommentBox`中完成所有逻辑是有道理的,因为`CommentBox` 拥有代表了评论列表的状态(state)。 @@ -536,7 +583,7 @@ React使用驼峰命名规范(camelCase)给组件绑定事件处理器。我们 我们需要从子组件传回数据到它的父组件。我们在父组件的`render`方法中以传递一个新的回调函数(`handleCommentSubmit`)到子组件完成这件事,绑定它到子组件的 `onCommentSubmit` 事件上。无论事件什么时候触发,回调函数都将被调用: ```javascript{16-18,31} -// tutorial17.js +// tutorial18.js var CommentBox = React.createClass({ loadCommentsFromServer: function() { $.ajax({ @@ -573,28 +620,45 @@ var CommentBox = React.createClass({ }); ``` -当用户提交表单时,让我们从 `CommentForm` 调用这个回调函数: +既然 `CommentBox` 已经通过 `onCommentSubmit` prop 使回调函数对于 `CommentForm` 可用,`CommentForm` 就可以在用户提交表单时调用回调函数: -```javascript{10} -// tutorial18.js +```javascript{19} +// tutorial19.js var CommentForm = React.createClass({ + getInitialState: function() { + return {author: '', text: ''}; + }, + handleAuthorChange: function(e) { + this.setState({author: e.target.value}); + }, + handleTextChange: function(e) { + this.setState({text: e.target.value}); + }, handleSubmit: function(e) { e.preventDefault(); - var author = React.findDOMNode(this.refs.author).value.trim(); - var text = React.findDOMNode(this.refs.text).value.trim(); + var author = this.state.author.trim(); + var text = this.state.text.trim(); if (!text || !author) { return; } this.props.onCommentSubmit({author: author, text: text}); - React.findDOMNode(this.refs.author).value = ''; - React.findDOMNode(this.refs.text).value = ''; - return; + this.setState({author: '', text: ''}); }, render: function() { return (
- - + +
); @@ -605,7 +669,7 @@ var CommentForm = React.createClass({ 既然现在回调函数已经就绪,我们所需要做的就是提交到服务器然后刷新列表: ```javascript{17-28} -// tutorial19.js +// tutorial20.js var CommentBox = React.createClass({ loadCommentsFromServer: function() { $.ajax({ @@ -657,8 +721,8 @@ var CommentBox = React.createClass({ 我们的应用现在已经功能完备,但是它感觉很慢,因为在评论出现在列表前必须等待请求完成。我们可以优化添加这条评论到列表以使应用感觉更快。 -```javascript{17-19,29} -// tutorial20.js +```javascript{17-23,33} +// tutorial21.js var CommentBox = React.createClass({ loadCommentsFromServer: function() { $.ajax({ @@ -675,6 +739,10 @@ var CommentBox = React.createClass({ }, handleCommentSubmit: function(comment) { var comments = this.state.data; + // Optimistically set an id on the new comment. It will be replaced by an + // id generated by the server. In a production application you would likely + // not use Date.now() for this and would have a more robust system in place. + comment.id = Date.now(); var newComments = comments.concat([comment]); this.setState({data: newComments}); $.ajax({