React State & 生命周期
本介绍了 React 组件中 state 和生命周期的概念。
在元素渲染章节中,我们只了解了一种更新 UI 界面的。通过 ReactDOM.render() 来我们想要渲染的元素:
function tick() { const element = ( <div> <h1>Hello, world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render( element, document.getElementById('root') ); } setInterval(tick, 1000);
在本章节中,我们将学习如何封装真正可复用的 Clock 组件。它将设置自己的计时器并每秒更新一次。
我们可以从封装时钟的外观开始:
function Clock(props) { return ( <div> <h1>Hello, world!</h1> <h2>It is {props.date.toLocaleTimeString()}.</h2> </div> ); } function tick() { ReactDOM.render( <Clock date={new Date()} />, document.getElementById('root') ); } setInterval(tick, 1000);
然而,它忽略了关键的技术细节:Clock 组件需要设置计时器,并且需要每秒更新 UI。
理想情况下,我们希望只编写一次,便可以让 Clock 组件自我更新:
ReactDOM.render( <Clock />, document.getElementById('root') );
我们需要在 Clock 组件中 “state” 来实现这个。
State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
将组件转换成 class 组件
通过以下五步将 Clock 的组件转成 class 组件:
创建同名的 ,并且继承于 React.Component。
空的 render() 。
将体移动到 render() 之中。
在 render() 中使用 this.props 替换 props。
剩余的空声明。
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
现在 Clock 组件被定义为 class,而不是。
每次组件更新时 render 都会被,但只要在相同的 DOM 节点中渲染 <Clock /> ,就仅有 Clock 组件的 class 实例被创建使用。这就使得我们可以使用如 state 或生命周期等很多其他特性。
向 class 组件中局部的 state
我们通过以下三步将 date 从 props 移动到 state 中:
把 render() 中的 this.props.date 替换成 this.state.date :
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
,然后在该中为 this.state 赋初值:
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
通过以下方式将 props 传递到的构造中:
constructor(props) { super(props); this.state = {date: new Date()}; }
Class 组件应该始终使用 props 参数来的构造。
移除 <Clock /> 元素中的 date :
ReactDOM.render( <Clock />, document.getElementById('root') );
我们之后会将计时器相关的到组件中。
如下:
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
接下来,我们会设置 Clock 的计时器并每秒更新它。
将生命周期到 Class 中
在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。
当 Clock 组件第一次被渲染到 DOM 中的时候,就为其。这在 React 中被称为“挂载(mount)”。
同时,当 DOM 中 Clock 组件被的时候,应该。这在 React 中被称为“卸载(unmount)”。
我们可以为 class 组件声明一些特殊的,当组件挂载或卸载时就会去执行这些:
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { } componentWillUnmount() { } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
这些叫做“生命周期”。
componentDidMount() 会在组件已经被渲染到 DOM 中后运行,所以,最好设置计时器:
componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); }
接下来把计时器的 ID 保存在 this 之中(this.timerID)。
尽管 this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是其实你可以向 class 中随意不参与数据流(比如计时器 ID)的额外字段。
我们会在 componentWillUnmount() 生命周期中清除计时器:
componentWillUnmount() { clearInterval(this.timerID); }
最后,我们会实现叫 tick() 的,Clock 组件每秒都会它。
使用 this.setState() 来时刻更新组件 state:
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
现在时钟每秒都会刷新。
让我们来概括一下发生了什么和这些的顺序:
当 <Clock /> 被传给 ReactDOM.render()的时候,React 会 Clock 组件的构造。因为 Clock 需要当前的时间,所以它会用包含当前时对象来初始化 this.state。我们会在之后更新 state。
之后 React 会组件的 render() 。这就是 React 确定该在上展示什么的方式。然后 React 更新 DOM 来匹配 Clock 渲染的。
当 Clock 的被插入到 DOM 中后,React 就会 ComponentDidMount() 生命周期。个中,Clock 组件向浏览器请求设置计时器来每秒一次组件的 tick() 。
浏览器每秒都会一次 tick() 。 之中,Clock 组件会通过 setState()来计划进行一次 UI 更新。得益于 setState() 的,React 能够知道 state 已经改变了,然后会重新 render() 来确定上该什么。这一次,render() 中的 this.state.date 就不一样了,如此以来就会渲染更新过的时间。React 也会相应的更新 DOM。
一旦 Clock 组件从 DOM 中被移除,React 就会 componentWillUnmount() 生命周期,这样计时器就停止了。
正确地使用 State
关于 setState() 你应该了解三件事:
不要直接 State
例如,此不会重新渲染组件:
// Wrong this.state.comment = 'Hello';
而是应该使用 setState():
// Correct this.setState({comment: 'Hello'});
构造是唯一可以给 this.state 赋值的地方:
State 的更新可能是异步的
出于考虑,React 可能会把多个 setState() 合并成。
因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下状态。
例如,此可能会无法更新计数器:
// Wrong this.setState({ counter: this.state.counter + this.props.increment, });
要这个问题,可以让 setState() 接收而不是对象。
这个用上 state 作为第参数,将此次更新被应用时的 props 做为第二个参数:
// Correct this.setState((state, props) => ({ counter: state.counter + props.increment }));
上面使用了,不过使用普通的也同样可以:
// Correct this.setState(function(state, props) { return { counter: state.counter + props.increment }; });
State 的更新会被合并
当你 setState() 的时候,React 会把你提供的对象合并到当前的 state。
例如,你的 state 包含几个独立的变量:
constructor(props) { super(props); this.state = { posts: [], comments: [] }; }
然后你可以分别 setState() 来单独地更新它们:
componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); }
这里的合并是浅合并,所以 this.setState({comments}) 完整保留了 this.state.posts, 但是完全替换了 this.state.comments。
数据是向下流动的
不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是组件还是 class 组件。
这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。
组件可以选择把它的 state 作为 props 向下传递到它的子组件中:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
这对于组件同样适用:
<FormattedDate date={this.state.date} />
FormattedDate 组件会在其 props 中接收参数 date,但是组件本身无法知道它是来自于 Clock 的 state,或是 Clock 的 props,还是手动输入的:
function FormattedDate(props) { return <h2>It is {props.date.toLocaleTimeString()}.</h2>; }
这通常会被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
如果你把以组件构成的树想象成 props 的数据瀑布的话,那么每组件的 state 就像是在任意一点上给瀑布额外的水源,但是它只能向下流动。
为了证明每个组件都是真正独立的,我们可以创建渲染三个 Clock 的 App 组件:
function App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> ); } ReactDOM.render( <App />, document.getElementById('root') );
每个 Clock 组件都会单独设置它自己的计时器并且更新它。
在 React 应用中,组件是有状态组件还是无状态组件属于组件实现的细节,它可能会随着时推移而改变。你可以在有状态的组件中使用无状态的组件,反之亦然。