再谈react的状态机state

1,422 阅读4分钟

在react中可以通过this.state.{属性}的方式直接获取state,但是当我们修改state的时候,往往有许多的坑需要注意。

三种常见的陷阱

  • 不能直接修改state

组件修改state,并不会重新触发render。列如:

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

正确修改方式是使用setState();

//正确
this.setState({
  title: 'React'
});
  • state的更新时异步的

调用setState时,组件state并不会立即改变,只是把要修改的状态放入事件队列当中,而react会优化真正的执行时机,并且处于本身的性能原因,可能会将多次setState的状态修改合并成一次状态修改。因此不要依靠当前的state计算下一个state。

列对于电商类的应用中,在购物车里,点击一次购买按钮,购买数量会加1,如果连续点击两次,会加2,而在react合并修改为一次的情况下,相当与执行了如下代码:

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
}))
  • state的更新是一个合并的过程

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

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

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

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

react会合并最新的title到原来的状态,同时保留原来状态的content,最终合并state为:

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

state与不可变对象

react官方把state当成不可变对象,一方面直接修改this.state,组件并不会重新render; 另一方面,state中包含的所有状态都应该是不可变的对象,当state当中的摸一个状态发生变化时,应该重新创建这个状态对象,而不是直接修改原来的state状态; 那么当状态发生变化时,如何去创建新的状态呢,我们根据状态类型可以分为下面三种情况:

  • 状态类型为不可变类型(number,string,bool,bull,undefined),直接给要修改的状态赋一个新值即可
this.setState({
  count: 1,
  title: 'React',
  success: true
})
  • 状态类型为数组

假如有一个数组类型的状态books,当想books中增加一本书时,既可使用数组的concat方法或者es6的或者语法(apread syntax)

//方法一:使用preState,concat创建新数组

this.setState((preState)=>({
  books: preState.books.concat(['React Guide'])
}))

//方法二:ES6 spread syntax
this.setState(preState=>({
  books: [...preState,''React Guide]
}))

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

this.setState(preState=>({

books:preState.books.slice(1,3);

}))

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

this.setState(preState=>({
  books: preState.books.filter(item => {
   return item != 'React';
  })
}))

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

  • 状态的类型是普通对象(不包含:string,array)

    • 使用es6的Object.assgin()方法
    this.setState ({
      onwer: Object.assgin({},preState.onwer,{name:'Jason'});
    })
    
    • 使用对象扩展语法 Object spread properties
    this.setState(preState=>{
      owner: {...preState.owner,name:'Jason'}
    })
    

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

常见的setState

  • 修改object中某项
this.setState({
  object: {...object, key: value}
});
  • 删除数组首位
array.splice(0, 1);
this.setState({
  array
});

删除数组尾部

array.splice(array.length - 1);
this.setState({
  array
});

删除数组任意一项

array.splice(index, 1);
this.setState({
  array
});

数组尾部添加一项

this.setState({
  array: [...array, item]
});

数组头部添加一项

this.setState({
  array: [item, ...array]
});

数组任意位置添加一项

array.splice(index, 0, item);
this.setState({
  array
});

修改数组中任意一项中值

function updateArrayItem(index, key, value) {
  this.setState({
    array: array.map((item, _index) => _index == index ? {...item, [key]: value} : item)
  });
}