5.React中文之状态和生命周期

思考前面小节的一个时钟滴答例子。

到目前为止我们仅学习了更新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)

在CodePen中试试吧。

在这节中,我们将学习如何真正地复用和封装Clock组件。它将建立自己的计时器和每秒中更新自己。

我们从如何封装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);

在CodePen中试试吧。

然而它遗漏了一个至关重要的需求:Clock建立计时器每秒钟更新UI是Clock实现细节的事实。

理想地,我们想要写一次,Clock更新自己。

ReactDOM.render(

    <Clock />,

    document.getElementById('root')

);

为了实现这个,我们需要给Clock组件加“state”。

State和props是相似的,但是它是私有的,完全被组件控制的。

我们之前提到过被定义成类的组件有一些附加特性。

Local state is exactly that:a feature available  only to classes.

本地State正是如此:仅适用类的特性。

将函数转换为类

用以下五步,你可以将像Clock的函数转换为类:

1.Create an ES6 class with the same name that extends React.Component.

用相同的名字创建一个ES6类继承React.Component.

2.Add a single empty method to it called render().

添加一个render()空方法。

3.Move the body of the function into the render() method.

将函数体放在render()方法中。

4.Replace props with this.props in the render() body.

在render()方法体中用this.props替换掉props.

5.Delete the remaining empty function declaration.

删除剩下的空函数声明。

class Clock extends React.Component{

        render(){

              return(

                    <div>

                        <h1>Hello,world!</h1>

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

                   </div>

              ); 

       }

}

在CodePen上试试吧。

Clock现在被定义为一个类而不是函数。

这让我们可以使用像本地State和生命周期挂钩的特性。

添加本地State到一个类。

我们用三步将date从props移至steps:

1)Replace this.props.date with this.state.date in the render()method:

在render()方法中用this.state.date替换this.props.date.

class Clock extends React.Component{

      render(){

           return(

                <div>

                      <h1>Hello,world</h1>

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

               </div>

           );   

      }

}

2)Add a class constructor that assigns the initial this.state:

添加一个类构造函数,指定初始的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()};

}

类组件总会调用带有props的构造函数。

3)Remove the date prop from the <Clock /> element:

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')

);

在CodePen试试吧。

接下来,我们将给Clock创建计时器并每秒钟更新一下自己。

添加生命周期到类中

在有许多组件的应用中,组件被销魂腾出的空间是很重要的。

一旦Clock组件被渲染到DOM中,我们就创建一个计时器。在React称之为"mounting"。

一旦由Clock组件产生的DOM被移除,我们也想清楚计时器。在React中称之为“unmounting”。

当组件mount和unmount时,我们可以在组件类的特定方法中运行一些代码:

class Clock extends React.Component{

       constructor(props){

            super(props);

            this.state={date:new Date()};

       }

       componnetDidMount(){

        }

        componentWillUnmount(){

       }

       render(){

            return(

                  <div>

                        <h1>Hello,world!</h1>

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

                  </div>

            ); 

      }

}

这些方法被称为“lifecycle hooks”。

组件输出被渲染至DOM后,componentDidMount()运行。这是一个放置计时器的好地方:

componentDidMount(){

   this.timerID=setInterval(

      ()=>this.tick(),1000 

   );

}

注意我们怎样正确地将timer ID 保存在this上。

While this.props is set up by React itself and this.state has a special meaning,you are free to add additional fields to the class manually if you need to store something that is not used for the visual output.

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')

);

在CodePen上试试吧。

现在时钟每秒钟可以滴答一次了。

我们快速扼要重述下正在进行的事情和方法被调用的顺序:

1)当<Clock />被传送给ReactDOM.render()时,React调用Clock组件的构造函数。因为Clock需要展示当前时间,它需要用一个包含当前时间的对象初始化this.state。我们以后将更新这个状态。

2)然后React调用Clock组件的render()方法。这是React怎样得知什么应该显示在屏幕上。然后React更新DOM匹配Clock的render的输出。

3)当Clock输出被插入到DOM时,React调用componentDidMount()生命循环钩。在这个方法中,Clock组件要求浏览器建立一个计时器每一秒调一次tick()

4)浏览器每秒钟调用一次tick()方法。在这个方法中,Clock组件计划调用包含当前时间对象的setState()方法更新一次UI。由于setState()方法调用,React知道状态已经发生变化,再次调用render()方法得知什么应该显示在屏幕上。这次,render()方法的this.state.date将是不同的,因此render输出将包含更新时间。React相应更新DOM。

5)如果Clock组件从DOM中移除,React调用componentWillUnmount()生命循环钩子因此计时器被停止。

正确使用状态

关于setState()有三个你应该知道的事情。

不直接修改状态

例如,不重复渲染组件:

this.state.comment='Hello';//wrong

相反,使用setState():

this.setState({comment:'Hello'});

The only place where you can assign this.state is the constructor.

你可以声明this.state的唯一地方是构造函数。

状态更新可能是异步。

React可能将多个setState()批量更新。

因为this.props和this.state可能被异步更新,所以你不应该依赖他们的值去计算下一个状态。

例如,以下代码更新计数器可能会失败:

//Wrong

this.setState({

    counter:this.state.counter +this.props.increment

});

修正以上代码,使用setState()的第二种方式:接受一个函数而不是对象。这个函数将接受以前的状态作为第一个参数,在更新的时候属性会被作为第二个参数:

this.setState(

     (prevState,props)=>({counter:prevState.counter+props.increment})

);

以上我们使用一个箭头函数,但是它也可以传入标准函数:

this.setState(function(prevState,props){

        return {

             counter:prevState.counter +props.increment 

       };

});

State更新被合并

当你调用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

              ); 

        }

    );

}

The merging is shallow,so this.setState({comments}) leaves this.state.posts intact,but completely replaces this.state.comments.

合并是浅合并,因此this.setState({comments})保持this.state.posts原封不动,但是可以完全替换this.state.comments.

数据下流

不管是父组件还是子组件都无法知晓某个组件是有状态还是没有状态,他们也不关心它是作为函数被定义的,还是作为类被定义的。

这是因为state经常被称为本地的或封装的。除了拥有并可设置它的组件外,其他任何组件都是不可访问的。

A component may choose to pass its state down as props to its child components:

一个组件可以选择将它的状态作为属性传递给它的子组件:

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

这也适用在用户定义组件。

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

FormattedDate组件在属性中接受date,并不清楚它来自Clock的state,还是Clock的props,还是手动输入的:

function FormattedDate(props){

   return <h2>It is {props.date.toLocaleTimeString()}.</h2>

}

在CodePen上试试吧。

这普遍被称为“自上而下‘或”单向“数据流。任何状态总是被一些特定的组件所拥有,由state衍生的任何数据或UI仅影响组件树下的他们。

如果你将组件树想象成属性瀑布,每个组件状态就像一个新增的水资源,将它随意加在一个位置但是也向下。

为了展示所有组件都是真实孤立的,我们创建一个渲染 <Clock>树的App组件:

function App(){

     return(

          <div>

              <Clock />

              <Clock />

               <Clock />

         </div>

     );

}

ReactDOM.render(

     <App  />,

      document.getElementById('root')

);

在CodePen试试吧。

每个Clock建立自己的计时器并独立更新。

在React apps中,组件是否有状态被认为是组件可能随时间变化的实现细节。你可以在有状态的组件中使用无状态的组件,反之亦然。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值