Skip to content

Instantly share code, notes, and snippets.

@iShawnWang
Created April 28, 2022 09:40
Show Gist options
  • Save iShawnWang/529e90dd32980ab6458b6ae444f784b4 to your computer and use it in GitHub Desktop.
Save iShawnWang/529e90dd32980ab6458b6ae444f784b4 to your computer and use it in GitHub Desktop.
ES6 Proxy Immer.js
const PROXY_STATE = Symbol('immer-proxy-state')
class State {
srcObj
copy
touched
parent
constructor(srcObj, parent) {
this.srcObj = srcObj
this.parent = parent
this.copy = undefined
this.touched = false
}
markChanged() {
if (!this.touched) {
this.touched = true
this.copy = { ...this.srcObj }
if (this.parent) this.parent.markChanged()
}
}
}
const createProxy = (obj, parent) => {
const state = new State(obj, parent)
return new Proxy(state, {
get: (__, prop) => {
if (prop === PROXY_STATE) {
return state
}
state.markChanged()
const src = state.copy
src[prop] = typeof src[prop] === 'object' ? createProxy(src[prop], state) : src[prop]
return src[prop]
},
set: (__, prop, value) => {
state.markChanged()
state.copy[prop] = value
return true
},
})
}
function isProxy(value) {
return !!value && !!value[PROXY_STATE]
}
const finalize = (base) => {
if (!isProxy(base)) {
return base
}
const state = base[PROXY_STATE]
if (state.touched) {
const copy = state.copy
Object.keys(copy).forEach((prop) => {
copy[prop] = finalize(copy[prop])
})
return copy
} else {
return state.base
}
}
const produce = (srcObj, producer) => {
// 1. 创建 proxy 对象, 自动 copy on write
const proxyedObj = createProxy(srcObj)
// 2. (proxyedObj) => { //用户执行修改 return 结果 }
producer(proxyedObj)
// 3. proxy 对象还原原始 js obj 返回
return finalize(proxyedObj)
}
// 测试
const obj = { a: { b: {c: 3} } }
const modified = produce(obj, (draft) => (draft.a.b = {d:4}))
console.log(obj)
console.log(modified)
console.log(obj === modified)
@iShawnWang
Copy link
Author

// 简化版本

const produce = (rawObj, producer) => {
  // 1. 创建 proxy obj, 自动实现 copy on write
  const proxyObj = createProxy(rawObj)
  // 2. producer(proxyedObj), 让用户修改对象
  producer(proxyObj)
  // 3. proxyObj => js Obj 返回
  return finalize(proxyObj)
}

const createProxy = (rawObj) => {
  let touched = false
  let copyObj = null

  return new Proxy(() => {}, {
    apply: () => {
      return touched ? copyObj : rawObj
    },
    get: (__, prop) => {
      if(touched){
        return copyObj ? copyObj[prop] : undefined
      }else {
        return rawObj ? rawObj[prop] : undefined
      }
    },
    set: (__, prop, value) => {
      // copy on write
      if(!touched){
        touched = true
        copyObj = {...rawObj}
      }
      copyObj[prop] = value
      return true
    }
  })
}

const finalize = (proxyObj) => {
  return proxyObj()
}

const raw = {a:1}
const modified = produce(raw, (draft => draft.a = 2))
console.log(raw)
console.log(modified)

@iShawnWang
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment