Vue3学习之手写一个简单的初始化流程

268 阅读2分钟

这是我参与更文挑战的第2天,活动详情查看: 更文挑战

挺早之前就和村长学习了vue3的初始化流程剖析,奈何时间总是太少,除了学习完的第二天自己敲了几遍,之后都没有再次复习、总结、归纳,还要外力引导才霸蛮总结一次,惭愧惭愧...

首先,测试用例来一发,最后自己手写的初始化流程能跑通就算OK

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue3 初始化流程学习</title>
</head>
<body>
  <div id="app">
    {{title}}
  </div>
  <!--
    注释官方的vue3文件,放上我们自己写的mini版vue3文件
    <script src="https://www.unpkg.com/vue@next"></script>
  -->
  <script src="./fvue3.js"></script>
  <script>
    const { createApp } = Vue
    const app = createApp({
      data () {
        return {
          title: 'hello vue3!'
        }
      },
      setup () {
        return {
          title: 'vue3 初始化流程学习!'
        }
      }
    })
    app.mount('#app')
  </script>
</body>
</html>

紧接着对着测试用例去实现Vue对象

  • 实现createApp方法,为了兼容不同平台,重构一下这个方法,在这个方法中调用一个createRender方法,然后在createRender方法中返回真正的createApp方法
  • 在真正的createApp方法中实现mount方法、compile编译方法
  • 在编译方法中返回真正的渲染函数,这里直接返回测试用例中的元素即可
  • 在mount方法中处理数据,并将返回的dom添加到parent里面
  • PS: 还有将数据进行响应式处理,数据变动后驱动视图进行更新,将模板转换为虚拟DOM,对虚拟DOM进行比对以便精准更新,将虚拟DOM转换为真实DOM等等一系列的后面再写吧~
// fvue3.js
const Vue = {
  createApp (options) {
    // 以下方法仅支持在游览器中使用
    const querySelector = selector => {
      // 查找元素
      return document.querySelector(selector)
    }
    const insert = (child, parent, anchor) => {
      // 如果有anchor元素不存在相当于appendChild
      parent.insertBefore(child, anchor || null)
    }
    const clearContent = parent => {
      // 清除内容
      parent.innerHTML = ''
    }
    const createEle = tag => {
      return document.createElement(tag)
    }
    const setContent = (el, content) => {
      el.textContent = content
    }
    // console.log(this)
    // 注意,这里的this是指向window,原因是在创建app实例的时候用的createApp是直接解构赋值的
    // const app = Vue.createApp({...})这样this才会指向Vue对象
    const render = Vue.createRender({
      querySelector,
      insert,
      clearContent,
      createEle,
      setContent
    })
    // 将createApp进行一层封装,以便兼容多平台
    return render.createApp(options)
  },
  createRender ({ querySelector, insert, clearContent, createEle, setContent}) {
    // 返回自定义渲染器
    return {
      createApp (options) {
        // 返回app实例
        return {
          mount (selector) {
            // mount函数,将vue实例挂载到某个元素上
            const parent = querySelector(selector);
            if (!options.render) {
              // createApp选项是否有传入render函数,没有就执行compile函数中的render方法
              // 将被挂载元素中的所有内容传入compile函数中
              options.render = this.compile(parent.innerHTML)
            }
            // 因为vue3兼容vue2中data()的写法,所以判断下要从setup中获取数据还是从data中获取数据
            // setup这种options api优先data
            if (options.setup) {
              this.setupData = options.setup()
            } else {
              this.data = options.data()
            }
            // 对实例做代理, 确定render中数据从哪获取
            const proxy = new Proxy(this, {
              get (target, key) {
                if (key in target.setupData) {
                  return target.setupData[key]
                } else {
                  return target.data[key]
                }
              },
              set (target, key, value) {
                if (key in target.setupData) {
                  target.setupData[key] = value
                } else {
                  target.data[key] = value
                }
              }
            })
            const el = options.render.call(proxy)
            // 追加到宿主元素上去
            clearContent(parent)
            insert(el, parent)
          },
          compile (template) {
            // 简易版没有使用到template,只是实现将测试用例跑通
            return function render () {
              const h3 = createEle('h3')
              setContent(h3, this.title)
              return h3
            }
          }
        }
      }
    }
  }
}