最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 理解不可变状态Immer.js

    正文概述 掘金(黄鹏1481946874501)   2021-03-09   714

    先来看看什么叫可变状态

    let objA = { name: '爸爸' };
    let objB = objA;
    objB.name = '妈妈';
    console.log(objA.name); // objA 的name也变成了妈妈
    

    代码解析: 我们明明只修改代码objB的name,发现objA也发生了改变。这个就是可变状态。

    可变状态的缺点:

    1. 间接修改了其他对象的值,在复杂的代码逻辑中,会造成代码隐患。

    解决方案:

    • 深度拷贝
    • JSON.parse(JSON.stringify(objA)) 。 我常用的方案
    • immutable-js

    不可变状态immer.js

    案例分析:

    const {produce} = require('immer')
    // const {produce} = require('./immer')
    let baseState = {
      home:{name:'爸爸',arr:[1]},
      b:{}
    }
    
    let nextState = produce(baseState, (draft) => {
        draft.home.name = '妈妈';
    })
    console.log(baseState.home === nextState.home); // false
    console.log(baseState.home.arr === nextState.home.arr) // true
    console.log(baseState.b === nextState.b); // true
    
    

    特点:

    1. 子节点被修改,那么父节点,或者父父节点被重新创建。
    2. 兄弟节点或者其他与修改节点无关的节点,兄弟节点是被复用的节点,不会重新创建。

    其形成特点如下: 理解不可变状态Immer.js

    immer.js 原理讲解

    前置知识proxy的讲解

    let baseState = {
      home:{name:'爸爸',arr:[1]},
      b:{}
    }
    let p = new Proxy(baseState,{
      get(tartget, key) {
          console.log('get', tartget, key)
          return tartget[key]
      },
      set(tartget,key,value){
        console.log('set', tartget, key, value)
        tartget[key]=value
      }
    })
    p.home = 'mama1'
    // 打印结果
    // set { home: { name: '爸爸', arr: [ 1 ] }, b: {} } home mama1
    p.home.name = 'mama1'
    // 打印结果
    // get { home: { name: '爸爸', arr: [ 1 ] }, b: {} } home
    

    结论:

    1. 赋值的时候会触发,set的方法。
    2. proxy 不会深度代码,只代理一层,如果想深度代理的话,需要递归代理。

    immer 源码实现流程

    1. 辅助方法。
    var is = {
      isObject: (val) => Object.prototype.toString.call(val) === '[object Object]',
      isArray: (val) => Array.isArray(val),
      isFunction: (val) => typeof val === 'function'
    }
    
    1. 对象进行浅复制。用于产生草稿对象。
    // 浅复制
    function createDraftState(baseState) {
      if (is.isArray(baseState)) {
        return [...baseState];
      } else if (is.isObject(baseState)) {
        return Object.assign({}, baseState);
      } else {
        return baseState;
      }
    }
    
    1. 对象进行代理,并增加标识位,用户判断是否发生修改。
     var internal = {
        draftState: createDraftState(baseState),
        mutated: false
      }
    
    1. set 方法实现
    function set(target, key, value) {
          internal.mutated = true;
          let {draftState}= internal;
          draftState[key] = value;
          valueChange && valueChange()
          return true
    }
    
    1. get 方法实现
    function get(target, key) {
          if (key === INTERNAL) {
            return internal
          }
          const value = target[key];
          if (is.isObject(value) || is.isArray(value)) {
            if (key in keyToProxy){
                return keyToProxy[key];
            }else{
              keyToProxy[key] = toProxy(value,()=>{
                internal.mutated = true;
                const proxyOfChild = keyToProxy[key];
                var {draftState} = proxyOfChild[INTERNAL];
                internal.draftState[key] = draftState;
                valueChange && valueChange()
              })
              return keyToProxy[key];
            }
          }
    
          return internal.mutated ? internal.draftState[key] : baseState[key];
     }
    

    思路梳理完毕,接下来组装所有代码。

    var is = {
      isObject: (val) => Object.prototype.toString.call(val) === '[object Object]',
      isArray: (val) => Array.isArray(val),
      isFunction: (val) => typeof val === 'function'
    }
    // 定义一个唯一变量
    const INTERNAL = Symbol('INTERNAL');
    
    // 对外暴露的核心方法
    function produce(baseState, producer) {
      // 代理传入的初始对象
      const proxy = toProxy(baseState);
    
      producer(proxy) // 将代理对象
      const internal = proxy[INTERNAL];
      return internal.mutated ? internal.draftState : baseState;
    }
    
    function toProxy(baseState,valueChange) {
      var keyToProxy = {};
      var internal = {
        draftState: createDraftState(baseState), // 创建一个草稿对象
        mutated: false // 标记位,标记节点是否修改了。
      }
      return new Proxy(baseState, {
        get(target, key) {
          if (key === INTERNAL) { // 用户获取草稿对象
            return internal
          }
          const value = target[key];
          // 判断是不是,对象或者数组,如果是,则进行递归代理
          if (is.isObject(value) || is.isArray(value)) {
            // 如果已经代理过了,就不进行代理了,直接返回。
            if (key in keyToProxy){
                return keyToProxy[key];
            }else{
              // 递归代理
              keyToProxy[key] = toProxy(value,()=>{//子节点发生变化,通知父节点的回掉函数
                // 子节点,改变了,父节点也要标记改变了。
                internal.mutated = true;
                const proxyOfChild = keyToProxy[key];
                // 拿到子节点的草稿对象,
                var {draftState} = proxyOfChild[INTERNAL];
                // 父节点执行,字节点的草稿对象。
                internal.draftState[key] = draftState;
                // 然后在通知父节点。
                valueChange && valueChange()
              })
              return keyToProxy[key];
            }
          }
    			// 如果没有发生修改,则返回原始对象,修改了,则返回草稿对象
          return internal.mutated ? internal.draftState[key] : baseState[key];
        },
        set(target, key, value) {
          // 进行复制操作毕竟发生了修改。
          internal.mutated = true;
          // 如果发生了修改,不能修改原始对象,只能修改草稿对象。
          let {draftState}= internal;
          // 修改草稿对象
          draftState[key] = value;
          //通知父节点进行父节点变更。
          valueChange && valueChange()
          return true
        }
      })
    }
    
    // 浅复制
    function createDraftState(baseState) {
      if (is.isArray(baseState)) {
        return [...baseState];
      } else if (is.isObject(baseState)) {
        return Object.assign({}, baseState);
      } else {
        return baseState;
      }
    }
    
    module.exports = {
      produce
    }
    

    代码已经实现,但是感觉不是特别好理解。我这里举一个生活动比较常见的例子。希望能帮助大家理解。

    小A,有一个父亲,有一个爷爷。 有一天小b告诉小a,你不是你爸爸亲生的(相当于修改了小a)。这个时候小a去找它新的爸爸,找到新的爸爸后,新爸爸带它去见新的页面,重新组建了一个新的家庭关系。(上诉代码就是这个实现逻辑)。


    起源地下载网 » 理解不可变状态Immer.js

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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