React状态(State)和生命周期

主要记录React的简单使用,便于查阅。

  • 引入react库
  • 组件(Components)
  • 状态(State)
  • 生命周期

一、引入react库

三种引入方式,分别如下:

  • 用<script>标签加载react文件
  • 在npm下使用ES6(推荐)
    有node环境,如果没有node环境,先安装nodejs,在nodejs官网https://nodejs.org/en/下载需要的版本,进行安装。
    然后在项目中安装react,npm install react --save-dev
    最后在文件中引入react,import React from 'react'
  • 在npm下使用ES5
    同样需要node环境,安装react,同上
    最后在文件中引入react,var React = require('react')

引入react后,就会有React这个全局变量,React是React库的入口,顶级API都在React这个全局变量上,接下来就就可以使用React的API编写代码了。


二、组件(Components)

组件可以将用户界面分成独立的,可复用的小部件,并且可以对每个部件进行单独的设计。
React可以将组件定义为类或函数,定义为类组件会提供更多功能,所以常定义为类组件,要定义组件类,需要扩展React.Component,如下:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

注:必须在React.Component子类中定义render()方法。


三、状态(State)和生命周期

React把组件看成是一个状态机,通过与用户的交互,实现不同状态,然后渲染UI,让用户界面和数据保持一致。
状态是私有的,完全受控于当前组件。
React里,只需要更新组件的state,然后根据新的state重新渲染用户界面(不用操作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>
    );
  }
}

如何定义State

State必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变,都可以从State的变化中反映出来;同时,State还必须是代表一个组件UI呈现的最小状态集,即State中的所有状态都是用于反映组件UI的变化,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态。

组件中用到的一个变量是不是应该作为组件State,可以通过下面的4条依据进行判断:

  • 这个变量是否是通过Props从父组件中获取?如果是,那么它不是一个状态。
  • 这个变量是否在组件的整个生命周期中都保持不变?如果是,那么它不是一个状态。
  • 这个变量是否可以通过其他状态(State)或者属性(Props)计算得到?如果是,那么它不是一个状态。
  • 这个变量是否在组件的render方法中使用?如果不是,那么它不是一个状态。这种情况下,这个变量更适合定义为组件的一个普通属性,例如组件中用到的定时器,就应该直接定义为this.timer,而不是this.state.timer。
  • 注意:不是组件中用到的所有变量都是组件的状态!当存在多个组件共同依赖一个状态时,一般的做法是状态上移,将这个状态放到这几个组件的公共父组件中。

如何使用state

1、 在构造函数(constructor)中初始化state
2、在render中使用state的值
3、用setState修改state,从而触发DOM重新渲染
注意:不要直接修改state,这样并不会重新触发render,如:this.state.date = '2018年7月8日'
用setState修改,如:this.setState({date: ' 2018年7月8日'})
4、 setState方法是异步的
调用setState时,组件state不会立即改变,只是把要修改的状态放入事件队列中,react会优化真正的执行时机,并且处于本身的性能原因,可能会对此setState的状态修改合并成一次状态修改。
因此不要依靠当前的state计算下一个state,因为当真正执行状态修改时,依赖的this.state并不能保证是最新的state,因为react本身会把多次state合并成一次,这时this.state可能还是setState前的state。
如果需要使用当前state计算新的state,正确用法:

//preState: 当前的最新状态的前一个状态
//props: 当前最新的props
this.setState((preState,props)=>({counter:preState.quantity+1}))

如果要在setState方法后直接获取更新后的state值,需要在setState中传入第二个参数,传入一个回调函数,正确写法为:

this.setState({date: ' 2018年7月8日'},  () => {console.log(this.state.date)})

5、state的更新是一个合并的过程
当调用setState()修改组件状态时,只需要传入发生变化的state,不是完整的state,因为组件state的更新是一个合并的过程,如:一个组件的状态为:

this.state({
title: 'React',
content: 'React is an wondeful JS library'
})

当只需要修改title时,只需要将修改的title传给setState即可:

this.setState({title: 'ReactJs'})

