Skip to content

produce.ts

ts
const clone = function <T>(obj: T): T {
  return JSON.parse(JSON.stringify(obj))
}

const produce = function <T>(fn: (state: T, ...options: any[]) => void) {
  return (state: T, ...options: any[]): T => {
    const clonedState = clone(state)
    fn(clonedState, ...options)
    return clonedState
  }
}

const baseState: any[] = [
  {
    todo: "Learn typescript",
    done: true
  },
  {
    todo: "Try immer",
    done: false
  }
]

const nextState = produce((draftState: any[]) => {
  draftState.push({todo: "Tweet about it"})
  draftState[1].done = true
})(baseState)

console.log(baseState.length === 2)
console.log(nextState.length === 3)

console.log(baseState[1].done === false)
console.log(nextState[1].done === true)

console.log(nextState[0] === baseState[0])
console.log(nextState[1] !== baseState[1])

produce((draftState: {x : number }) => {
  draftState.x = 10
})({ x: 0 })

produce-simple.js

js
const clone = json => JSON.parse(JSON.stringify(json))

const produce = (state, recipe) => {
  const clonedState = clone(state)
  recipe(clonedState)
  return clonedState
}

const baseState = [
  {
    todo: "Learn typescript",
    done: true
  },
  {
    todo: "Try immer",
    done: false
  }
]

const nextState = produce(baseState, (draftState) => {
  draftState.push({todo: "Tweet about it"})
  draftState[1].done = true
})

console.log(baseState.length === 2) // true
console.log(nextState.length === 3) // true

console.log(baseState[1].done === false) // true
console.log(nextState[1].done === true) // true

console.log(nextState[0] === baseState[0]) // false
console.log(nextState[1] !== baseState[1]) // true

persistant-unit/linked-list.js

js
/*
base: 오리지날 데이터
state: linkedList 아이템
list: state[]
 */

const toLinkedListItem = (base, parent = null, propName = null) => {
  return {
    base,
    parent,
    propName,
    copy: null,
  }
}

const toLinkedList = (base, parent = null, propName = null, list = []) => {
  const state = toLinkedListItem(base, parent, propName)

  list.push(state)

  for (const propName in base) {
    if (typeof base[propName] === 'object') {
      toLinkedList(base[propName], state, propName, list)
    }
  }

  if (parent) {
    return state
  } else {
    return list
  }
}

/*
첫번째 아이템이 copy가 있으면 변경된 것을 의미함
 */
const changeLinkedList = (state, propName, value) => {
  if (state.copy) {
    state.copy[propName] = value
  } else {
    state.copy = Object.assign({}, state.base, {[propName]: value})
  }

  if (state.parent) {
    changeLinkedList(state.parent, state.propName, state.copy)
  }
}

const toBase = (list) => {
  return list[0].copy ? list[0].copy : list[0].base
}

persistant-unit/proxy.js

js
const createProxy = target => {
  const handler = {
    get (target, key) {
      console.log('GET', key)
      return target[key]
    },
    set (target, key, value) {
      console.log('SET', key, value)
      target[key] = value
    }
  }
  return new Proxy(target, handler)
}

const target = {
  message: ''
}
const proxy = createProxy(target)

proxy.message = 'World!'
console.log(proxy.message)

console.group('Destructuring')
const {message} = proxy
console.log(message)
console.groupEnd()

persistant-unit/proxy-all.js

js
const createProxy = (state, revokes) => {
  const handler = {
    get (target, key) {
      const value = target[key]
      if (typeof value === 'object') {
        const {proxy} = createProxy(value, revokes)
        return proxy
      } else {
        return value
      }
    },
    set (target, key, value) {
      console.log('SET', key, value)
      target[key] = value
    }
  }

  const {proxy, revoke} = Proxy.revocable(state, handler)
  revokes.push(revoke)
  return {proxy, revoke, revokes}
}

const proxyAll = (base, fn) => {
  const {proxy, revokes} = createProxy(base, [])

  fn(proxy)
  revokes.forEach(fn => fn())

  return base
}

persistant-unit/proxy-revoke.js

js
const createProxy = target => {
  const handler = {
    get (target, key) {
      console.log('GET', key)
      return target[key]
    },
    set (target, key, value) {
      console.log('SET', key, value)
      target[key] = value
    }
  }
  return Proxy.revocable(target, handler)
}

const target = {
  message: ''
}
const {proxy, revoke} = createProxy(target)

proxy.message = 'World!'
console.log(proxy.message)

revoke()

console.log(proxy.message)

persistant/produce.js

js
const isArray = value => Array.isArray(value)
const canProduce = value => {
  return value === undefined || value === null ?
    false :
    isArray(value) || typeof value === 'object'
}

const assign = (...obj) => Object.assign(...obj)

const shallowCopy = obj => {
  if (!canProduce(obj)) return obj
  if (isArray(obj)) return obj.concat()
  return assign({}, obj)
}

const toLinkedListItem = (base, parent = null, propName = null) => {
  return {
    base,
    parent,
    propName,
    copy: null,
  }
}

const toBase = (state) => {
  return state.copy ? state.copy : state.base
}

