相关文章推荐
深沉的火柴  ·  python - ...·  7 月前    · 
乐观的芒果  ·  uefi ...·  1 年前    · 
2022要用immer来代替immutable优化你的React项目

不可变数据

React的老手们早就知道为什么要用不可变数据了,但是为了防止新手们看不懂,所以还是要解释一下什么是不可变数据,不可变数据指的其实就是当你修改一个数据的时候,这个数据会给你返回一个新的引用,而自己的引用保持不变,有点像是经常用到的数组的map方法:

const arr1 = [1, 2, 3];
const arr2 = arr1.map(item => item * 10);
console.log(arr1 === arr2)
//false

这样的话每次修改数据,新返回的数据就和原来不相等了。

如果数据变更,节点类型不相同的时候会怎样呢?React 的做法非常简单粗暴,直接将 原 VDOM 树上该节点以及该节点下所有的后代节点 全部删除,然后替换为新 VDOM 树上同一位置的节点,当然这个节点的后代节点也全都跟着过来了。

这样的话非常浪费性能,父组件数据一变化,子组件全部都移除,再换新的,所以才有了shouldComponentUpdate这个生命周期(Vue的小伙伴请放心,Vue原理和React不太一样,所以没这毛病),这个函数如果返回false的话子组件就不会更新,但是每次在这个函数里面写对比会很麻烦,所以有了PureComponent和Memo,但是只提供了浅比较,所以这时候不可变数据就派上用场了,每次修改数据都和原数据不相等的话,就可以精确的控制更新。

immutable

Facebook早就知道React这一缺陷,所以历时三年打造了一个不可变数据的immutable.js。它内部实现了一套完整的 Persistent Data Structure,还有很多易用的数据类型。像Collection、List、Map、Set、Record、Seq。有非常全面的map、filter、groupBy、reduce``find函数式操作方法。同时 API 也设计的和JS对象、数组等类似。
不过功能虽全,但是如果我们仅仅只是为了优化浅对比防止子组件过度刷新的话,引入这么大的一个库就未免有些大材小用了,而且学习成本也是需要考虑在内的,所以要为大家介绍一下今天的主角:轻量、易用、简洁又可以快速上手的immer.js

immer

immer这玩意来头可不小,他的创造者就是大名鼎鼎的Mobx作者,听过Mobx的人应该都知道,它与Redux相比更简洁、更轻量、同时也更加易学,所以immer也同样的继承了这些优点:轻量、简洁、易上手、并且使用起来也非常的舒服,不会产生容易把immutable数据类型与原生JS数据类型搞混的情况。它的核心思想就是利用Vue3源码中大量运用的Proxy代理,几乎以最小的成本实现了JS的不可变数据结构,解决了许多日常开发中的棘手问题,相信看完我的文章你一定会喜欢上它的!
首先第一步就是先进行安装:

