typescript 解法, 执行用时: 932 ms, 内存消耗: 119.3 MB, 提交时间: 2023-10-15 14:59:48
type Obj = Record<PropertyKey, unknown> | unknown[]
/**
* 实现immer库的produce函数.
*/
class ImmutableHelper {
private readonly _base: Obj
constructor(draft: Obj) {
this._base = draft
}
produce(recipe: (draft: Obj) => void): Obj {
const handler = new HandlerNode(this._base)
recipe(new Proxy(this._base, handler))
return handler.query()[1]
}
}
/**
* const originalObj = {"x": 5};
* const mutator = new ImmutableHelper(originalObj);
* const newObj = mutator.produce((proxy) => {
* proxy.x = proxy.x + 1;
* });
* console.log(originalObj); // {"x: 5"}
* console.log(newObj); // {"x": 6}
*/
/**
* 类似于TrieNode的结构.
* @property _origin 原始对象.
* @property _children 子节点, 保存每个儿子的handler和proxy.
* @property _action 每个结点处的修改.
*/
class HandlerNode {
private readonly _origin: Obj
private readonly _children: Map<PropertyKey, [handler: HandlerNode, proxy: Obj]> = new Map()
private readonly _action: Map<PropertyKey, Obj> = new Map()
constructor(obj: Obj) {
this._origin = obj
}
has(_: Obj, prop: PropertyKey): boolean {
return prop in this._origin || this._action.has(prop)
}
get(_: Obj, prop: PropertyKey): Obj {
if (this._action.has(prop)) return this._action.get(prop)!
if (this._children.has(prop)) return this._children.get(prop)![1]
const res = this._origin[prop]
if (!isObj(res)) return res
const handler = new HandlerNode(res)
const proxy = new Proxy(res, handler)
this._children.set(prop, [handler, proxy])
return proxy
}
set(_: Obj, prop: PropertyKey, value: Obj): boolean {
this._children.delete(prop)
this._action.set(prop, value)
return true
}
query(): [dirty: boolean, res: Obj] {
const patch: Record<PropertyKey, Obj> = Object.create(null)
let dirty = this.dirty
this._children.forEach(([childHandler], key) => {
const [childDirty, childRes] = childHandler.query()
if (childDirty) {
dirty = true
patch[key] = childRes
}
})
if (!dirty) return [false, this._origin]
const res = { ...this._origin, ...patch }
this._action.forEach((value, key) => {
res[key] = value
})
return [true, res]
}
get dirty(): boolean {
return !!this._action.size
}
}
function isObj(obj: unknown): obj is Obj {
return typeof obj === 'object' && obj !== null
}