State and Lifecycle(React 状态和生命周期)

回顾一下我们实现的一个关于时钟的例子。

我们仅仅是知道如何用一种方法更新用户界面。

我们仅仅是使用了ReactDOM.render()每一秒重新渲染了页面而已:

  1. function tick(){
  2. const element = (
  3. <div>
  4. <h1>Hello,world!</h1>
  5. <h2>It is {new Date().toLocaleTimeString()}</h2>
  6. </div>
  7. );
  8. ReactDOM.render(
  9. element,
  10. document.getElementById('root')
  11. );
  12. };
  13. setInterval(tick,1000);

但是在本章节,我们将会以另外一种方法实现时钟Component——真正实现了可复用和封装,它将会实现自己的计时器,同时每一秒自己去更新用户界面。

我们可以先把代码改成这样:

  1. function Clock(props){
  2. return (
  3. <div>
  4. <h1>Hello,world!</h1>
  5. <h2>It is {props.date.toLocaleTimeString()}.</h2>
  6. </div>
  7. );
  8. };
  9. function tick(){
  10. ReactDOM.render(
  11. <Clock date={new Date()}/>,
  12. document.getElementById('root')
  13. );
  14. };
  15. setInterval(tick,1000);

我们从原先的直接渲染一个时钟到抽象处一个Clock组件出来,这样就会在多个地方实现使用Clock组件了,是不是省去了很多功夫呢!

但是,我们回过来看上面的代码,其实它漏掉了一个最大的需求,就是这个Clock组件,完全可以自己在内部实现自己的定时器而不用我们周期地调用tick方法来实现更新用户界面。

正常情况下,我们只需要写下下面的代码,就能实现上面的功能:

  1. ReactDOM.render(
  2. <Clock />,
  3. document.getElementById('root')
  4. );

那怎么才能实现呢?这时候就需要在Clock组件中用到我们的“state”了。

state和props类似,但是它对于一个组件来说,完全是私有的,并且全权受组件控制!

我们前面提到过,在用“类式”方式写一个组件的时候,可以写一些附加的属性,本地state就包含在这些可以定义的附加属性里面。

Converting a Function to a Class(从函数式方式转变成用类式方式写一个组件)

用“类式”方式实现上面的Clock组件,你只需要按下面五步操作:

_ 1、创建一个es6 class,类名就叫Clock,同时Clock继承React.Component;

_ 2、添加一个空方法render();

_ 3、把函数式方式函数体里面的内容添加到render函数体里面;

_ 4、同时在函数体里面,把原先的props改成this.props;

_ 5、再删除不需要的部分。

经过上面五步,你的Clock组件将会变成这样:

  1. class Clock extends React.Component{
  2. render(){
  3. return (
  4. <div>
  5. <h1>Hello,world!</h1>
  6. <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
  7. </div>
  8. );
  9. };
  10. };

现在我们的Clock组件就变成了一个“类式”方式构建的了。

让我们在为组件添加一些附加的特性,比如本地state、生命周期钩子等等。

Adding Local State to a Class

首先我们需要删除从props上获取date,只需要三步:

  • 1、在render方法里面移除this.props.date,取而代之为this.state.date:
  1. class Clock extends React.Component{
  2. render(){
  3. return (
  4. <div>
  5. <h1>Hello,world!</h1>
  6. <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
  7. </div>
  8. );
  9. };
  10. };
  1. class Clock extends React.Component{
  2. constructor(props){
  3. super(props);
  4. this.state = {date:new Date()};
  5. };
  6. render(){
  7. return (
  8. <div>
  9. <h1>Hello,world!</h1>
  10. <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
  11. </div>
  12. );
  13. };
  14. };

这里注意一下,我们是怎么传递props给我们的构造函数的,有兴趣的的看下es6 class,看过之后就能懂了!

  1. constructor(props){
  2. super(props);
  3. this.state = {date:new Date()};
  4. };

采用“类式”方式构建组件总是在构造函数里传递props参数。

  • 3、在中删除date属性。
  1. ReactDOM.render(
  2. <Clock />,
  3. document.getElementById('root')
  4. );

