JSFiddle - React, Tailwind, and code Playground

by jin5354

HTML

<script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>

Babel + JSX

// more detail go to 
// https://github.com/jin5354/leaf-observable

let uid = 1
class Dep {

  constructor() {
    this.id = uid++ // 为每个 dep 标记一个 uid
    this.subs = []
  }

  addSub(sub) {
    this.subs.push(sub)
  }

  depend() { // 依赖收集函数,在 getter 中执行,在 Dep.target 上找到当前 watcher,并添加依赖
    Dep.target && Dep.target.addDep(this)
  }

  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }

}
Dep.target = null // Dep.target 用来暂存正在收集依赖的当前 watcher

// 现在 Wactcher 接收三个参数,第一个为依赖收集函数(如上文的 render),第二个为回调,第三个为附加配置
class Watcher {

  constructor(expFn, cb, options = {}) {
    this.context = options.context
    this.expFn = expFn
    this.depIds = new Set()  //标记当前 watcher 已经加入到了哪些 dep
    this.cb = cb
    this.value = this.subAndGetValue()
    this.clonedOldValue = _.cloneDeep(this.value)
  }

  // 执行回调
  update() {
    let value = this.subAndGetValue() //获取 newValue
    if(!_.isEqual(value, this.clonedOldValue)) {
      this.value = value
      this.cb.call(this.context, value, this.clonedOldValue)
      this.clonedOldValue = _.cloneDeep(value) // 缓存本次结果,会成为下次的 oldValue
    }
  }

  //执行依赖收集函数,订阅依赖!
  subAndGetValue() {
    Dep.target = this // 把当前 watcher 放到 Dep.target 上,这样 getter 就知道应该把哪个 watcher 加入 dep 中了。
    let value = this.expFn.call(this.context)
    Dep.target = null // 订阅完置回空。
    return value
  }

  // 在 dep 上添加订阅
  addDep(dep) {
    if(!this.depIds.has(dep.id)) { //防止重复订阅,防止在一个 dep 中订阅两次
      this.depIds.add(dep.id)
      dep.addSub(this)
    }
  }
}

//简单封装下 new Watcher
function watch(expFn, cb, context) {
  return new Watcher(expFn, cb, context)
}

// 使一个对象响应化
function observify(value) {
  if(!isObject(value)) {
    return
  }
  if(Array.isArray(value)) {  // 由于性能问题,我们不再对数组的每个 key 执行 Object.defineReactive
    observifyArray(value)
    for(let i = 0; i < value.length; i++) {
      observify(value[i])
    }
  }else {
    Object.keys(value).forEach((key) => {
      defineReactive(value, key, value[key])  // 遍历每个键使其响应化
    })
  }
}

function...