Last active
February 14, 2023 05:01
-
-
Save dexbol/3295333eacc66419c5b5236a860f0d63 to your computer and use it in GitHub Desktop.
immer.js 实现方式简介
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
/** | |
* 此函数只为说明 immer.js 的基本实现方式(基于 ES6 Proxy 非 ES5),因此尽量保持简陋, | |
* 并且只支持普通对象,而且不能通过返回值的方式生成新对象, | |
* 也不能增加新属性。 | |
* 同时,也不支持 Array Map Set 等其他数据类型。 | |
*/ | |
// 存储 revoke 函数,用于释放 Proxy 对象资源。 | |
// 详见:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/revocable | |
const revokes = []; | |
const STATE_KEY = Symbol.for('state'); | |
function produce(plainObject, recipe) { | |
var proxy = createProxy(plainObject, null); | |
recipe(proxy); | |
var state = proxy[STATE_KEY]; | |
var result; | |
if (!state.modified) { | |
result = plainObject; | |
} else { | |
// 将 Proxy 对象,从末端属性依次还原回普通对象。 | |
result = process(state.draft); | |
} | |
// 释放 Proxy 对象资源 | |
while ((rev = revokes.shift())) { | |
rev(); | |
} | |
return result; | |
} | |
function createProxy(base, parent) { | |
var state = { | |
parent, | |
modified: false, | |
base: base, | |
copy: null, | |
draft: null, | |
drafts: {} | |
}; | |
var {proxy, revoke} = Proxy.revocable(state, proxyTraps); | |
state.draft = proxy; | |
revokes.push(revoke); | |
return proxy; | |
} | |
var proxyTraps = { | |
get(state, prop) { | |
var result; | |
if (prop === STATE_KEY) { | |
result = state; | |
} else { | |
result = (state.copy || state.base)[prop]; | |
// 为了判断子对象的修改状态,生成一个对应的 Proxy 对象。 | |
if ( | |
typeof result == 'object' && | |
result != null && | |
!result[STATE_KEY] | |
) { | |
if (state.modified) { | |
state.drafts = state.copy; | |
} | |
result = state.drafts[prop] = createProxy(result, state); | |
} | |
} | |
return result; | |
}, | |
set(state, prop, value) { | |
if (!state.modified) { | |
var baseValue = state.base[prop]; | |
var changed = !Object.is(baseValue, value); | |
if (changed) { | |
markChanged(state); | |
state.copy[prop] = value; | |
} | |
} else { | |
state.copy[prop] = value; | |
} | |
return true; | |
} | |
}; | |
function markChanged(state) { | |
if (!state.modified) { | |
state.modified = true; | |
state.copy = Object.assign({}, state.base, state.drafts); | |
state.drafts = null; | |
if (state.parent) { | |
markChanged(state.parent); | |
} | |
} | |
} | |
function process(draft) { | |
var state = draft[STATE_KEY]; | |
if (!state) { | |
return draft; | |
} | |
if (!state.modified) { | |
return state.base; | |
} | |
for (var p in state.copy) { | |
draft[p] = processProperty(p, state.copy[p], draft); | |
} | |
return state.copy; | |
} | |
function processProperty(property, value, parent) { | |
if (value[STATE_KEY]) { | |
return (parent[property] = process(value)); | |
} | |
return value; | |
} | |
var testObject = { | |
id: '007', | |
info: { | |
name: 'James Bond', | |
sexy: 'man', | |
favors: { | |
women: true, | |
guns: true, | |
terrorist: false | |
} | |
}, | |
organization: { | |
name: 'MI6', | |
fullName: 'The Secret Intelligence Service' | |
} | |
}; | |
var t1 = produce(testObject, (draft) => { | |
draft.info.favors.women = false; | |
}); | |
var t2 = produce(testObject, (draft) => { | |
draft.info.name = 'James Bond'; | |
}); | |
console.log(t1); | |
// true t1.info.favor 被修改,引发整个对象不同。 | |
console.log(t1 !== testObject); | |
// true | |
console.log(t1.info !== testObject.info); | |
// true 复用没有修改过的对象属性。 | |
console.log(t1.organization === testObject.organization); | |
// true 修改的值没有变化,返回原对象。 | |
console.log(t2 === testObject); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment