Mobx

May 24, 2022

build your own mobx

以下是一段 mobx 的简单使用例子

const store = observable({ a: 1, b: { c: 1 } })

autorun(() => {
  if (store.a === 2) {
    console.log(store.b.c)
  }
})

store.a = 2 // 1
store.b.c = 5 // 5
store.b.c = 6 // 6

上面这个例子中,修改传入 observable 的对象的某个属性,会自动触发 autorun 中使用了该属性的方法

当属性被使用时,我们需要搜集使用该属性的方法;当该属性被赋值时,我们需要调用使用了该属性的方法,因此首先我们需要一个收集方法与调用方法的工具

class EventEmitter {
  list = new WeakMap()
  // 当属性被使用时,以该属性值 key,将使用了该属性的 fn 存放入数组中
  on(obj, fn) {
    let targetEvents = this.list.get(obj)
    if (!targetEvents) {
      targetEvents = []
      this.list.set(obj, targetEvents)
    }

    if (!targetEvents.includes(fn)) {
      targetEvents.push(fn)
    }
  }
  // 当该属性被赋值时,依次调用 key 为该值的数组存放的方法
  emit(obj, ...args) {
    const targetEvents = this.list.get(obj)
    if (targetEvents) {
      const fns = targetEvents
      if (fns && fns.length > 0) {
        fns.forEach((fn) => {
          fn && fn(...args)
        })
      }
    }
  }
}

接下来,我们就需要监听 observable 传入的对象的使用与赋值,并且还要与 autorun 的方法相关联

const em = new EventEmitter()
let currentFn

const autorun = (fn) => {
  const warpFn = () => {
    currentFn = warpFn
    fn() // 依赖收集
    currentFn = null
  }
  warpFn()
}

const observable = (obj) => {
  return new Proxy(obj, {
    get: (target, key) => {
      if (typeof target[key] === 'object') {
        return observable(target[key])
      } else {
        // 如果有当前执行的函数,则进入依赖收集
        if (currentFn) {
          em.on(target, currentFn)
        }
        return target[key]
      }
    },
    set: (target, key, value) => {
      if (target[key] !== value) {
        target[key] = value
        em.emit(target, key)
      }
      return true
    }
  })
}