我们待会儿其内部实现计时器,目前来看,整体代码如下:

  1. class Clock extends React.Component{
  2. constructor(props){
  3. super(props);
  4. this.state = {date:new Date()};
  5. };
  6. render(){
  7. return (
  8. <div>
  9. <h1>Hello,world!</h1>
  10. <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
  11. </div>
  12. );
  13. };
  14. };
  15. ReactDOM.render(
  16. <Clock />,
  17. document.getElementById('root')
  18. );

现在,我们来实现Clock自己的计时器,并且自己每秒去更新自己。

Adding Lifecycle Methods to a Class(向类中添加生命周期方法)

在一个包含很多组件的app里面,一件非常重要的事情需要去做就是——当一个组件被删除的时候,需要释放它所占的系统资源空间。

我们将在任何时候Clock组件第一次被渲染到页面的时候实现设置一个计时器。在react中,一个组件出现在页面中,技术术语称作“mounting(挂载)”。

我们同时需要在任何时候清除这个计时器,只要Clock组件在页面上消失。这个技术术语在react中称作“unmounting(卸载)”。

我们可以在用“类式”方式构建组件的时候,添加一些方法,表明组件“卸载”和“挂载”时需要执行的一些操作,如下:

  1. class Clock extends React.Component{
  2. constructor(props){
  3. super(props);
  4. this.state = {date:new Date()};
  5. };
  6. //组件挂载时执行
  7. componentDidMount(){
  8. //代码省略
  9. };
  10. //组件卸载时执行
  11. componentWillUnmount(){
  12. //代码省略
  13. };
  14. render(){
  15. return (
  16. <div>
  17. <h1>Hello,world!</h1>
  18. <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
  19. </div>
  20. );
  21. };
  22. }

像上面的componentWillUnmount和componentDidMount方法,在react中,我们称之为“react生命周期钩子”。

componentDidMount方法在组件渲染到页面上之后调用,这个时候非常适合开启我们的计时器。

  1. componentDidMount(){
  2. this.timerID = setInterval(()=>{
  3. this.tick();
  4. },1000);
  5. };

注意,我们这里是怎么保存计时器返回的id值的。

我们知道this.props和this.state是由react自己创建的,但是this.state却又特殊的含义,你可以自由向this.state对象下添加一些你可能需要或者不需要的数据。

如果你要使用的数据不在render方法里面,那么不需要在state里面设置。

当componentWillUnmount调用的时候,我们将关闭计时器。如:

  1. componentWillUnmount(){
  2. clearInterval(this.timerID);
  3. };

接下来,我们将实现tick方法,它会每秒调用一次,并且会修改本地state。如:

  1. class Clock extends React.Component{
  2. constructor(props){
  3. super(props);
  4. this.state = {date:new Date()};
  5. this.tick = this.tick.bind(this);
  6. };
  7. render(){
  8. return (
  9. <div>
  10. <h1>Hello,world!</h1>
  11. <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
  12. </div>
  13. );
  14. };
  15. componentDidMount(){
  16. this.timerID = setInterval(()=>this.tick(),1000);
  17. };
  18. componentWillUnmount(){
  19. clearInterval(this.timerID);
  20. };
  21. tick(){
  22. this.setState({
  23. date:new Date()
  24. });
  25. };
  26. };
  27. ReactDOM.render(
  28. <Clock />,
  29. document.getElementById('root')
  30. );

现在,我们的代码完了,它会向时钟一样,每秒更新一次。

我们简单回顾一下,react做了什么,并且看一下依次调用了那些方法:

  • 1、当Clock组件被ReactDOM.render方法向页面渲染的时候,React会首先调用Clock类的构造方法,然后它知道Clock组件需要显示当前时间,那么它就会初始化this.state,并且将它设置成包含一个当前时间的对象。而后会更新this.state;

  • 2、当然调用Clock组件的render方法时候,react就会知道需要在页面上显示什么内容,那么react就会更新页面内容,这个内容就是Clock组件的render方法的输出;

  • 3、当Clock插入到页面的时候,React就会调用componentDidMount,从而React会请求浏览器启动一个计时器,以便用来每秒调用一次tick方法;

  • 4、当调用tick方法的时候,会调用this.setState方法,这个方法会把state的date属性更新会当前时间,得益于this.setState方法,React才知道state发生了改变,那么它重新调用一次Clock组件的render方法,那么此时的date变成了当前时间,React会有效的更新页面;

  • 5、只要Clock从DOM中移除,就会触发componentWillUnmount方法钩子,那么这个就会清除定时器。

