深入理解组件
state 所代表的一个组件 UI 呈现的完整状态又可以分成两类数据:用作渲染组件时使用到的数据的来源以及用作组件 UI 展现形式的判断依据。
如何定义 state
在组件中需要用到一个变量,且与组件的渲染无关时,就应该把这个变量定义为组件的普通属性,直接挂载到 this 下,而不是作为组件的 state。还有就是看 render 方法有无用到该变量,没有就同样挂载到 this 下:
state 和 props 都直接和组件的 UI 渲染有关,它们的变化会触发组件的重新渲染,但 props 对于使用它的组件来说是只读的,是通过父组件传递过来的,想要修改 props,只能在父组件中修改,而 state 是组件内部自己维护的状态,是可变的。
总结一下,组件中用到一个变量是不是应该作为 state 可通过下面 4 条依据进行判断:
- 该变量是否通过 props 从父组件中获取?若是,则不是 state
- 该变量是否在组件的整个生命周期中保持不变?若是,则不是 state
- 该变量是否可通过其他 state 或 props 计算得到?若是,则不是 state
- 该变量是否在 render 方法中使用?若是,则为 state,反之则不是,该情况下,变量更适合定义为组件的一个普通属性
- 不能直接修改 state,需要用 setState
- state 的更新是异步的
调用 setState 时,组件的 state 不会立即改变,setState 只是把要修改的状态放入一个队列中,React 会优化真正的执行时机,出于性能考虑,可能会将多次 setState 状态修改合并成一次状态修改。所以不要依赖当前 state 去计算下一个 state。当真正执行修改时,依赖的 this.state 并不能保证是最新的 state。同样不能依赖当前 props 计算下一状态,因为 props 的更新也是异步的。
state 与不可变对象
- state 类型是不可变类型(数字、字符串、布尔值、null、undefined)
因 state 是不可变类型,所以直接给要修改的状态赋一个新值即可
this.setState({
count: 1,
title: 'React',
success: true,
});- state 类型是数组
法一:使用 preState、concat 创建新数组
this.setState(preState => ({
books: preState.books.concat(['React Guide']),
}));法二:ES6 spread syntax
this.setState(preState => ({
books: [...preState.books, '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 会返回一个新的数组
state 的类型是普通对象(不包含字符串、数组)
- 使用 ES6 的 Object.assign
this.setState(preState => ({
owner: Object.assign({}, preState.owner, { name: 'Jason' }),
}));- 使用对象扩展语法 Object spread properties
this.setState(preState => ({
owner: { ...preState.owner, name: 'Jason' },
}));总结下,创建新的 state 的关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。当然也可以使用一些 Immutable 的 JS 库来实现
React状态为什么要是不可变对象呢? 一方面是因为不可变对象的修改会返回一个新对象,不用担心原有对象在不小心情况下被修改导致的错误,方便程序管理与调试。另一方面是出于性能考虑,当对象组件状态都是不可变对象时,在组件的 shouldComponentUpdate 方法中仅需要比较前后两次状态引用就可以判断状态是否真的改变,从而避免不必要的 render 调用。
