最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React中使用immer的实践探索|牛气冲天新年征文

    正文概述 掘金(wangly19)   2021-02-16   1353

    前言

    对于MOBX,可能并不会陌生。而immer.js也是该开源项目作者的一大力作,用于immutable管理的实践。而今天,会对其做一个简单的小分享。

    谈谈为什么会选择immer.js来作为immutable的解决方案,以及一些项目上的小实践。

    immer的优缺点

    在这里总结一些优缺点,其实对比是相当明显的,比时下immutable.js来说更加的接地气,实用。

    优点

    • 上手快,学习成本较低
    • 原生语法实现,没有额外的方言依赖
    • 体积小,方便紧张的空间内实用
    • api精炼,理解较为容易

    缺点

    • 浏览器需要支持proxy语法糖,否则会实用defineProperty代替
    • ES5后备实现的速度大约是代理实现速度的两倍,在某些情况下更糟。

    用大白话来说就是,如果你需要在ie10上开发项目,那还是别用了。在这里也侧面突出了两个api的一个生产速度。在2021年的今天,ie10用户应该很少了吧。

    工作模式

    用过immutable.js大家都知道,它的实现方式是自己维护了一套自有的数据结构。虽然解决了问题,但也面临很多的问题。

    开发者需要在兼顾原生类型的时候,还要抽时间学习对应的数据结构的使用。对于轻度用户来说,无疑是非常鸡肋的事情。

    immer的话,没有额外的学习负担,更加的贴合应用场景,且改造代码也非常的快捷和优雅。基本的实现思路就如下图所表达的一样,我们所做的更改只是在当前数据代理上,一旦你的更改结束了,那么就会基于更改产生新的对象,这样就非常方便的在隔离沙箱中修改一个数据,且没有过多的副作用

    React中使用immer的实践探索|牛气冲天新年征文

    如何使用

    immer中,主要的操作是由produce函数来进行的,produce需要接收两个参数。

    produce(currentState, producer: (draftState) => void): nextState
    
    • 当前数据: 元数据
    • 草稿函数: 代理函数

    使用起来也非常简单。下面就简单的分享下immerReact中是如何工作的一些小实例。

    为什么在React使用

    在这里,我也谈谈为什么使用immer吧。 在之前,我们也知道React如何判断视图更新是依赖于浅比较,如下图事例。

    当我们是基本数据类型的时候,看起来并没有什么问题,这个时候数据比较是不想等的。a

    var a = 1
    var b = a
    b = 2
    
    // a = 1, b = 2
    console.log(a === b) // false
    

    但是,如果是引用类型呢?

    var a = { x: 1 }
    var b = a
    
    b.x = 2
    
    // a = { x: 2 }, b = { x: 2 }
    console.log(a === b) // true
    

    这个时候,我们ab都指向了同一片内存单元,当我们在一处引用中修改了属性值,所有引用的数据都变了。另一个就是在浅比对的时候,对象的指向如果一样,那么React就不会刷新render,哪怕你的值已经修改了。因此,大多数时候,都是用一以下当时进行对象数组的更新的。

    this.setState({
     ...state,
     count: 2
    })
    

    以上方式产生一个新的内存引用进行设置,来确保引用类型数据的新值当前值相对来说是没有瓜葛的。虽然解决了问题,但其实从解决的方式来说非常的暴力。所以,为了更加精准的管理数据,引入了immer对引用状态进行管理,减少不必要的状态变化和意外的render渲染。

    在最后面留下了一个小问题,小伙伴们可以思考下。本文就不做赘述了。嘿嘿

    Class组件

    class组件中的state更新是通过触发setState来进行的,对于对象状态的更改形式非常的简单,参考官网的demo,对于状态来说非常的简单。在produce中直接对数据进行更改,是不是有vuex风味了,嘿嘿。

    从代码开发的角度来讲,更加接地气。

    
    EthicalAds: A privacy-focused ad network for developers. Publishers & Advertisers wanted!
    Ad by EthicalAds
    egghead.io lesson 8: Using Immer with useState. Or: useImmer
    Deep updates in the state of React components can be greatly simplified as well by using immer. Take for example the following onClick handlers (Try in codesandbox):
    
    /**
     * Classic React.setState with a deep merge
     */
    onBirthDayClick1 = () => {
        this.setState(prevState => ({
            user: {
                ...prevState.user,
                age: prevState.user.age + 1
            }
        }))
    }
    
    /**
     * ...But, since setState accepts functions,
     * we can just create a curried producer and further simplify!
     */
    onBirthDayClick2 = () => {
        this.setState(
            produce(draft => {
                draft.user.age += 1
            })
        )
    }
    

    上述例子中,其实很多同学会看不懂,为什么没有原始数据呢?那么怎么知道你要代理的数据来源呢?

    对于这个问题就要思考于函数柯里化(currying)了,得益于函数的柯里化(currying)。其作用非常的朴素,就是将接收多个参数的函数变成为接收一个参数且返回值为剩余参数的函数。

    这样理解的话,由于默认的setState会传入一个state参数,因此柯里化之后,只需要传递一个草稿函数就可以完成状态的更改了。

    函数组件

    对于无状态组件,immer将其可能用到State抽离成为了hook,名字是use-immer

    React中使用immer的实践探索|牛气冲天新年征文

    • 如何使用?查看文档

    useImmer

    useImmer用于state的对象处理,其实本质上是一个自定义hook,对useState进行了一层包装。对于状态的更改,只需要和class一样传递一个函数就可以了。

    import { useImmer } from 'use-immer';
    
    const [person, setPerson] = useImmer({
      name: "wangly",
    });
    
    // button click event ...
    const handleClick = () => {
     setPerson(state => {
     	state.name = 'wangly19 yes!!!'
     })
    }
    
    

    实现原理

    export function useImmer(initialValue: any) {
      const [val, updateValue] = useState(initialValue);
      return [
        val,
        useCallback(updater => {
          updateValue(produce(updater));
        }, [])
      ];
    }
    

    对于state的使用方式更加的简介和易懂,且不需要关注其他函数改变副作用的担忧,在开发的时候能够明细修改的数据,不必刻意去成产一个新对象,将这些事情更多交给统一化处理,出错也更加容易定位。

    其次,对于useReducer来说,和useState一半,也有一个自定义的hook来管理这个内容。使用方式也相对来说更加好理解。 参考以下事例,通过dispatch调用不同的action操作时,进行不同的更改操作。

    import { useImmerReducer } from "use-immer";
    
    const initialState = { count: 0 };
    
    function reducer(draft, action) {
      switch (action.type) {
        case "reset":
          return initialState;
        case "increment":
          return void draft.count++;
        case "decrement":
          return void draft.count--;
      }
    }
    

    代码实例点击查看

    其他

    由于项目使用的是umi-cli,那么必不可少的会使用dva来管理项目的部分状态,如果需要体验immer,只需要在配置文件中加上声明就可以享受immer的数据流了。

    import { defineConfig } from 'umi';
    import routes from './routes'
    
    export default defineConfig({
      hash: true,
      antd: {
      },
      dva: {
        immer: true
      },
      history: {
        type: 'browser'
      },
      locale: {
        // default zh-CN
        default: 'zh-CN',
        antd: true,
      },
      routes
    })
    

    性能

    对于性能来说,从官方给出一些对比数据来看,immer在大多数的场景下看起来消费差距并不是很大。相对一观之即可。

    React中使用immer的实践探索|牛气冲天新年征文

    # wangly19 @ wangly19s-MacBook-Pro in ~/Desktop/项目/immer on git:master o [11:42:31] 
    $ yarn test:perf
    yarn run v1.22.5
    $ cd __performance_tests__ && babel-node add-data.js && babel-node todo.js && babel-node incremental.js
    
    # add-data - loading large set of data
    
    just mutate: 0ms
    just mutate, freeze: 1ms
    handcrafted reducer (no freeze): 0ms
    handcrafted reducer (with freeze): 0ms
    immutableJS: 65ms
    immutableJS + toJS: 34ms
    seamless-immutable: 40ms
    seamless-immutable + asMutable: 48ms
    immer (proxy) - without autofreeze * 10000: 27ms
    immer (proxy) - with autofreeze * 10000: 29ms
    immer (es5) - without autofreeze * 10000: 93ms
    immer (es5) - with autofreeze * 10000: 65ms
    
    # todo - performance
    
    just mutate: 1ms
    just mutate, freeze: 195ms
    deepclone, then mutate: 175ms
    deepclone, then mutate, then freeze: 368ms
    handcrafted reducer (no freeze): 19ms
    handcrafted reducer (with freeze): 19ms
    naive handcrafted reducer (without freeze): 19ms
    naive handcrafted reducer (with freeze): 42ms
    immutableJS: 5ms
    immutableJS + toJS: 164ms
    seamless-immutable: 52ms
    seamless-immutable + asMutable: 143ms
    immer (proxy) - without autofreeze: 64ms
    immer (proxy) - with autofreeze: 74ms
    immer (proxy) - without autofreeze - with patch listener: 87ms
    immer (proxy) - with autofreeze - with patch listener: 84ms
    immer (es5) - without autofreeze: 257ms
    immer (es5) - with autofreeze: 257ms
    immer (es5) - without autofreeze - with patch listener: 2439ms
    immer (es5) - with autofreeze - with patch listener: 2715ms
    
    # incremental - lot of small incremental changes
    
    just mutate: 0ms
    handcrafted reducer: 72ms
    immutableJS: 17ms
    immer (proxy): 988ms
    immer (es5): 3520ms
    immer (proxy) - single produce: 9ms
    immer (es5) - single produce: 3ms
    ✨  Done in 67.52s.
    

    参考资料

    • immerjs github
    • immerjs docs
    • immutabl-维基百科
    • immutablejs

    后话

    immer本身来说并没有性能屏障,哪怕在文档的后面贴出了性能测试也并没有达到很大的提升。只能说在某些场景会优于现今的解决方案。如果在项目中存在困扰,不妨可以试试能不能更好的解决问题。

    对于引用类型来说,很容易发生一些小意外,对于我来说,引入immer更多的是解决不可变数据(immutable)带来的负担,在开发的时候不需要因为一些隐式方法而改变数据导致BUG,哪怕这类问题出现的几率很小,但如果出现一次的话,排查起来是非常困难的。

    在最后,祝大家2021年,牛气冲天。


    下载网 » React中使用immer的实践探索|牛气冲天新年征文

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元