npm i -S immer
yarn add immer
import produce from 'immer';
const array = [{value: 0}, {value: 1}, {value: 2}];
const arr = produce(array, draft => {
  draft[0].value = 10;
console.log(arr === array);
//false

解释一下:produce是生产的意思(你想起啥名都行,但是官网喜欢这么叫,我就跟着这么起名),这个函数第一个参数是你想要改变的数据对象,第二个参数是一个函数,这个函数的参数draft是草稿的意思,代表的就是你想要改变的那个数据对象,然后在函数体内你就正常想怎么改就怎么改,produce运行完的结果就是一个全新的对象啦!怎么样是不是超级简洁超级好用呢?

  • 注意:如果你什么也不返回或者并没有操作数据的话,并不会返回一个新的对象!
  • const array = [{value: 0}, {value: 1}, {value: 2}];
    const arr = produce(array, draft => {});
    console.log(array === arr);
    // true
    

    引用一张immutable的图,从图中可以看出来返回值并不是一份深拷贝内容,而是共享了未被修改的数据,这样的好处就是避免了深拷贝带来的极大的性能开销问题,并且更新后返回了一个全新的引用,即使是浅比对也能感知到数据的改变。

  • 如果把produce的第一个参数省略掉的话,只传入第二个参数返回值将会是一个函数👇
  • const array = [{value: 0}, {value: 1}, {value: 2}];
    const producer = produce(draft => {
      draft[0].value = 10;
    const arr = producer(array);
    console.log(array === arr);
    // false
    

    这样虽然结果一样,但是却增强了可复用性,甚至可以进行再次封装来形成一个高阶函数:

    const array = [{value: 0}, {value: 1}, {value: 2}];
    const producer = (state, fn) => produce(fn)(state);
    const arr = producer(array, draft => { draft[0] = 666 });
    console.log(array, arr);
    // [{…}, {…}, {…}]
    // [666, {…}, {…}]
    
  • 此时我们并没有任何返回值,那么如果有返回值的话会怎样呢?
  • const array = [{value: 0}, {value: 1}, {value: 2}];
    const producer = (state, fn) => produce(fn)(state);
    const arr = producer(array, draft => [666, ...draft]);
    console.log(array, arr);
    // [{…}, {…}, {…}]
    // [666, {…}, {…}, {…}]
    

    我们发现返回值就是新数据的结果!所以我们可以清楚的得知:在没有返回值时数据是根据函数体内对draft参数的操作生成的。有返回值的话返回值就会被当做新数据来返回。

    使用use-immer来替代你的useState

    由于React Hooks的异军突起,导致现在很多组件都使用函数来进行编写,数据就直接写在useState中,但是有了useImmer,你以后就可以用它来代替useState啦!
    还是老规矩,先安装:

    npm install immer use-immer
    
    yarn add immer use-immer
    

    定义数据: const [xxx, setXxx] = useImmer(…)
    修改数据: setXxx(draft => {})

    可以看到用法和setState几乎没啥太大区别,接下来我们通过一个小案例来继续深入useImmer的用法:

    import React from "react";
    import { useImmer } from "use-immer";
    export default function () {
      const [person, setPerson] = useImmer({
        name: "马云",
        salary: '对钱没兴趣'
      function setName(name) {
        setPerson(draft => {
          draft.name = name;
      function becomeRicher() {
        setPerson(draft => {
          draft.salary += '$¥';
      return (
        <div className="App">
            {person.name} ({person.salary})
          <input
            onChange={e => {
              setName(e.target.value);
            value={person.name}
          <button onClick={becomeRicher}>变富</button>
        </div>
    

    这是一个改编自官网的小例子,可以看得出useImmer的用法和useState十分相似,在保持住了简洁性的同时还具备了immutable的数据结构,十分便捷。

    useImmerReducer

    use-immer对useReducer进行了加强封装,同样也几乎没什么学习成本,再改编一下官网小案例👇

    import React from "react";
    import { useImmerReducer } from "use-immer";
    const initialState = { salary: 0 };
    function reducer(draft, action) {
      switch (action.type) {
        case "reset":
          return initialState;
        case "increment":
          return void draft.salary++;
        case "decrement":
          return void draft.salary--;
    export default function () {
      const [state, dispatch] = useImmerReducer(reducer, initialState);
      return (
          期待工资: {state.salary}K
          <button onClick={() => dispatch({ type: "increment" })}>+</button>
          <button onClick={() => dispatch({ type: "decrement" })}>-</button>
          <button onClick={() => dispatch({ type: "reset" })}>重置</button>
    

    怎么样?看完之后是不是感觉神清气爽,有这么一个东西轻量、简洁、易用又好学,看一篇文章的功夫就能学会,而且还能很好的解决你的React性能问题,那还等什么?赶紧npm install下载安装吧!

    往期精彩文章

  • 《整治GitHub不文明现象!微软推出评论区!》
  • 《Vue 3.0.3 : 新增CSS变量传递以及最新的Ref提案》
  • 《千万别小瞧九宫格 一道题就能让候选人原形毕露!》
  • 《移动端布局面试题 全面考察你的CSS功底(居中篇)》
  • 《将原型对象设置成Proxy后的一系列迷惑行为》
  • 《Vue超好玩的新特性:DOM传送门》
  • 《Vue超好玩的新特性:在CSS中引入JS变量》
  • 《不依赖任何库打造属于自己的可视化数据地图》
  • 《在Vue项目中使用React超火的CSS-in-JS库: styled-components》
  • 《终于轮到Vue来带给React灵感了?》
  • 《Vue3在IOS下的一个小坑》
  • 《来自《React Hooks 与 Immutable》小册作者'神三元'的灵魂拷问》
  • 《好消息,Vue3官方文档出中文版的啦!》
  • 《新版vue-router的hooks用法》
  • 《[译]Vue 3:2020年中状态更新》
  • 《[译]React 17终于发布RC版本了 官方竟说17是个过渡版!》
  • 《[译]尤雨溪:Vue3的设计过程》
  • 《Node之父重构的Deno终于发布了,它终究会取代Node吗?》
  • 《今日凌晨Vue3 beta版震撼发布,竟然公开支持脚手架项目!》
  • 分类:
    前端
  •