这篇介绍 React 组件中状态和声明周期的概念。详情能够查看API参考 。javascript
思考前一部分中时钟的例子。渲染元素中,咱们仅学习了一种更新 UI 的方式。调用 ReactDOM.render() 改变渲染后的输出。html
function tick() { const element = ( <div> <h1>Hello, world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render( element, document.getElementById('root') ); }
在线尝试 java
这部分,咱们学习如何编写真正可复用的封装 Clock 组件。 它会设置本身的计时器每秒更新本身。react
咱们从封装 时钟的外层开始:segmentfault
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 的细节实现。浏览器
理想状况下咱们写一次让 Clock 更新自身:异步
ReactDOM.render( <Clock />, document.getElementById('root') );
实现这一需求咱们须要给 Clock 组件添加 "state".函数
State 很相似 props, 不一样的是它彻底私有由组件控制。post
转化一个相似 Clock 的函数组件为类组件须要五步:
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
[在线尝试]()
函数组件定义的 Clock 如今由类组件定义。
render() 方法会在每次更新时调用,可是只要咱们渲染 <Clock /> 到一样的 DOM 节点,就会使用 Clock 类的单一实例。这让咱们可使用如 local state 和 lifecycle 钩子等额外的特性。
三步把 date 从 props 移动到 state:
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
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 到基础构造器:
construct(props) { super(props); this.state = {date: new Date()}; }
类组件老是经过 props 调用基础构造器。
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 设置他本身的计时器每秒更新自身。
多个组件的应用中,当组件销毁时释放组件占用的资源很是重要。
咱们想 [设置一个计时器]() 不管什么时候 Clock 第一次被渲染到 DOM. React 中称之为 ”mounting".
咱们也想 [清楚一个计时器]() 不管什么时候 Clock 被DOM 移除。 React 中称之为 “unmounting".
当组件 mounts 和 unmounts 时咱们能够在组件类声明特殊的方法来运行。
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { } componentWillMount() { } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
这些方法称为 "lifecycle hooks(生命周期钩子)".
componentDidMount() 钩子在组件输出渲染至DOM 后运行。这个位置很适合创建一个计时器:
componentDidMount() { this.timerID = setInterval(() => this.tick(), 1000); }
注意咱们如何正确的将 计时器 ID 保存到 this.
当 React 设置 this.props this.state 有了特别的含义,你能够随意为类手动添加额外字段,若是你须要保存一些不参与数据流(好比 timerID)。
咱们在 componentWillUnmount() 生命周期钩子函数中去掉 计时器。
componentWillUnmount() { clearInterval(this.timerID); }
最后咱们,实现一个称为 tick() 的方法实现 Clock 组件每秒运行。
这会用到 this.setState() 来调度更新组件的本地状态:
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') );
如今时钟按秒运行。
快速的回顾下发生了什么还有方法调用顺序:
关于setState() 的三个须知:
例如,这样不会 从新渲染一个组件:
// Wrong this.state.comment = 'Hello';
应该使用 setState():
// Correct this.setState({comment: 'Hello'});
构造器是惟一可为 this.state 赋值的地方。
React 为了性能可能批量屡次 调用 setState() 一个单独的更新。
由于 this.props 和 this.state 可能异步更新,不该该依赖它们的值来计算下一个状态。
例如,以下代码可能更新计数器失败:
// Wrong this.setState({ counter: this.state.counter + this.props.increment, });
修复这个问题,使用 setState() 的第二种形式,接受函数而不是一个对象。这个函数接受以前的 state 做为第一个参数, 当时间更新时 props 做为第二个参数。
// Correct this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));
上面的例子中咱们使用了 [箭头函数](),但常规函数也是能够的。
// Correnct this.setState(function(prevState, props) { return { counter: prevState.counter + props.increment }; });
当调用 setState(), React会合并你提供给当前状态的对象。
例如, 你的状态可能包含多个独立的变量:
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.
不管子组件仍是父组件都没法知道一个特定的主键是有状态仍是无状态,并且他们也不该当关心它是用函数方式仍是类方式定义。
这是为何 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>; }
这个一般称为 "top-down(自上而下)" 或者 "unidirectional(单向)" 数据流。任何 state 总数被特定的组件所拥有,任何经过 state 传递的数据或 UI 都只能影响树形结构的下方组件。
你能够家乡组件树是一个 props 瀑布,每一个组件的状态就像一个额外的水源在随机点加入它同时向下流。
为展现全部组件真正独立,咱们建立一个 App 组件 渲染三个 <Clock />:
function App() {
return (
<div> <Clock /> <Clock /> <Clock /> </div> );
}
ReactDOM.render() {
<App />,
document.getElementById('root')
[在线尝试]() 每一个 **Clock** 设置他本身的计时器独立更新他们。 React 应用中,不管一个组件是有状态仍是无状态都被当作一个组件可随时间改变的实现细节。 你可在有状态组件中使用无状态组件,反之亦然。