[转] 深刻理解React 组件状态(State)

React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据。javascript

一. 如何定义State

定义一个合适的State,是正确建立组件的第一步。State必须能表明一个组件UI呈现的完整状态集,即组件的任何UI改变,均可以从State的变化中反映出来;同时,State还必须是表明一个组件UI呈现的最小状态集,即State中的全部状态都是用于反映组件UI的变化,没有任何多余的状态,也不须要经过其余状态计算而来的中间状态。css

组件中用到的一个变量是否是应该做为组件State,能够经过下面的4条依据进行判断:html

  1. 这个变量是不是经过Props从父组件中获取?若是是,那么它不是一个状态。
  2. 这个变量是否在组件的整个生命周期中都保持不变?若是是,那么它不是一个状态。
  3. 这个变量是否能够经过其余状态(State)或者属性(Props)计算获得?若是是,那么它不是一个状态。
  4. 这个变量是否在组件的render方法中使用?若是不是,那么它不是一个状态。这种状况下,这个变量更适合定义为组件的一个普通属性,例如组件中用到的定时器,就应该直接定义为this.timer,而不是this.state.timer。

请务必牢记,并非组件中用到的全部变量都是组件的状态!当存在多个组件共同依赖一个状态时,通常的作法是状态上移,将这个状态放到这几个组件的公共父组件中。java

二. State 与 Props 区别

除了State, 组件的Props也是和组件的UI有关的。他们之间的主要区别是:State是可变的,是组件内部维护的一组用于反映组件UI变化的状态集合;而Props对于使用它的组件来讲,是只读的,要想修改Props,只能经过该组件的父组件修改。在组件状态上移的场景中,父组件正是经过子组件的Props, 传递给子组件其所须要的状态。git

三. 如何正确修改State

1.不能直接修改State。

直接修改state,组件并不会从新重发render。例如:github

// 错误 this.state.title = 'React'; 

正确的修改方式是使用setState():redux

// 正确 this.setState({title: 'React'}); 

2. State 的更新是异步的。

调用setState,组件的state并不会当即改变,setState只是把要修改的状态放入一个队列中,React会优化真正的执行时机,而且React会出于性能缘由,可能会将屡次setState的状态修改合并成一次状态修改。因此不要依赖当前的State,计算下个State。当真正执行状态修改时,依赖的this.state并不能保证是最新的State,由于React会把屡次State的修改合并成一次,这时,this.state将仍是这几回State修改前的State。另外须要注意的事,一样不能依赖当前的Props计算下个状态,由于Props通常也是从父组件的State中获取,依然没法肯定在组件状态更新时的值。数组

举个例子,对于一个电商类应用,在咱们的购物车中,当咱们点击一次购买数量按钮,购买的数量就会加1,若是咱们连续点击了两次按钮,就会连续调用两次this.setState({quantity: this.state.quantity + 1}),在React合并屡次修改成一次的状况下,至关于等价执行了以下代码:ecmascript

Object.assign( previousState, {quantity: this.state.quantity + 1}, {quantity: this.state.quantity + 1} ) 

因而乎,后面的操做覆盖掉了前面的操做,最终购买的数量只增长了1个。异步

若是你真的有这样的需求,可使用另外一个接收一个函数做为参数的setState,这个函数有两个参数,第一个是当前最新状态(本次组件状态修改后的状态)的前一个状态preState(本次组件状态修改前的状态),第二个参数是当前最新的属性props。以下所示:

// 正确 this.setState((preState, props) => ({ counter: preState.quantity + 1; })) 

3. State 的更新是一个浅合并(Shallow Merge)的过程。

当调用setState修改组件状态时,只须要传入发生改变的State,而不是组件完整的State,由于组件State的更新是一个浅合并(Shallow Merge)的过程。例如,一个组件的状态为:

this.state = { title : 'React', content : 'React is an wonderful JS library!' } 

当只须要修改状态title时,只须要将修改后的title传给setState

this.setState({title: 'Reactjs'}); 

