immer
大约 3 分钟约 753 字
react immer 不可变数据
为什么强调不可变数据
import React from 'react';
export default () => {
const [obj, setObj] = useState({ name: 'alvin', others: { age: 18 } });
const [count, setCount] = useState(0);
const hanleClick = () => {
obj.others.age = 99;
setCount((prev) => prev + 1);
};
return (
<>
<button onClick={hanleClick}>click</button>
{JSON.stringify(obj, null, 2)}
</>
);
};
如上,点击后就会发现 obj 引用没变,但是 obj.others.age
修改了,然后也被重新渲染了,这是由于这就是引用类型的副作用导致的。
解决方案
- 浅复制:只能复制一层
- 深克隆:我们不仅要考虑到正则、Symbol、Date 等特殊类型,还要考虑到原型链和循环引用的处理,性能消耗大!
- 深克隆的的性能相比于浅克隆大打折扣,但是浅克隆又不能从根本上杜绝引用类型的副作用,使用 immutable:
- 即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
实现简单的 immutable
immer
用法举例: const nextState = produce(state, (draft) => {});
/** 浅复制 */
function shallowCopy(value) {
if (Array.isArray(value)) return value.slice();
if (value.__proto__ === undefined) return Object.assign(Object.create(null), value);
return Object.assign({}, value);
}
function createState(target) {
this.modified = false; // 是否被修改
this.target = target; // 目标对象
this.copy = undefined; // 拷贝的对象
}
createState.prototype = {
// 对于get操作,如果目标对象没有被修改直接返回原对象,否则返回拷贝对象
get: function (key) {
if (!this.modified) return this.target[key];
return this.copy[key];
},
// 对于set操作,如果目标对象没被修改那么进行修改操作,否则修改拷贝对象
set: function (key, value) {
if (!this.modified) this.markChanged();
return (this.copy[key] = value);
},
// 标记状态为已修改,并拷贝
markChanged: function () {
if (!this.modified) {
this.modified = true;
this.copy = shallowCopy(this.target);
}
},
};
const PROXY_STATE = Symbol('proxy-state');
// 接受一个目标对象和一个操作目标对象的函数
function produce(state, producer) {
const store = new createState(state);
const proxy = new Proxy(store, {
get(target, key) {
if (key === PROXY_STATE) return target;
return target.get(key);
},
set(target, key, value) {
return target.set(key, value);
},
});
producer(proxy);
const newState = proxy[PROXY_STATE];
if (newState.modified) return newState.copy;
return newState.target;
}
const baseState = [
{ todo: 'Learn typescript', done: true },
{ todo: 'Try immer', done: false },
];
const nextState = produce(baseState, (draft) => {
draft.push({ todo: 'Tweet about it', done: false });
draft[1].done = true; // 这里会改到原属性
});
console.log(baseState, nextState);
执行结果:
[
{ todo: 'Learn typescript', done: true },
{ todo: 'Try immer', done: true },
][
({ todo: 'Learn typescript', done: true }, { todo: 'Try immer', done: true }, { todo: 'Tweet about it', done: false })
];
defineProperty vs Proxy
这个没特别的深入,
defineProperty
无法监听数组变化 比如arr[1] = 2
; 如果需要监听defineProperty
只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择。Proxy
可以直接监听对象而非属性,Proxy
的劣势就是兼容性问题,而且无法用polyfill
磨平
版权属于:h7ml
作品采用: 《 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 》许可协议授权