State和生命周期
考虑前面部分中的滴答时钟示例(第三章)。
到目前为止,我们只学习了一种更新UI的方法。
我们调用ReactDOM.render()
来改变渲染输出:
import React from 'react'; import ReactDOM from 'react-dom'; function tick() { const element = ( <div> <h1>Hell world</h1> <h2>It is {new Date().toLocaleTimeString()}</h2> </div> ); ReactDOM.render( element,document.getElementById('root') ); } setInterval(tick,1000);
在本节中,我们将学习如何使Clock
组件真正可重用和封装。 它将设置自己的计时器并每秒更新一次。
我们可以从封装时钟的外观开始:
import React from 'react'; import ReactDOM from 'react-dom'; 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);
然而,它缺少了一个关键要求:时钟设置一个定时器和每秒更新UI的事实应该是时钟的实现细节。
理想情况下,我们要写这一次,并由时钟本身来更新时间:
ReactDOM.render( <Clock />,document.getElementById('root') );
要实现这一点,我们需要添加“state”到时钟组件。
state类似于props,但它是私有的,完全由组件控制。
我们之前提到,定义为类组件具有一些附加功能。 内部state就是:一个只有类组件可用的功能。
将函数形式组件改为类形式组件
您可以通过五个步骤将功能组件(如Clock)转换为类组件 :
创建一个与扩展
React.Component
相同名称的ES6类。在
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
现在已经重新定义为类组件而不是之前的功能组件了。
这使我们可以使用额外的功能,如内部state和生命周期钩子。
向类组件中添加state
我们将分为三个步骤把date
从props
移动到state
:
1)在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> ); } }
2)添加一个赋值初始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.toLocalTimeString()}.</h2> </div> ); } }
注意我们如何将props传递给基类的构造函数:
constructor(props) { super(props); this.state = {date: new Date()}; }
3)从<Clock />
元素中删除date
prop:
ReactDOM.render( <Clock />,document.getElementById('root') );
import React from 'react'; import ReactDOM from 'react-dom'; 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') );
接下来,我们将使时钟设置自己的定时器,并每秒更新一次。
向类中添加声明周期方法
在具有许多组件的应用程序中,释放组件在销毁时占用的资源非常重要。
我们想要在第一次将时钟渲染到DOM时设置一个计时器。 这在React中称为“安装(mounting)”
。
我们还想清除定时器,当时钟产生的DOM被删除。 这在React中称为“卸载(unmounting)"
。
我们可以在组件类上声明特殊方法,以便在组件装入和卸载时运行一些代码:
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.props
是由React本身设置的,并且this.state
有一个特殊的含义,如果你需要存储不用于视觉输出的东西,你可以手动地添加额外的字段到类中。
如果你不使用render()
中的东西,它不应该放置在state
中。
我们将拆除componentWillUnmount()
生命周期钩子中的计时器:
componentWillUnmount() { clearInterval(this.timerID); }
最后,我们将实现每秒运行的tick()
方法。
它将使用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') );
现在时钟每秒钟都在滴答地走,棒不棒。。。。
1)当将
<Clock />
传递给ReactDOM.render()时,React调用Clock组件的构造函数。由于Clock需要显示当前时间,它使用包括当前时间的对象初始化this.state
。我们稍后将更新此state。2)React然后调用Clock组件的
render()
方法。这是React如何学习应该在屏幕上显示什么。 React然后更新DOM以匹配时钟的渲染输出。3)当时钟输出插入到DOM中时,React调用
componentDidMount()
生命周期钩子。在其中,时钟组件要求浏览器设置一个定时器,每秒调用tick()
一次。4)每秒钟浏览器调用
tick()
方法。在其中,Clock组件通过调用setState()
和包含当前时间的对象来调度UI更新。由于setState()
调用,React知道state已更改,并再次调用render()
方法来了解屏幕上应该显示的内容。这个时候,render()
方法中的this.state.date
将会不同,因此渲染输出将包括更新的时间。 React相应地更新DOM。5)如果时钟组件从DOM中被移除,React将调用
componentWillUnmount()
生命周期钩子,因此定时器停止。
正确使用state
关于setState()
你应该了解三件事情:
不要直接修改state
例如,这将不会重新渲染组件:
// 这是错误的 this.state.comment = 'hello';
应该使用setState()
代替:
// 这是正确的 this.setState({comment: 'hello'});
唯一可以分配this.state
的地方是构造函数。
state更新可能是异步的
React可以将多个setState()
用批处理为单个更新以实现较高的性能。
因为this.props
和this.state
可能是异步更新的,你不应该依赖它们的值来计算下一个state。
例如,此代码可能无法更新计数器:
// 这是错误的 this.setState({ counter: this.state.counter + this.props.increment,});
要解决它,应该使用回调函数而不是对象来调用setState()
。 回调函数将接收先前的state作为第一个参数,并将应用更新时的props
作为第二个参数:
// 这是正确的 this.setState((prevState,props) => ({ counter: prevState.counter + props.increment }));
// 这同样也是正确的,将剪头函数改为普通函数 this.setState(function(prevState,props) { return { counter: prevState.counter + prps.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
而已。
数据是向下流动的
父组件和子组件都不能知道某个组件是有State的还是无State的,并且它们不应该关心它是否为功能组件或类组件。
这就是为什么State通常被设置为局部变量或封装到组件内部。 除了拥有和设置它的组件之外的其他任何组件都不能访问它。
组件可以选择将其state作为props传递给其子组件:
<h2>Is is {this.state.date.toLocaleTimeString()}.</h2>
这也适用于用户定义的组件:
<formattedDate date={this.state.data} />
FormattedDate
组件将在其props
中接收date
,并且不知道它是来自时钟的state
,props
还是手动输入
:
function FormattedData(props) { return <h2>Is is {props.date.toLocaleTimeString()}.</h2>; }
这通常被称为“自顶向下”
或“单向”
数据流。 任何state总是由一些特定组件拥有,并且从该state派生的任何数据或UI只能影响树中的“下面”组件。
如果你想象一个组件树作为props的瀑布流,每个组件的state就像一个额外的水源,它可以在任意点连接它,但也向下流。
为了显示所有组件都是真正隔离的,我们可以创建一个App组件来渲染三个<Clock>
:
function App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> ); } ReactDOM.render( <App />,document.getElementById('root') );
每个时钟设置自己的定时器并独立更新。在React应用程序中,组件是有状态还是无状态被视为可能随时间更改的组件的实现细节。 您可以在有状态组件内使用无状态组件,反之亦然。