假定 React 组件有这样一个状况:
this.state = {
a: {
b: 1
}
}
咱们这样修正了它的状况:
this.state.a.b = 2;
this.setState(this.state);
你觉得组件会从头烘托么?
咱们先在 class 组件里试一下:
import { Component } from 'react';
class Dong extends Component {
constructor() {
super();
this.state = {
a: {
b: 1
}
}
}
componentDidMount() {
setTimeout(() => {
this.state.a.b = 2;
this.setState(this.state);
}, 2000);
}
render() {
return <div>{this.state.a.b}</div>
}
}
export default Dong;
烘托 state.a.b 的值,两秒今后修正 state。
你发现它从头烘托了,由于一般的 class 组件只需 setState 就会烘托。
但许多状况下咱们需求做功用优化,只要 props 和 state 变了才需求烘托,这时分会承继 PureComponent:
但这时分你就会发现组件不再从头烘托了。
说明这种状况下不能这样写 setState:
先不着急探究原因,咱们再在 function 组件里试一下:
import { useEffect, useState } from 'react';
function Dong() {
const [state, setState] = useState({
a: {
b: 1
}
});
useEffect(() => {
setTimeout(() => {
state.a.b = 2;
setState(state)
}, 2000);
}, [])
return <div>{state.a.b}</div>
}
export default Dong;
这时分你觉得组件会从头烘托么?
结果是也不会从头烘托。
这说明 React 内部肯定对 function 组件还有承继 PureComponent 的 class 组件做了相应的处理。
那 React 都做了什么处理呢?
咱们从源码看一下:
首先是承继 PureComponent 的 class 组件:
你会发现 React 在更新 class 组件的时分,会判断假如是 PureComponent,那么会浅比较 props 和 state,假如变了才会烘托。
怎样浅比较的呢?
你会发现它先比照了两个值是否持平,假如不持平的话,再取出 key 来,比照每个 key 的值是否持平。
所以说,咱们 setState 的时分传入 this.state 就不可了,第一个判断都过不去。
而且就算创立了新目标,假如每个 key 的值没有变,那依然也是不会烘托的。
这便是 React 对 PureComponent 做的优化处理。
再来看下 function 组件的,React 是怎样对它做的处理呢?
你会看到调用 useState 的 setXxx 时,React 会判断前次的 state 和这次的 state,假如相同,那就不会烘托,直接 return 了。
这是为什么 function 组件里 setState 前次的 state 不可的原因。
这两种状况仍是有差异的,PureComponent 的处理里假如 state 变了,还会依次比照每个 key 的值,假如有某个值变了才会去烘托,但 function 组件里只比照了 state。
咱们测验一下:
用上图的方式 setState,整个 state 变了,可是 key 对应的值没有变。
在 PureComponent 的 class 组件里,依照咱们的剖析应该不会再烘托,只会打印一次 render:
的确是这样,尽管 state 目标变了,可是 key 的值没变,不会从头烘托。
然后在 function 组件里试一下:
你会发现它打印了两次 render:
综上,咱们能够总结一下:
- 一般的 class 组件,setState 就会从头烘托
- 承继 PureComponent 的 class 组件,setState 时会比照 props 和 state 本身变没变,还会比照 state 的每个 key 的值变没变,变了才会从头烘托
- function 组件在用 useState 的 setXxx 时,会比照 state 本身变没变,变了就会从头烘托
为什么 function 组件里只比照了 state 没有比照每个 key 的值也很简略了解,由于原本每个 state便是用 useState 独自声明的了,不像 class 组件的 state 都在一同。
知道了这个结论,咱们也就知道了 setState 该怎样写了:
class 组件要这么写:
state 的每个要修正的 key 的值,假如是个目标,那要创立一个新的目标才行。
function 组件里也是,要这么写:
综上,不管是 class 组件,仍是 function 组件,setState 时都要创立新的 state,而且对应的 key 的值的时分,假如是目标,要创立新的目标(尽管一般 class 组件里能够不这么写,但仍是主张统一用这种写法,不然简略引起困惑)。
但这样又有了一个新的问题:
假如 state 的内容许多呢?
而你只想修正其中的一部分,要把整个目标仿制一次:
是不是很麻烦?
能不能我修正了目标的值,立马给我回来一个新的目标呢?
便是最开头的时分,咱们的那种写法改造一下:
const newState = this.state.set('a.b', 2);
this.setState(newState);
这么一个明显的痛点需求,自然就有相应的库了,也便是 immutable,这个是 facebook 官方出的,说是花了三年写的。
它有这么几个 api:fromJS、toJS、set、setIn、get、getIn。
咱们试一下就知道了:
const immutableObj = fromJS({
a: {
b: 1
}
});
const newObj = immutableObj.get('a').set('b', 2);
用 fromJS 把 JS 目标转成 immutable 内部的数据结构,然后 get a,再 set b 的值。
这样回来的是 immutable 的数据结构,而且对 b 做了修正:
你和之前的 a 属性的值比照下,发现也不相同了:
这便是它的效果,修正值今后回来新的 immutable 数据结构。
那假如像修正一个层数比较深的值,但期望回来的值是整个目标的新的 immutable 结构呢?
能够用 setIn:
这样修正了任意属性之后,都能拿到最新的目标,这不就完美处理了咱们的痛点问题么?
你还能够用 toJS 再把 immutable 数据结构转成 JS 目标:
再来回忆下 immutable 的 api: fromJS、toJS、set、get、setIn、getIn 这些都很简略了解。再便是 immutable 内部的数据结构 Map、Set 等。(留意这里的 Map、Set 不是 JS 里的那个,而是 immutable 完成的)
这些 immutable 数据结构一般不大需求手动创立,直接用 fromJS 让 immutable 去创立就行。
然后咱们在 React 组件里用一下试试:
先在 class 组件里用用:
a 的值是个目标,咱们用 fromJS 转成 immutable 的数据结构,之后修正调用 set、setIn 来修正。
不过,烘托的时分也得用 get、getIn 的 api 来取了。
这样也处理了 setState 需求创立新目标的问题,而且更优雅。
有的同学可能会问,为什么要 sate.a 用 fromJS 转成 immutable,而不是整个 state 呢?
由于 react 内部也会用到这个 state 呀,就比方上面那个浅比较那里:
react 需求把每个 key 的值取出来比照下变没变,而 immutable 目标只能用 get、getIn 来取,所以class 组件里不能把整个 state 变为 immutable,只能把某个 key 值的目标变为 immutable。
再在 function 组件里用下:
function 组件里就能够这样写了,把整个 state 用 fromJS 变为 immutable 的,然后后面修正用 setIn,获取用 getIn。
也相同处理了 setState 要创立新目标的问题。
为啥 function 组件里就能够把整个 state 变为 immutable 的了呢?
由于只要组件内部会用呀,咱们自己写的代码是知道用 setIn、getIn 来操作的,可是 class 组件的话 react 还会对 PureComponent 做一些优化,会在组件外把 state 取出来处理,所以那个就只能把某些 key 变为 immutable 了。
immutable 介绍完了,咱们觉得怎样样?
immutable 的确处理了创立新目标的复杂度的问题,而且功用也好,由于它创立了一套自己的数据结构。
但也相应的,导致运用的时分有必要要用 getIn、setIn 的 api 才行,有一些心智担负。
这种心智担负是不可避免的吧?
还真能够,这几年又出了一个新的 immutable 库,叫做 immer(MobX 作者写的)。它就覆盖了 immutable 的功用的同时,还没有心智担负。
没有心智担负?怎样可能?
咱们试一下就知道了:
import { produce } from 'immer';
const obj = {
a: {
b: 1
}
};
const obj2 = produce(obj, draft => {
draft.a.b = 2
});
obj 是原目标,调用 produce 传入该目标和要对它做的修正,回来值便是新目标:
后面便是一般 JS 目标的用法,也不用啥 getIn、setIn 啥的。
咱们在 class 组件里用一下:
setState 的时分调用 produce,传入原来的 state 和修正函数,这样回来的便是新的 state。
用 state 的时分依然是一般 JS 目标的用法。是不是简略的一批,心智担负基本为 0?
咱们再在 function 组件里用一下:
相同简略的一批,只需 setState 的时分调用下 produce 来产生新目标就行。
又学完了 immer,咱们来比照下 immutable 和 immer:
直接看图吧:
class 组件里,immutable 这样写:
immer 这样写:
function 组件里,immutable 这样写:
immer 这样写:
没有比照就没有伤害,从运用体会上,immer 完胜。
这么说,咱们只用 immer 不就行了?
也不全是,90% 的场景下用 immer 就行,但 immutable 也有它独特的长处:
immutable 有自己的数据结构,修正数据的时分会创立新的节点衔接之前的节点组成新的数据结构。
而 immer 没有自己的数据结构,它只是通过 Proxy 完成了署理,内部自动创立新的目标:
只不过是把手动创立新目标的过程通过署理给自动化了:
所以从功用上来说,假如有特别大的 state 的话,immutable 会好一些,由于他用的是专用数据结构,做了专门的优化,除此以外,immer 更好一些。
综上,90% 的 React 应用,用 immer 比 immutable 更好一些,代码写起来简略,也更简略维护。有大 state 的,能够考虑 immutable。
此外,immutable 在 redux 里也很有用的:
用 immutable 的话是这样写:
const initialState = fromJS({})
function reducer(state = initialState, action) {
switch (action.type) {
case SET_NAME:
return state.set('name', 'guang')
default:
return state
}
}
取 store 的 state 要用 getIn 或 get:
function mapStateToProps(state) {
return {
xxxx: state.getIn(['guangguang', 'guang']),
yyyy: state.getIn(['dongdong', 'dong'])
}
}
而 immer 是这样写:
const reducer = produce((state = initialState, action) => {
switch (action.type) {
case SET_NAME:
state.name = 'guang';
break;
default:
return state
}
})
用 store 的 state 是一般目标的用法:
function mapStateToProps(state) {
return {
xxxx: state.guangguang,
yyyy: state.dongdong
}
}
从结合 redux 的视点来看,也是 immer 在体会上完胜。
总结
在 React 组件里 setState 是要创立新的 state 目标的,在承继 PureComponent 的 class 组件、function 组件都是这样。
承继 PureComponent 的 class 组件会浅比照 props 和 state,假如 state 变了,而且 state 的 key 的某个值变了,才会烘托。
function 组件的 state 目标变了就会从头烘托。
尽管在一般 class 组件里,不需求创立新的 state,但咱们仍是主张统一,所有的组件里的 setState 都创立新的目标。
可是创立目标是件比较麻烦的事情,要一层层 …,所以咱们会结合 immutable 的库。
主流的 immutable 库有两个, facebook 的 immutable 和 MobX 作者写的 immer。
immutable 有自己的数据结构,Map、Set 等,有 fromJS、toJS 的 api 用来转化 immutable 数据结构和一般 JS 目标,操作数据需求用 set、setIn、get、getIn。
immer 只要一个 produce api,传入原目标和修正函数,回来的便是新目标,运用新目标便是一般 JS 目标的用法。
要留意在 class 组件里,只能 state 的某个 key 的值变为 immutable,而不能整体变为 immtable,由于 React 内部会用到。
从运用体会上来说,不管是和 react 的 setState 结合仍是和 redux 的 reducer 结合,都是 immer 完胜,可是 immutable 由于有专用数据结构的原因,在有大 state 目标的时分,功用会好一些。
90% 的状况下,immer 能完胜 immutable。