React会合并新的title到原来的组件状态中,同时保留原有的状态content,合并后的State为:

{
  title : 'Reactjs', content : 'React is an wonderful JS library!' } 

四. State与Immutable

React官方建议把State看成是不可变对象,一方面是若是直接修改this.state,组件并不会从新render;另外一方面State中包含的全部状态都应该是不可变对象。当State中的某个状态发生变化,咱们应该从新建立这个状态对象,而不是直接修改原来的状态。那么,当状态发生变化时,如何建立新的状态呢?根据状态的类型,能够分红三种状况:

1. 状态的类型是不可变类型(数字,字符串,布尔值,null, undefined)

这种状况最简单,由于状态是不可变类型,直接给要修改的状态赋一个新值便可。如要修改count(数字类型)、title(字符串类型)、success(布尔类型)三个状态:

this.setState({ count: 1, title: 'Redux', success: true }) 

2. 状态的类型是数组

若有一个数组类型的状态books,当向books中增长一本书时,使用数组的concat方法或ES6的数组扩展语法(spread syntax):

// 方法一:将state先赋值给另外的变量,而后使用concat建立新数组 var books = this.state.books; this.setState({ books: books.concat(['React Guide']); }) // 方法二:使用preState、concat建立新数组 this.setState(preState => ({ books: preState.books.concat(['React Guide']); })) // 方法三:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, 'React Guide']; })) 

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

// 方法一:将state先赋值给另外的变量,而后使用slice建立新数组 var books = this.state.books; this.setState({ books: books.slice(1,3); }) // 方法二:使用preState、slice建立新数组 this.setState(preState => ({ books: preState.books.slice(1,3); })) 

当从books中过滤部分元素后,做为新状态时,使用数组的filter方法:

// 方法一:将state先赋值给另外的变量,而后使用filter建立新数组 var books = this.state.books; this.setState({ books: books.filter(item => { return item != 'React'; }); }) // 方法二:使用preState、filter建立新数组 this.setState(preState => ({ books: preState.books.filter(item => { return item != 'React'; }); })) 

注意不要使用push、pop、shift、unshift、splice等方法修改数组类型的状态,由于这些方法都是在原数组的基础上修改,而concat、slice、filter会返回一个新的数组。

3. 状态的类型是普通对象(不包含字符串、数组)

3.1 使用ES6 的Object.assgin方法

// 方法一:将state先赋值给另外的变量,而后使用Object.assign建立新对象 var owner = this.state.owner; this.setState({ owner: Object.assign({}, owner, {name: 'Jason'}); }) // 方法二:使用preState、Object.assign建立新对象 this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: 'Jason'}); })) 

3.2 使用对象扩展语法(object spread properties

// 方法一:将state先赋值给另外的变量,而后使用对象扩展语法建立新对象 var owner = this.state.owner; this.setState({ owner: {...owner, name: 'Jason'}; }) // 方法二:使用preState、对象扩展语法建立新对象 this.setState(preState => ({ owner: {...preState.owner, name: 'Jason'}; })) 

总结一下,建立新的状态对象的关键是,避免使用会直接修改原对象的方法,而是使用能够返回一个新对象的方法。固然,也可使用一些Immutable的JS库,如Immutable.js,实现相似的效果。

那么,为何React推荐组件的状态是不可变对象呢?一方面是由于不可变对象方便管理和调试,了解更多可参考这里;另外一方面是出于性能考虑,当对象组件状态都是不可变对象时,咱们在组件的shouldComponentUpdate方法中,仅须要比较状态的引用就能够判断状态是否真的改变,从而避免没必要要的render调用。当咱们使用React 提供的PureComponent时,更是要保证组件状态是不可变对象,不然在组件的shouldComponentUpdate方法中,状态比较就可能出现错误,由于PureComponent执行的是浅比较(比较对象的引用)。

做者:苍山沭河连接:https://www.jianshu.com/p/c6257cbef1b1來源:简书著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。
相关文章
相关标签/搜索