immer.js 原理 - 使用 Proxy 实现不可变数据

什么是immer.js

  • 使用 es6 Proxy实现的不可变数据类型,Immutability 替代方案。

什么是不可变数据类型

  • 不可变数据,就是值的数据无法被修改,改动数据的时候会创建一个新值。
  • 说白了就是数据的克隆,克隆的方式有很多种。可以参考该文章 循环克隆
  • 说到不可变数据,就得说起函数式编程。immer.js 就是 react 实现不可变数据的一种方式
  • 不可变数据是函数式编程的 安全帽 ,在js 中 如果函数传入的参数是复杂数据类型,没有经过克隆等手段,改动参数就可能 会产生副作用

immer.js 的使用

import produce from "immer"
const baseState = [
    {
        title: "Learn TypeScript",
        done: true
    },
    {
        title: "Try Immer",
        done: false
    }
]

const nextState = produce(baseState, draftState => {
    draftState.push({title: "Tweet about it"})
    draftState[1].done = true
})

react 中使用

import React, { useCallback, useState } from "react";
import {produce} from "immer";

const TodoList = () => {
  const [todos, setTodos] = useState([
    {
      id: "React",
      title: "Learn React",
      done: true
    },
    {
      id: "Immer",
      title: "Try Immer",
      done: false
    }
  ]);

  const handleToggle = useCallback((id) => {
    setTodos(
      produce((draft) => {
        const todo = draft.find((todo) => todo.id === id);
        todo.done = !todo.done;
      })
    );
  }, []);

  const handleAdd = useCallback(() => {
    setTodos(
      produce((draft) => {
        draft.push({
          id: "todo_" + Math.random(),
          title: "A new todo",
          done: false
        });
      })
    );
  }, []);

  return (<div>{*/ See CodeSandbox */}</div>)
}

Proxy 的 妙用

代理对象

const obj = {
    a: 1,
    b: {
      c: 222
    }
  }
  const ProxyObj = new Proxy(obj, {
    get(state, key) {
      console.log(state, key, '--.')
      return state[key]
    },
    set(target, attr, value) {
      return value
    }
  })

  ProxyObj.b.c = 10

在这里插入图片描述

  • 我们 在 设置 obj.b.c 的值时候,首先会访问到b, 所以 控制台 在get 的时候 打印 的 key 是 b。
  • 但是由于proxy 是 浅代理,所以 b 这个对象是没有被代理过,就没有任何输出了。

getter + 递归 深度代理对象

 const obj = {
    a: 1,
    b: {
      c: 222,
      d: {
        e: 1
      }
    }
  }

  const createProxy = (data) => {
    const ProxyObj = new Proxy(data, {
      get(state, key) {
        console.log(state, key, '--.')
        // 递归代理,访问的对象
        return createProxy(state[key])
      },

      set(target, attr, value) {
        return Reflect.set(target, attr, value)
      }
    })

    return ProxyObj
  }
  const ProxyObj = createProxy(obj)
  ProxyObj.b.d.e = 10

在这里插入图片描述

  • 我们在设置 .b.d.e = 10 的时候。访问了 b, d 对象,在get 的都打印了。并且 b, d 都成了代理对象,其他还是个正常对象。

immer 的 核心思想

  • 比如我们要改.b.d.e = 10。最终要改的 只是 d 对象上的 e,其他属性我们都不会去操作它。那么我们可以不可以把 d 对象 拷贝 一份 ,得到一个新的 d 对象呢?
const d = { e: 1 }
const newD = JSON.parse(JSON.stringify(d))
const newObj = {
   a: 1,
   b: {
     c: 222,
     d: newD
   }
 }
  • 答案是肯定的,并且immer 也是这样做的,newObj 就是通过immer 处理后返回来的 对象。对象里面 除了d 属性 其他的属性都和 obj 属性 画等号。
  • 因为 newObj 里面的 d 是经过克隆的,那么 newObj.b.d.e = 10 是不是就不会影响到 obj 里面e 的值呢。这样就最小化的实现了一个 不可变数据
  • 这一小小设计体现了 “按需” 软件设计思想,按需代理。在数据对象特别庞大的时候,这种方式实现的数据不可变性能是最优的。

immer 代理的核心代码

const deepClone = (data, cb) => {
  const proxyData = createProxy(data)
  cb(proxyData)
  return proxyData
}
const createProxy = (data) => {
  const target = {
    base: data,
    copy: null
  }
  const handle = {
    set(target, attr, value) {
      target.copy = {
        ...target.base,
        [attr]: value,
      }
      return target
    },
    get(state, key) {
      // 没有设置任何值,直接获取
      if (key === 'copy' || key === 'base') {
        return state[key] || state.base
      }

      // 获取的值是对象
      if (typeof state.base[key] === 'object') {
        if (state.copy === null) {
          state.copy = { ...state.base, }
        }
        state.copy[key] = createProxy(state.base[key])
        return state.copy[key]
      }
      return state.base[key]
    }
  }
  const { proxy } = Proxy.revocable(target, handle)
  return proxy
}
export default deepClone
  • 调用上面的 deepClone
const baseObj = {
    a: { b: 1, c: { d: 4 } },
    name: 'ajin',
    age: 10,
    hh: {
      d: 9
    }
  }
  const nextState = deepClone(baseObj, (draftState) => {
    draftState.a.c.d = 3
  })
  console.log(baseObj, nextState)
  • 输出结果
    在这里插入图片描述
  • 以上就是immer 实现不可变数据的核心思想。Proxy 在 我们开发过程中很常见,多了解Proxy 的经典应用能在开发中使用得更加得心应手。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值