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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容