https://github.com/immerjs/immer
immer 英文 https://immerjs.github.io/immer
immer 中文 https://immerjs.github.io/immer/zh-CN
pnpm add immer
immer原理
- 核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构
- immer工作原理就是把 produce方法传入的第一个参数作为初始值initialState
- produce方法里面会通过createProxy这个方法创建一个代理对象
- 通过es6的 Proxy进行代理返回一个 draft代理对象
- 如果当前环境不支持 Proxy则会使用Object.defineProperty()实现监听
- 进行get、set的拦截把只有改变了的部分会从 draft映射到初始对象当中
解决的痛点
- 解决对象引用问题,cloneDeep的性能问题
- 深拷贝一次之前的对象,然后在这个新对象的基础上修改属性,最后进行 setState
- 在修改一个非常复杂的 state属性的时候,可能需要不断地使用扩展语法,这样会导致错误的几率上升
immer缺点
Immer 默认情况下必须启用自动冻结功能,
如果禁用自动冻结功能,性能会变得特别差,这使得 Immer 的 immutable 状态并不够通用。
在跨进程、远程数据传输等场景下,我们必须不断冻结这些 immutable 数据
Proxy 的 getitem、setitem 惰性求值,不能降低更新的时间复杂度;
- 如果要维护一个比较大的可持久化数据结构,如一个长度为 1e5 的完全可持久化数组,需要用 immutable.js
- immutable.js缺点
- 数据无法序列化,前期必须 fromJS,后期必须 toJS 转成js对象
- https://www.zhihu.com/question/266511546
immer 属性
import { produce } from "immer";const reducer = produce(state, draft => {draft.user = 'lucy';});function reducer(action, payload) {return produce(state, draft => {draft.user = paylaod;})};// setState 函数柯里化写法const _state = produce(state => {state.todoList.push({ name: 'immer源码', done: true })});setState(_state)
immer参考
https://segmentfault.com/a/1190000017270785
https://www.jianshu.com/p/8970a820c148
immer更新数据
immer更新数组
https://juejin.cn/post/7157745748832944141
import produce from 'immer';const todosArray = [{ id: 'id1', done: false, body: 'Take out the trash' },{ id: 'id2', done: false, body: 'Check Email' },];// 添加const addedTodosArray = produce(todosArray, draft => {draft.push({ id: 'id3', done: false, body: 'Buy bananas' });});// 索引删除const deletedTodosArray = produce(todosArray, draft => {draft.splice(3 /*索引 */, 1);});// 索引更新const updatedTodosArray = produce(todosArray, draft => {draft[3].done = true;});// 索引插入const updatedTodosArray = produce(todosArray, draft => {draft.splice(3, 0, { id: 'id3', done: false, body: 'Buy bananas' });});// 删除最后一个元素const updatedTodosArray = produce(todosArray, draft => {draft.pop();});// 删除第一个元素const updatedTodosArray = produce(todosArray, draft => {draft.shift();});// 数组开头添加元素const addedTodosArray = produce(todosArray, draft => {draft.unshift({ id: 'id3', done: false, body: 'Buy bananas' });});// 根据 id 删除const deletedTodosArray = produce(todosArray, draft => {const index = draft.findIndex(todo => todo.id === 'id1');if (index !== -1) {draft.splice(index, 1);}});// 根据 id 更新const updatedTodosArray = produce(todosArray, draft => {const index = draft.findIndex(todo => todo.id === 'id1');if (index !== -1) {draft[index].done = true;}});// 过滤const updatedTodosArray = produce(todosArray, draft => {// 过滤器实际上会返回一个不可变的状态,但是如果过滤器不是处于对象的顶层,这个依然很有用return draft.filter(todo => todo.done);});
useImmer
pnpm add immer use-immer
https://github.com/immerjs/use-immer
useImmer 就只是 useState 进行了一层包装,
将修改这个回调用 produce 包装了一层,才使得在外面使用的时候能够直接修改对象下面的属性
import React from "react";import { useImmer } from "use-immer";function App() {const [person, updatePerson] = useImmer({name: "Michel",age: 33});function updateName(name) {updatePerson(draft => {draft.name = name;});}function becomeOlder() {updatePerson(draft => {draft.age++;});}return (<div className="App">Hello {person.name} ({person.age})<inputonChange={e => {updateName(e.target.value);}}value={person.name}/><br /><button onClick={becomeOlder}>Older</button></div>);}
手写 useImmer
import { produce, Draft, nothing, freeze } from "immer";export function useImmer(initialValue: any) {const [val, updateValue] = useState(() =>freeze(typeof initialValue === "function" ? initialValue() : initialValue,true));return [val,useCallback((updater) => {if (typeof updater === "function") updateValue(produce(updater));else updateValue(freeze(updater));}, []),];}
useImmerReducer
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--;}}function Counter() {const [state, dispatch] = useImmerReducer(reducer, initialState);return (<>Count: {state.count}<button onClick={() => dispatch({ type: "reset" })}>Reset</button><button onClick={() => dispatch({ type: "increment" })}>+</button><button onClick={() => dispatch({ type: "decrement" })}>-</button></>);}
immer redux
import produce from 'immer';const namespace = 'LIST/'export const CHANGE_LIST = `${namespace}CHANGE`const defaultState = {dataSource: [],title: 'LIST'}export function listReducer(state = defaultState, action) {return produce(state, draft => {const { type, payload } = action;switch (type) {case CHANGE_LIST:draft.dataSource = payload.dataSourcedraft.title = payload.titlebreak;default:break;}console.log('action', action, 'draft', draft)})}
不可变数据
- 其实就是当你修改一个数据的时候,这个数据会给你返回一个新的引用,而自己的引用保持不变,
- 有点像是经常用到的数组的map
- Collection、List、Map、Set、Record、Seq。有非常全面的map、filter、groupBy、reduce``find函数式操作方法。同时 API 也设计的和JS对象、数组等类似
- immer来代替immutable https://juejin.im/post/6844904035409985549
- immerjs讲解 https://segmentfault.com/a/1190000017270785
- https://juejin.cn/post/6844903782145327118
解决引用类型对象被修改的办法
- 深度拷贝,但是深拷贝的成本较高,会影响性能;
- ImmutableJS,非常棒的一个不可变数据结构的库,可以解决上面的问题
- But,跟 Immer 比起来,ImmutableJS 有两个较大的不足:
- 需要使用者学习它的数据结构操作方式,没有 Immer 提供的使用原生对象的操作方式简单、易用;
- 它的操作结果需要通过
toJS方法才能得到原生对象, - 在操作一个对象的时候,要时刻注意操作的是原生对象还是 ImmutableJS 的返回结果,稍不注意,就会产生意想不到的 bug
immer替代品,性能优于 immer 10x
https://github.com/unadlib/mutative
https://zhuanlan.zhihu.com/p/422445526
https://juejin.cn/post/7218179400633253947
https://segmentfault.com/a/1190000017270785
https://zhangzhao.name/posts/immer-immutable/
