为什么说 90% 的情况下,immer 能完胜 immutable?

网站建设4年前发布
58 0 0

假设 React 组件有这样一个状态:,我们这样修改了它的状态:,你觉得组件会重新渲染么?,我们先在 class 组件里试一下:,渲染 state.a.b 的值,两秒以后修改 state。,20230306013818a380cae5096e41ec83b18288c70b6590ae6136629,你发现它重新渲染了,因为普通的 class 组件只要 setState 就会渲染。,但很多情况下我们需要做性能优化,只有 props 和 state 变了才需要渲染,这时候会继承 PureComponent:,20230306013819933d7ef13e1e5255f1f48139a9360294677028482,但这时候你就会发现组件不再重新渲染了。,说明这种情况下不能这样写 setState:,20230306013819296972742a6e90e53a729779f9376d1c368b2a117,先不着急探究原因,我们再在 function 组件里试一下:,这时候你觉得组件会重新渲染么?,20230306013819786ad30704997993626277c2eb64987e884258890,结果是也不会重新渲染。,这说明 React 内部肯定对 function 组件还有继承 PureComponent 的 class 组件做了相应的处理。,那 React 都做了什么处理呢?,我们从源码看一下:,首先是继承 PureComponent 的 class 组件:,2023030601382022a7fa0539b4561fff6265fcb8527c6a6d1a97380,你会发现 React 在更新 class 组件的时候,会判断如果是 PureComponent,那么会浅比较 props 和 state,如果变了才会渲染。,怎么浅比较的呢?,2023030601403604db446252a028fb092908ce47271438ed9dd6202,你会发现它先对比了两个值是否相等,如果不相等的话,再取出 key 来,对比每个 key 的值是否相等。,所以说,我们 setState 的时候传入 this.state 就不行了,第一个判断都过不去。,而且就算创建了新对象,如果每个 key 的值没有变,那依然也是不会渲染的。,这就是 React 对 PureComponent 做的优化处理。,再来看下 function 组件的,React 是怎么对它做的处理呢?,202303060138214792e77344e6c5afb6d4948823cd527bff7a89361,你会看到调用 useState 的 setXxx 时,React 会判断上次的 state 和这次的 state,如果一样,那就不会渲染,直接 return 了。,这是为什么 function 组件里 setState 上次的 state 不行的原因。,这两种情况还是有区别的,PureComponent 的处理里如果 state 变了,还会依次对比每个 key 的值,如果有某个值变了才会去渲染,但 function 组件里只对比了 state。,我们测试一下:,2023030601382257f312d307c9d685a9e853b7eeb17012dfa7f9417,用上图的方式 setState,整个 state 变了,但是 key 对应的值没有变。,在 PureComponent 的 class 组件里,按照我们的分析应该不会再渲染,只会打印一次 render:,20230306013822d2cdd83887e45e1f28c8551ba9f6601a442b9b947,确实是这样,虽然 state 对象变了,但是 key 的值没变,不会重新渲染。,然后在 function 组件里试一下:,20230306013822554f0850430b6730c2e9489546108386398a8b926,你会发现它打印了两次 render:,20230306014036e9d7050189c0570ee812105617e2196f1c2fd3747,综上,我们可以总结一下:,普通的 class 组件,setState 就会重新渲染,继承 PureComponent 的 class 组件,setState 时会对比 state 本身变没变,还会对比 state 的每个 key 的值变没变,变了才会重新渲染,function 组件在用 useState 的 setXxx 时,会对比 state 本身变没变,变了就会重新渲染,为什么 function 组件里只对比了 state 没有对比每个 key 的值也很容易理解,因为本来每个 state就是用 useState 单独声明的了,不像 class 组件的 state 都在一起。,知道了这个结论,我们也就知道了 setState 该怎么写了:,class 组件要这么写:,2023030601382317426e982cc43b0cb4514065991f6970f91a50263,state 的每个要修改的 key 的值,如果是个对象,那要创建一个新的对象才行。,function 组件里也是,要这么写:,20230306014037a41c91a26da6b154107421a06f63a08d555094385,综上,不管是 class 组件,还是 function 组件,setState 时都要创建新的 state,并且对应的 key 的值的时候,如果是对象,要创建新的对象(虽然普通 class 组件里可以不这么写,但还是建议统一用这种写法,不然容易引起困惑)。,但这样又有了一个新的问题:,如果 state 的内容很多呢?,202303060138241968eb008023e73d7543977bccd4fe9ab3672b631,而你只想修改其中的一部分,要把整个对象复制一次:,20230306013825e7225f2750e845903484822b10d4f4ce7081f2428,是不是很麻烦?,能不能我修改了对象的值,立马给我返回一个新的对象呢?,就是最开头的时候,我们的那种写法改造一下:,这么一个明显的痛点需求,自然就有相应的库了,也就是 immutable,这个是 facebook 官方出的,说是花了三年写的。,它有这么几个 api:fromJS、toJS、set、setIn、get、getIn。,我们试一下就知道了:,用 fromJS 把 JS 对象转成 immutable 内部的数据结构,然后 get a,再 set b 的值。,这样返回的是 immutable 的数据结构,并且对 b 做了修改:,202303060138255180b4d49cf729b94a04996cc59be7d25986d4973,你和之前的 a 属性的值对比下,发现也不一样了:,20230306013826e42c9bc4756034ca03d535cc5f082eb1738770368,这就是它的作用,修改值以后返回新的 immutable 数据结构。,那如果像修改一个层数比较深的值,但希望返回的值是整个对象的新的 immutable 结构呢?,可以用 setIn:,2023030601403842712231680cd1881d5903a5d89e9cb97dc24a735,这样修改了任意属性之后,都能拿到最新的对象,这不就完美解决了我们的痛点问题么?,你还可以用 toJS 再把 immutable 数据结构转成 JS 对象:,20230306013827f7594c4031534e7d43a93569ce84e1a14e458e692,再来回顾下 immutable 的 api:fromJS、toJS、set、get、setIn、getIn 这些都很容易理解。再就是 immutable 内部的数据结构 Map、Set 等。(注意这里的 Map、Set 不是 JS 里的那个,而是 immutable 实现的),这些 immutable 数据结构一般不大需要手动创建,直接用 fromJS 让 immutable 去创建就行。,然后我们在 React 组件里用一下试试:,先在 class 组件里用用:,20230306013827a35c4544690c0091a97508b7c96962422c82a8254,a 的值是个对象,我们用 fromJS 转成 immutable 的数据结构,之后修改调用 set、setIn 来修改。,不过,渲染的时候也得用 get、getIn 的 api 来取了。,202303060138285635aab603b22fd7e2a868cc033e5dd11ccc5d484,这样也解决了 setState 需要创建新对象的问题,而且更优雅。,有的同学可能会问,为什么要 sate.a 用 fromJS 转成 immutable,而不是整个 state 呢?,因为 react 内部也会用到这个 state 呀,就比如上面那个浅比较那里:,20230306013829a82a1387255d9c877d6419550338ec880d4f53447,react 需要把每个 key 的值取出来对比下变没变,而 immutable 对象只能用 get、getIn 来取,所以class 组件里不能把整个 state 变为 immutable,只能把某个 key 值的对象变为 immutable。,再在 function 组件里用下:,2023030601382922a4831282afe5358b15884003255dce2179cb359,function 组件里就可以这样写了,把整个 state 用 fromJS 变为 immutable 的,然后后面修改用 setIn,获取用 getIn。,20230306095450494023a54c5c47171cd172a09b56adbe03478c645,也同样解决了 setState 要创建新对象的问题。,为啥 function 组件里就可以把整个 state 变为 immutable 的了呢?,因为只有组件内部会用呀,我们自己写的代码是知道用 setIn、getIn 来操作的,但是 class 组件的话 react 还会对 PureComponent 做一些优化,会在组件外把 state 取出来处理,所以那个就只能把某些 key 变为 immutable 了。,immutable 介绍完了,大家觉得怎么样?,immutable 确实解决了创建新对象的复杂度的问题,而且性能也好,因为它创建了一套自己的数据结构。,但也相应的,导致使用的时候必须要用 getIn、setIn 的 api 才行,有一些心智负担。,这种心智负担是不可避免的吧?,还真可以,这几年又出了一个新的 immutable 库,叫做 immer(MobX 作者写的)。它就覆盖了 immutable 的功能的同时,还没有心智负担。,没有心智负担?怎么可能?,我们试一下就知道了:,obj 是原对象,调用 produce 传入该对象和要对它做的修改,返回值就是新对象:,20230306013832749517898efe50009044549b772f5cc4bf4b77799,后面就是普通 JS 对象的用法,也不用啥 getIn、setIn 啥的。,我们在 class 组件里用一下:,20230306013832e59a4d68645bf98897509461eec671e9b06cb1929,setState 的时候调用 produce,传入原来的 state 和修改函数,这样返回的就是新的 state。,用 state 的时候依然是普通 JS 对象的用法。是不是简单的一批,心智负担基本为 0?,我们再在 function 组件里用一下:,2023030601404009724c7171a05104c9f854e277f4d63fca3a72736,同样简单的一批,只要 setState 的时候调用下 produce 来产生新对象就行。,又学完了 immer,我们来对比下 immutable 和 immer:,直接看图吧:,class 组件里,immutable 这样写:,20230306013832d93c877109fe087bc45468b60ae842cf490cfa203,immer 这样写:,2023030601383256b9314366204cb0a29102232aac3a66027ac3278,function 组件里,immutable 这样写:,20230306014040958ab236170d9c6bce7128294ded095ea09829644,immer 这样写:,20230306013834a3c8d4b5579563089424617771323d0d0d3f49538,没有对比就没有伤害,从使用体验上,immer 完胜。,这么说,我们只用 immer 不就行了?,也不全是,90% 的场景下用 immer 就行,但 immutable 也有它独特的优点:,immutable 有自己的数据结构,修改数据的时候会创建新的节点连接之前的节点组成新的数据结构。,20230306014040c94a5b9582828401eb221493ab349b62a25f75109,而 immer 没有自己的数据结构,它只是通过 Proxy 实现了代理,内部自动创建新的对象:,20230306013835a1799cd06c4496ca045169792693f40622491d108,只不过是把手动创建新对象的过程通过代理给自动化了:,20230306013835a4068363996f94f7b83563c3146baf9b31d0ca191,所以从性能上来说,如果有特别大的 state 的话,immutable 会好一些,因为他用的是专用数据结构,做了专门的优化,除此以外,immer 更好一些。,综上,90% 的 React 应用,用 immer 比 immutable 更好一些,代码写起来简单,也更容易维护。有大 state 的,可以考虑 immutable。,此外,immutable 在 redux 里也很有用的:,用 immutable 的话是这样写:,取 store 的 state 要用 getIn 或 get:,而 immer 是这样写:,用 store 的 state 是普通对象的用法:,从结合 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 对象的用法。,从使用体验上来说,不管是和 react 的 setState 结合还是和 redux 的 reducer 结合,都是 immer 完胜,但是 immutable 因为有专用数据结构的原因,在有大 state 对象的时候,性能会好一些。,90% 的情况下,immer 能完胜 immutable。

© 版权声明

相关文章