Using State Correctly(正确使用state)

在使用state的时候,有三件事情你需要知道。

Do Not Modify State Directly (不要直接修改state)

例如,下面的代码就不会重新调用组件render方法:

  1. //Wrong
  2. this.state.comment = 'Hello';

你不应该直接修改this.state,而应该通过this.setState方法。如:

  1. //Correct
  2. this.setState({comment:'Hello'});

只有一个地方你可以直接修改,那就是组件构造函数里面,在其它地方凡是涉及到需要修改state的,都应该使用this.setStata。

State Updates May Be Asynchronous(state的更新是异步的)

React可能为了性能,一次更新一批state状态。

因为props和state的更新是异步的,所以你不能依靠它的前一个值算出下一个值。

例如,下面的代码就会失败:

  1. //Wrong
  2. this.setState({
  3. counter:this.state.counter + this.props.increment
  4. });

为了解决这个问题,我们可以使用另外一种格式的this.setState方法调用,我们通过传递一个回调函数而不是对象来解决。这个回调函数接受两个参数,分别是前一个state和当前组件的props,代码如下:

  1. //Correct
  2. this.setState((previousState,props)=>{
  3. return {counter:previousSate.counter + props.increment};
  4. });

我们上面使用了arrow function,但它可以使用正常的javascript函数。如:

  1. //Correct
  2. this.setState(function(previousState,props){
  3. return {counter:previousSate.counter + props.increment};
  4. });

State Updates are Merged(采用合并的方法更新state)

当我们调用setState方法的时候,react会合并一个对象到当前的state里面。

例如,你的state可能包含几个独立的值:

  1. constructor(props){
  2. super(props);
  3. this.state = {
  4. posts : [],
  5. comments : []
  6. };
  7. };

然后你可以通过分开调用setState方法来分别更新它们,如:

  1. componentDidMount(){
  2. fetchPosts().then(response=>{
  3. this.setState({
  4. posts:response.posts
  5. });
  6. });
  7. fetchComments().then(response=>{
  8. this.setState({
  9. comments:response.comments
  10. });
  11. });
  12. };

这个合并是简单执行的,就是说当更新comments的时候,它不会动posts,但却完整地改变comments。

The Data Flows Down(数据是向下流的)

在一个组合组件里,父组件和子组件都不知道这个组件的是富状态的还是少状态的,它们也不关心组件是用“函数式”还是“类式”构建的。

这也是为什么说state是本地或密封的。组件是不能访问另外一个组件的state,更不要说去设置或更改它。

一个父组件可以选择通过向下传递它的state作为子组件的props,如:

  1. <h2>It is {this.state.date.toLocaleTimeString()}.</h2>

也可以在用户定义的组件用:

  1. <FormattedDate date={this.state.date} />

这里,FormattedDate组件从它的props中接受date值,而不用管它是通过Clock的state或者Clock的props传递过来的,又或者是手写的:

  1. function FormattedDate(props){
  2. return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
  3. };

这通常称为“自上向下”或者“单向”的数据流。一个state只能被某个独立的组件所拥有,但这个state只能影响或者传递给组件树层中下一层的组件。

如果你把一个组件树的props看作一个瀑布流的话。每个组件的comment的state就像传统的水源一样,不管来自什么地方。但总是向下流的。

为了展示我们所说的所有的组件都是隔离的,我们可以创建一个App组件,它选了一个Clock三次:

  1. function App(props){
  2. return (
  3. <div>
  4. <Clock />
  5. <Clock />
  6. <Clock />
  7. </div>
  8. );
  9. };
  10. ReactDOM.render(
  11. <App />,
  12. document.getElementById('root')
  13. );

每个Clock都用自己的定时器,并且独立的更新自己。

在react程序里面,无论一个组件是富状态的还是少状态的,都被认为是一个合格的组件实现。你可以在一个富状态的组件里面引用一个少状态的组件,反之亦然。