const changeLinkedList = (state, propName, value) => {
  const nextValue = {[propName]: value}

  state.copy ?
    assign(state.copy, nextValue) :
    assign(state, {
      copy: assign(shallowCopy(state.base), nextValue)
    })

  if (state.parent) {
    changeLinkedList(state.parent, state.propName, state.copy)
  }
}

const createProxy = (base, revokes, parentState, propName) => {
  const state = toLinkedListItem(base, parentState, propName)
  const handler = {
    get (target, key) {
      const value = toBase(state)[key]
      if (canProduce(value)) {
        const {proxy} = createProxy(value, revokes, state, key)
        return proxy
      } else {
        return value
      }
    },
    set (target, key, value) {
      changeLinkedList(state, key, value)
    }
  }

  const {proxy, revoke} = Proxy.revocable(base, handler)
  revokes.push(revoke)
  return {proxy, revoke, revokes, state}
}

const produceBase = (base, fn) => {
  const {proxy, revokes, state} = createProxy(base, [])

  fn(proxy)
  revokes.forEach(fn => fn())

  return toBase(state)
}

const produce = (fn) => (base) => {
  return canProduce(base) ? produceBase(base, fn) : base
}


module.exports = {produce}

persistant-merge/produce.js

js
const toLinkedListItem = (base, parent = null, propName = null) => {
  return {
    base,
    parent,
    propName,
    copy: null,
  }
}

const changeLinkedList = (state, propName, value) => {
  if (state.copy) {
    state.copy[propName] = value
  } else {
    state.copy = Object.assign({}, state.base, {[propName]: value})
  }

  if (state.parent) {
    changeLinkedList(state.parent, state.propName, state.copy)
  }
}

const createProxy = (base, revokes, parentState, propName) => {
  const state = toLinkedListItem(base, parentState, propName)
  const handler = {
    get (target, key) {
      const value = state.copy ? state.copy[key] : state.base[key]
      if (typeof value === 'object') {
        const {proxy} = createProxy(value, revokes, state, key)
        return proxy
      } else {
        return value
      }
    },
    set (target, key, value) {
      console.log('SET', target, key, value)
      changeLinkedList(state, key, value)
    }
  }

  const {proxy, revoke} = Proxy.revocable(base, handler)
  revokes.push(revoke)
  return {proxy, revoke, revokes, state}
}

const produce = (base, fn) => {
  const {proxy, revokes, state} = createProxy(base, [])

  fn(proxy)
  revokes.forEach(fn => fn())

  return state.copy ? state.copy : state.base
}

persistant-oop/produce.js

js
const isArray = value => Array.isArray(value)
const assign = (...obj) => Object.assign(...obj)
const canProduce = value => {
  return value === undefined || value === null ?
    false :
    isArray(value) || typeof value === 'object'
}
const shallowCopy = obj => {
  return !canProduce(obj) ?
    obj :
    isArray(obj) ?
      obj.concat() :
      assign({}, obj)
}

class LinkedList {
  constructor(base, parent, propName) {
    this.base = base
    this.parent = parent
    this.propName = propName
    this.copy = null
  }
  toBase() {
    return this.copy || this.base
  }
  changeLinkedList (propName, value) {
    const nextValue = {[propName]: value}

    this.copy ?
      assign(this.copy, nextValue) :
      assign(this, {
        copy: assign(shallowCopy(this.base), nextValue)
      })

    if (this.parent) {
      this.parent.changeLinkedList(this.propName, this.copy)
    }
  }

  static create(base, parent = null, propName = null) {
    return new LinkedList(base, parent, propName)
  }
}

class LinkedListProxy {
  constructor(base, parentState, propName) {
    const {proxy, revoke} = this.createProxy(base)
    this.proxy = proxy
    this.revokeFn = revoke
    this.state = LinkedList.create(base, parentState, propName)
    this.children = []
  }
  createProxy(base) {
    return Proxy.revocable(base, {
      get: (...args) => this.getter(...args),
      set: (...args) => this.setter(...args)
    })
  }
  getter(target, propName) {
    const value = this.toBase()[propName]
    return canProduce(value) ?
      this.createChildProxy(value, propName) :
      value
  }
  createChildProxy(value, propName) {
    const child = LinkedListProxy.create(value, this.state, propName)
    this.children.push(child)
    return child.proxy
  }
  setter(target, propName, value) {
    this.state.changeLinkedList(propName, value)
  }
  revoke() {
    this.revokeFn()
    this.children.forEach(child => child.revoke())
  }
  toBase() {
    return this.state.toBase()
  }
  toProxy() {
    return this.proxy
  }
  static create(base, parentState, propName) {
    return new LinkedListProxy(base, parentState, propName)
  }
}

const produceBase = (base, fn) => {
  const linkedListProxy = LinkedListProxy.create(base)

  fn(linkedListProxy.toProxy())
  linkedListProxy.revoke()

  return linkedListProxy.toBase()
}

const produce = (fn) => (base) => {
  return canProduce(base) ? produceBase(base, fn) : base
}


module.exports = {produce}