react会合并最新的title到原来的状态,同时保留原来的content。
6、state与不可变对象
React官方把state当成不可变对象,一方面是直接修改this.state组件不会重新render,通过setState修改state才会render;另一方面,state中包含的所有状态都应该是不可变对象。
当state当中的某一个状态发生变化时,应该重新创建这个状态对象,不是直接修改原来的state状态。
状态类型可以分为三种情况:

  • 状态类型为不可变类型(number, string, boolean, null, undefined)
    状态为不可变类型时,直接给要修改的状态赋新值即可。
  • 状态类型为数组
    假如有一个数组类型的状态books,当想给books中增加一本书时,可以使用数组的concat方法或者es6的扩展运算符
//方法一:使用preState,concat创建新数组
this.setState((preState) => ({
  books: preState.books.concat(['React'])
}))
//方法二:使用ES6扩展运算符
this.setState(preState => ({
  books: [...preState, 'React']
}))

当从books中截取部分元素作为新状态时,可以使用数组的slice方法:

this.setState(preState => ({
  books: preState.books.slice(1, 3)
}))

当从books中过滤部分元素作为新状态时,可以使用filter方法:

 this.setState(preState => ({
  books: preState.books.filter(item => item !== 'React')
}))
  • 状态的类型是普通对象
//使用es6的Object.assign()方法
 this.setState(preState => {
  onwer: Object.assign({}, preState.onwer, {name: 'Jason'})
})

//使用对象扩展运算符
 this.setState(preState => {
  owner: {...preState.owner, name: ''Jason}
})

注意:创建新的状态对象关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。可以使用一些Immutable的JS库,如Immutable.js,实现类似的效果。

为什么React推荐组件的状态是不可变对象?

一方面是因为不可变对象方便管理和调试,另一方面是出于性能考虑,当对象组件状态都是不可变对象时,在组件的shouldComponentUpdate方法中,仅需要比较状态的引用就可以判断状态是否真的改变,从而避免不必要的render调用


四、生命周期

React定义了组件的生命周期,主要分为三个过程:

  • 挂载过程(Mount),组件第一次在DOM树渲染的过程
  • 更新过程(Update),组件被重新渲染的过程
  • 卸载过程(Unmount),组件从DOM树中删除的过程
    执行这3个过程的调用函数就是声明周期函数。

挂载过程
该过程会依次调用如下函数:

  • constructor():ES6类的构造函数,接收两个参数props, context,可以获得父组件传下来的props(为了初始化state和绑定this)
  • componentWillMount():在组件被挂载前调用,只执行一次
  • render():渲染组件
  • componentDidMount():组件挂载后调用,这时已经生成了真实的DOM节点,只执行一次,一般在这里发送异步请求,操作DOM

更新过程
当组件的props或者state改变时会触发组件的更新过程
该过程会依次执行如下函数:

  • componentWillReceiveProps(nextProps):在接受父组件改变后的props需要重新渲染组件时用到的比较多,初始化时不调用
  • shouldComponentUpdate(nextProps,nextState):判断组件是否应该重新渲染,默认是true,当props或者state改变时调用(父组件重新渲染),初始化时不调用,返回boolean。true表示继续执行render方法,false表示跳过本次渲染。在这里可以优化react性能。
  • componentWillUpdate(nextProps,nextState):shouldComponentUpdate返回true后,componentWillUpdate会被调用

卸载过程
componentWillUnmount:将组件从DOM树移除,防止内存溢出

测试生命周期执行顺序:

class App extends React.Component {
    constructor(props){
        super(props)
        console.log("---初始化组件---")
    }
    componentWillMount(){
        console.log("---组件挂载前---")
    }
    componentDidMount(){
        console.log("---组件挂载后---")
    }
    componentWillReceiveProps(nextProps){
        console.log("---父组件重新渲染---")
    }
    shouldComponentUpdate(nextProps,nextState){
        console.log("---组件接受到重绘状态---")
        if(this.props != nextProps || this.state != nextState)
        return true
    }
    render() {
        console.log("---组件渲染---")
    }
}

参考文档:
http://www.css88.com/react/docs/getting-started.html
https://www.jianshu.com/p/c6257cbef1b1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容