likes
comments
collection
share

手写实现mini-vue3

作者站长头像
站长
· 阅读数 58

前言

xdm,大家好,我是熊猫小弟,一个比较佛系的程序猿,说来惭愧,Vue3都发布一年左右了,我才学习Vue3,最近通过阅读Vue3的源码简单的写了一下Vue3中如何初始化,如何实现响应式,如何更新并diff虚拟dom,分享一下个人的浅薄认知,写的不好请大家轻喷。

实现

初始化(挂载)

const Vue = {
  // 兼容多平台只需要传入平台的特性就可以
  createRenderer({ querySelector }) {
    return {
      // 1. 创建createApp函数,函数返回Vue的实例
      createApp(options) {
        // 2. 返回mount进行挂载
        return {
          // mount方法的作用是将数据状态转换成dom并追加到宿主元素上
          mount(selector) {
            // 1. 获取宿主元素
            const parent = querySelector(selector)
​
            // 1.1 保存setup和data的实例
            if (options.setup) {
              this.setupState = options.setup()
            }
            if (options.data) {
              this.data = options.data()
            }
​
            // 1.2 通过代理的方法判断setup和data的优先级
            this.proxy = new Proxy(this, {
              get(target, key) {
                if (key in target.setupState) {
                  return target.setupState[key]
                } else {
                  return target.data[key]
                }
              },
              set(target, key, value) {
                if (key in target.setupState) {
                  target.setupState[key] = value
                } else {
                  target.data[key] = value
                }
              },
            })
​
            // 2. 渲染
            // 编译template > render, 如果用户写入render直接进行编译,没写的话内部会给template进行编译
            if (!options.render) {
              options.render = this.compile(parent.innerHTML)
            }
            
            this.update = effect(() => {
              const el = options.render.call(this.proxy)
              // 3. 清空并挂载
              parent.innerHTML = ''
              parent.appendChild(el)
            })
​
            this.update()
          },
          // 简单的编译方法low
          compile(template) {
            return function render() {
              const h3 = document.createElement('h3')
              h3.textContent = this.title1
              return h3
            }
          },
        }
      },
    }
  },
​
  createApp(options) {
    const renderer = this.createRenderer({
      querySelector(selector) {
        return document.querySelector(selector)
      },
    })
    return renderer.createApp(options)
  },
}
Vue3.0的响应式更新换了一种方式,换成了副作用依赖,使用new Proxy进行数据代理,在获取的时候进行依赖收集并存放在副作用的队列中,在设置该值的时候在副作用的队列中获取到该值对应的副作用执行更新,这样就会做到精准的更新。

响应式(reactive)

// 实现reactive
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      const res = Reflect.get(target, key)
      return res
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value)
      return res
    },
  })
}

更新

1. 实现副作用

// 实现副作用
const effectStack = []
function effect(fn) {
  const eff = function () {
    try {
      effectStack.push(fn)
      fn()
    } finally {
      effectStack.pop()
    }
  }
​
  eff()
​
  return eff
}

2. 收集依赖

// 实现reactive
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      const res = Reflect.get(target, key)
      // 依赖收集
      track(target, key)
      return res
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value)
      // 通知更新
      trigger(target, key)
      return res
    },
  })
}

3. 建立联系

// 收集依赖的数据结构
// targetMap : {
//   target : {
//     deps: [eff, eff1, eff2]
//   }
// }
const targetMap = new WeakMap()
// 收集依赖
function track(target, key) {
  // 1. 取到最后的一个副作用
  const effect = effectStack[effectStack.length - 1]
​
  if (effect) {
    // 2. 获取target并判断是否存在,不存在就创建
    let depMap = targetMap.get(target)
    if (!depMap) {
      depMap = new Map()
      targetMap.set(target, depMap)
    }
​
    // 3.获取deps并判断是否存在,不存在就创建
    let deps = depMap.get(key)
    if (!deps) {
      deps = new Set()
      depMap.set(key, deps)
    }
​
    // 4. 添加副作用
    deps.add(effect)
  }
}

4. 通知更新

// 找到对应的副作用并执行
function trigger(target, key) {
  // 1. 获取依赖的Map没有不执行
  const depMap = targetMap.get(target)
  if (!depMap) return
​
  const deps = depMap.get(key)
  if (deps) {
    deps.forEach((dep) => dep())
  }
}

5. diff

this.update = effect(() => {
  // const el = options.render.call(this.proxy)
  // // 3. 清空并挂载
  // parent.innerHTML = ''
  // parent.appendChild(el)
​
  // 获取虚拟节点
  const vnode = options.render.call(this.proxy)
  // this.isMounted判断初始化还是更新阶段
  if (!this.isMounted) {
    // 初始化获取元素清空请挂载
    const el = this.createElm(vnode)
    parent.innerHTML = ''
    insert(el, parent)
    this.isMounted = true
  } else {
    // 更新
    this.patch(this._vnode, vnode)
  }
​
  this._vnode = vnode
})
​
// diff
patch(n1, n2) {
  // 获取节点
  const el = (n2.el = n1.el)
  // n1 老节点
  // n2 新节点
  // 只有相同的节点才会比较
  if (n1.tag === n2.tag) {
    const oldCh = n1.children
    const newCh = n2.children
    
     /**
      * 老节点是字符串,新节点有孩子 遍历新节点的children插入到对应节点
      * 老节点有孩子,新节点有孩子,updateChildren
      * 老节点有孩子,新节点是字符串, 直接把老节点替换成新节点
      * 新节点是字符串,老节点是字符串,比较新老节点不一样在更新
     */
    if (typeof oldCh === 'string') {
      if (typeof newCh === 'string') {
        if (oldCh !== newCh) {
          el.textContent = newCh
        }
      } else {
        el.innerHTML = ''
        insert(this.createElm(newCh), el)
      }
    } else {
      if (typeof newCh === 'string') {
        el.textContent = newCh
      } else {
        // 更新孩子操作 —— 先比较头部,在比较尾部,都找不到的话遍历所有字节点使用最长递增子序列快速排序
        this.updatedChildren(oldCh, newCh)
      }
    }
  }
}

结语

xdm,简单的实现了Vue3的基础功能,Vue3不止这些内容,Vue3还设计到了算法思维,编译原理等诸多知识。不仅如此Vue3还在静态节点的提升,缓存black,PatchFlag的判断以及兼容Vue2.x版本做了很多优化。咱们后面慢慢分享。

最后如果文章对你有用,请帮忙点赞,关注 + 评论。

转载自:https://juejin.cn/post/7072992501547663373
评论
请登录