先前有一篇文章探索了 React 组件里的数据。数据由两个结构组成——属性与状态。前者用来代表不可变的数据,而后者代表通过与 UI 交互或通过其他外部手段而改变的数据。 当使用状态数据时,UI 会在调用 setState 函数使得状态改变时进行更新。传统地,这个函数会被调用来响应一个使用事件处理器的事件。 在这篇文章中,我们将进一步探索状态的更新,包括表单输入组件以及通过子组件属性的状态值传递。最后,我们会讲解 Facebook 创建的 Immutable JavaScript 库,以便在需要重新呈现组件时有效地通知 React。 |
输入组件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
var Message = React.createClass({ getInitialState: function () { return { message: this .props.message }; }, _messageChange: function (e) { this .setState({ message: e.target.value }); }, render: function () { return ( <div> <span>Message: { this .state.message}</span> <br /> Message: <input type= "text" value={ this .state.message} onChange={ this ._messageChange} /> </div> ); }, }); |
用户在 input 框中输入文本的时候,change 事件的处理函数被执行,从 textbox 获得值,并更新状态。这个示例中的 change 事件处理函数是 _messageChange。如果没有注册 onChange 事件处理函数,仍然会读取字段,但不会用输入的数据更新 state。在 React 中,输入控件不会进行自我更新,它们更新 state,然后 state 触发一次重绘来更新输入控件。表面上看这种方式有点绕,但是关键在于 React 总是保持组件的 state 并与 DOM 同步。
标准 HTML 表单中的 input 元素,如 input, select 和 textarea 等,被 React 认为是输入组件。因为这些控件可以改变值,React 提供了一套控制机制,通过该机制可以初始化控件、接收输入、更新并在 UI 中反映出来。
输入组件可以是受控的或不受控的。受控的组件由 React 通过 value 和 onChange 属性来管理。当用户在 input 元素中输入文本的时候,已注册的 onChange 事件处理函数会被执行,输入的文本作为参数被传递给事件对象。文本参数用于更新 state,它会通过 props 回传给受控的组件。一个有 value 属性的表单组件,如果没有 onChange 属性,它会像之前提到的那样,是只读的。
然而为什么输入组件是只读的呢?之前说过,受控的组件并不能通过交互来更新,而是直接更新,并触发 change 事件。新值必须通过这个事件的处理函数来获取,通过传入处理函数的事件对象来访问新值。然后,新值会用于更新输入组件父组件的 state。在上面的示例中,父组件是 Message。调用父组件的 setState 函数重绘输入组件,更新的 state 值会由 props 回传给输入组件。为什么要用这种方式?React 组件的视图(在这里就是 DOM)和 state 必须总是保持一致,这不可能使用传统的不受控的输入元素。 思考下面这段没用 React 的代码。
当用户在 input 控件中输入文本的时候,input 控件会显示输入的文本。而在这之后,点击了 button,你认为会有什么样的输出呢?
很有意思,输出的并不是输入更新的内容,而是最初 input 控件渲染时的值。在新文本显示的时候,DOM 却没有同步 input 控件的 state。 |
在 CodePen 上可以看到这个情况发生。 对于多数 JavaScript 库和框架来说,这并不是问题。但对于 React 来说,虚拟 DOM 和组件的 state 必须总是保持同步。 看看下面的 CodePen 演示。 参阅由 SitePoint (@SitePoint) 在 CodePen 上写的 React.js Controlled / Uncontrolled Input 演示。 在第一个输入框中输入文本,可以观察到只有第一个输入框有更新。因为第二个输入框没有绑定 value 属性,当 message 更新的时候,并不会影响到第二个输入框。第二个输入框通过 onChange 属性处理 change 事件,当 state 更新,message 值更新到第一个输入框,然后显示在屏幕上。第二个输入框的 defaultValue 属性只在输入组件首次渲染的时候会用到。 不受控的输入组件没有 value 属性集,而且一般在组件上发生交互的时候在 UI 中更新,但不会因为 state 变化而进行重绘。 要探索输入组件的其它功能,就看看下面两段关于 ColorList 的演示。 |
在子组件中传递 State
|
1
2
3
4
5
6
7
8
9
|
Parent Color Component getInitialState: function () { return { colors: new Immutable.List( this .props.colors) }; }, render: function () { return ( <ColorList colors={ this .state.colors} /> <ColorForm addColor={ this ._addColor} /> ); } |
为了将 state 的值从父组件传送给子组件,state 值由 props 传入子组件,就像上面父组件的 render 函数那样。 子组件通过组件的 props 属性访问传入的属性值,就像下面的代码。
观察数据流向——父组件通过它的 props 接收数据,然后由这些 props 初始化父组件的 state,之后父组件将 state 传递给子组件的 props,最后子组件根据 props 进行渲染。 同一个数据被当作不可变的 props,也被当作可变的 state,到底是什么取决于接收数据的组件用它来干什么。父组件把数据当作可变的 state 来进行处理,是因为它能处理子组件的事件,而从这些事件可以得到新的数据,这就可以导致 state 变化,再将更新的 state 传递给所有子组件。子组件得到新数据之后不会用来更新任何东西,它只是直接把数据传送给父组件来进行更新。这样的结果使得理解和预测数据流变得简单。
上面的示例中,点击按钮会触发相应的处理函数,这个函数是从父组件 Color 中以 props 方式传递给子组件 ColorForm 的。这个函数触发时会让父组件往 state 里添加一个新的颜色,再由 setState 函数触发重绘。
一个组件发生更新时会通知它的父组件,随后这个父组合会通知所有子组件。state 由某个组件维护,其它组件都只是显示从 state 而来的不可变的 props。 要查看整个 ColorList 演示的偌,就去下面的 CodePen。 |
不可变性props 严格来说是可变的(JavaScriopt 没有阻止组件改变它们),但修改它们会违背 React 的基本原则,因此应该把 props 看作是不可变的。 另一方面,state 却经常变化。然后将不变性原则用于 state 可以提高 React 组件的性能。 对改变 state 来说有一个重要的方面就是检测改变了什么,然后根据这些变化更新虚拟 DOM。其中一些变化很容易确定。state 中的数和字符串值变化很容易通过对新旧值的简单比较来确定。甚至创建一个新的对象,并在 state 上设置的新引用也很容易检测出来。但是,一个数组呢?程序如何确定一个数组已经改变了?当在数组中添加新项时,数据的引用并不会发生变化。检查数据的长度可能会发现更改,但如果添加了一项同时删除了一项又该怎么办?程序如何在不遍历数组的情况下确定数据是否发生变化?与检查意会的更改相比,这是一个难以解决的问题。 |
解决这个问题的办法就是不可变性。Facebook 创建了一个叫 Immutable 的 JavaScript 库,它提供了一些结构来辅助 JavaScript 创建和管理不可变对象。
在上面的示例代码中,向列表中添加 “orange” 会产生新的列表。push() 不会返回一个添加了额外的颜色的原列表。相反,它返回一个新对象的新引用。新的对象引用可以很容易地用于检测列表是否已经发生了变化。使用不可变结构让 React 组件避免在列表中一项项地去比较 — 而只是简单的比较数组的引用。 React 对每个组件调用 shouldComponentUpdate 函数以确定是否需要重绘以响应 state 的变化。这个函数的默认实现只是返回了一个 true。这么一来,组件及其子组件就不会关心是否发生变化,每次都会进行重绘。如果要在不需要重绘的情况下避免重绘,组件可以改写默认的函数,对 state 或 props 的数据进行检测。(props 由新的 state 数据填充)。为了利用这种高效的检查,可以在组件的定义中自定义 shouldComponentUpdate 函数(原函数会隐藏在原型中),添加一行简单的代码用于检查引用是否相同。 |
看看上一节的演示的颜色列表的代码,展示如下。
父组件中的 _addColor 函数作为另一个子组件 (这里没有展示出来,它在 CodePen 中)的结果来执行。事件中将新的颜色传递给 _addColor 函数,添加到颜色列表中。添加颜色的时候,Immutable 库提供的 push 函数会创建一个新的列表返回出来。父组件重绘的时候,子组件 ColorList 的 shouldComponentUpdate 函数被调用,它会比较原来的颜色列表和新颜色列表的引用(想明白新颜色列表是如何从父组件传递给子组件的,就看看上一节的内容)。因为 Immutable 库产生了新的对象,只需要通过引用就能判断列表是否发生变化。因此,列表在有变化时更新,而不是每次都由父组件触发重绘。 下面的 CodePen 中有所有示例。 参阅由 SitePoint (@SitePoint) 在 CodePen 上写的 React.js Immutability 演示。 |
演示程序
|
结论
|
本文标题:探索 React 的状态传播
本文地址:https://www.oschina.net/translate/exploring-reacts-state-propagation
参与翻译:Viyi, 边城, xufuji456
英文原文:Exploring React’s State Propagation