此文章是翻译State and Lifecycle这篇React(版本v15.4.0)官方文档。
State and Lifecycle
考虑之前章节中的时钟例子。
到现在为止,我们只会通过一种方式进行更新UI。
我们通过调用ReactDOM.render()
方法去改变渲染输入:
1 | function tick(){ |
在这一章节中,我们将学习如何使时钟component 真正可复用和封装。它将设置自己的定时器来每一秒更新它自己。
我们开始封装始时钟外观:
1 | function Clock(props){ |
但是,它忽略了一个关键的需求:一个事实,即Clock
应该在Clock
内部设置一个定时器来每隔一秒进行更新。
理想状况下,我们只写一次来让Clock
更新自身:
1 | ReactDOM.render( |
为了实现,我们需要在Clock
component 中添加state。
State 同props 非常相似,但是它是私有的完全被component 控制。
我们之前提到过 component 使用classes 定义有额外的特性(additional features)。本地State 只能通过classes 提供此功能。
Converting a Function to a Class
你可以通过五步来实现将一个funtional component 类似Clock
转成class:
- 使用相同的名字实现一个ES6 class 并继承
React.Component
。 - 添加一个
render()
空方法。 - 将function 体内容移到
render()
方法内。 - 在
render()
方法体内将props
替换为this.props
。 - 删除空的function 声明。
1 | class Clock extends Component { |
Clock
现在是通过class 定义而不是function。
现在我们可以使用额外的特性例如本地state 和lifecycle hooks。
Adding Local State to a Class
通过三步将date
从props 移到state:
1)在render()
方法中 将this.props.date
替换为 this.state.date
:
1 | class Clock extends Component { |
2)添加一个class constructor 并给this.state
赋初值:
1 | class Clock extends Component { |
注意,我们将props
传入了constructor 中:
1 | constructor(props){ |
Class component 应该总是使用props
调用base constructor。
3)将date
从<Clock />
element 中移除:
1 | ReactDOM.render( |
稍后我们将component 自身添加定时器。
结果看上去如下:
1 | class Clock extends Component { |
接下来,我们将给Clock
设置定时器并每秒更新一次。
Adding Lifecycle Methods to a Class
在一个使用许多component 的应用中,当components 被销毁时去释放资源是非常重要的。
我们将在每次渲染DOM 的一次时设置定时器。在React 中被称为“mounting”。
我们将在Clock
产生的DOM 被移除时去清除定时器。在React 中被称为“unmounting”。
当一个组件被mounts 和unmounts 时,我们将在通过在component class 中声明特殊的方法(special methods)去运行一些代码:
1 | class Clock extends Component { |
这些方法被称为“lifecyle hooks”。
这个componentDidMount()
hook 是在component 的输出已经被渲染到DOM 中之后运行。这是一个好地方去设置一个定时器:
1 | componentDidMount(){ |
注意我们将定时器ID 保存在this
上。
虽然this.props
是被React 自身设置的的但是this.state
有特殊的含义,如果你要存储一些不用于可视化输出的东西,你可以手动自由添加额外的字段到class 上。
如果你不需要在render()
中使用一些东西,它不应该存储在state 中。
我们会在componentWillUnmount()
lifecycyle hook 中销毁这个定时器:
1 | componentWillUnmount(){ |
最后,我们将实现这个每秒运行一次的tick()
方法。
它将会使用this.setState()
去调度更新component 的local state:
1 | class Clock extends Component { |
现在这个时钟每秒滴答一次。
让我们快速回顾一下每一步都调用那个方法:
1)当将<Clock />
传给ReactDOM.render()
方法时,React 调用 Clock
component 的constructor 方法。由于Clock
需要展示当前时间,它使用一个包含当前时间的对象初始化this.state
。我们之后将更新这个state。
2)React 然后调用Clock
component 的render()
方法。这是React 了解要在屏幕上展示什么。React 然后更新DOM 去匹配Clock
的渲染输出。
3)当Clock
输出被插入到DOM 中,React 调用componentDidMount()
lifecycle hook。在它内部,Clock
component 要求浏览器设置一个定时器去每个一秒调用tick()
。
4)每隔一秒浏览器就调用tick()
方法。在它内部,Clock
component 调用一个UI 更新通过调用一个包含当前时间对象的setState()
方法。由于setState()
调用,React 知道当前state 已经发生变化,于是又去调用render()
方法去了解屏幕上应该显示什么。这次,this.state.date
在render()
中是不同的,所以渲染输出会包含当前时间。React 相应地更新DOM。
5)如果Clock
component 被从DOM 中移除,React 将会调用componentWillUnmount()
lifecycle hook 所以这个定时器会停止。
Using State Correctly
你需要知道关于setState()
的三件事。
Do Not Modify State Directly
例如,它将不会重新渲染component:
1 | // Wrong |
相反,应该使用setState()
:
1 | // Correct |
唯一能够直接赋值this.state
的是在constructor 中。
State Updates May Be Asynchronous
为了性能React 可能批处理多个setState()
调用在一次更新中。
因为this.props
和this.state
可以异步更新,你不应该依赖它们中任意值去计算下一个state。
例如,下面代码更新计数器会可能会失败:
1 | // Wrong |
为了解决这个问题,使用另一种方式的setState()
接受一个function 而不是一个对象。这个function 可以接受之前的state 作为第一个参数,此时props 可以被应用为第二个参数:
1 | // Correct |
在上面我们使用了arrow function,但是我们使用常规functions 也是可以工作的:
1 | // Correct |
State Updates are Merged
当你调用setState()
,React 会合并你提供的对象到当前state 中。
例如,你的state 可能包含几个独立的变量:
1 | constructor(props){ |
然后你可以独立地调用几个setState()
方法:
1 | componentDidMount(){ |
合并操作是隐式的,所以this.setState({comments})
保留了this.state.post
完整,但是完全替换了this.state.comments
。
The Data Flows Down
父components 或子components 都不知道某一个component 是有状态的(stateful ) 还是无状态的(stateless ),并且它们也不管它是被定义为一个function 还是一个class。
这就是为什么state 经常被称为本地的(local)或者是封装的(encapsulated)。它不能被除拥有它之外的component 进行设置。
一个component 可以将它的state 作为子components 的props 进行传递:
1 | <h2>It is {this.state.date.toLocaleTimeString()}.</h2> |
在用户自定义component 中工作:
1 | <FormattedDate date={this.state.date} /> |
这个FormattedDate
component 在它的props 中接受date
而不需要知道它是来自Clock
的state ,还是props,亦或是手动输入的:
1 | function FormattedDate(props){ |
这就是自上而下(top-down)或单向的(unidirectional)数据流。任何state 都是有某些特定component 拥有的,任何来自该state 的数据或UI 都只能影响位于层级树之下的component。
如果你设想一个component tree 是一个props 的瀑布流,每一个component 的state 都像是一个在任意位置加入的额外的只能向下流的水源。
为了展示所有的component 都是独立的,我们可以创建一个App
component 去渲染三个Clock
:
1 | function App(){ |
每一个Clock
都独立的设置它们自己的定时器进行更新。
在React 应用中,component 是否有状态是一个可以随着时间变化的实现细节。你可以在有状态的component 中使用无状态的component,反之亦然。