[路飞]_学习Vue3初始化流程

1,165 阅读2分钟

「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

工作中使用Vue3已经有大半年,对一些用法也是比较熟悉了,最近对Vue3的源码进行学习与记录。主要用法就不在这里赘述,详阅官方中文文档,目前Vue3的版本是3.2.x。

Vue3简单体验

<body>
  <div id="app">
    <h3>{{title}}</h3>
    <h3>{{state.title}}</h3>
  </div>
</body>

<script src="http://unpkg.com/vue@next"></script>
<script>
  const {
    createApp,
    reactive
  } = Vue

  const app = createApp({
    // 兼容vue2.0 Options API
    data() {
      return {
        title: '兼容vue2.0'
      }
    },
    // Composition API
    setup() {
      const state = reactive({
        title: 'vue3.0'
      })
      const click = () => {
        console.log('Hi, vue3')
      }
      return {
        state,
        click
      }
    }
  })
  //挂载在dom上
  app.mount('#app')
</script>

关于为什么vue3将Options API改成 Composition API
【Vue3.0】尤雨溪 - 聊聊 Vue.js 3.0 Beta 官方直播完整版
composition api 文档

手动实现初始化

首先将代码修改一下,去掉引入的Vue@next,我们手动来实现

<body>
  <div id="app"></div>
</body>
<script>
  const {
    createApp,
  } = Vue
  const app = createApp({
    // 兼容vue2.0 Options API
    data() {
      return {
        title: '我是data中的title'
      }
    },
    setup() {
      return {
        title: '我是setup中的title'
      }
    }
  })
  app.mount('#app')
</script>
  • 分析
  1. vue3.0不需要实例化Vue,直接使用Vue对象中createApp来构造一个新的对象app来初始化,createApp接收一些配置项options并且返回一个对象,其中有一个mount函数最终将结果挂载到页面上。因此Vue对象看起来应该是这样的
  const Vue = {
    createApp(options) {
      return {
        mount(selector) {}
      }
    }
  }
  1. mount函数接收一个selector参数表示需要挂载在页面上的dom宿主对象,可以新增一个编译的方法compile,涉及编译的内容比较复杂,这里就简单的生成一个dom
  createApp(options) {
    return {
      mount(selector) {
        const parent = querySelector(selector) // 宿主元素
        const el = this.compile().call() // 渲染出来的对象
        parent.insertBefore(el, null)
      },
      compile() {
        return function render() {
          // 假数据
          const h3 = document.createElement('h3')
          h3.textContent = '我是手动写的'
          return h3
        }
      }
    }
  }

这时候刷新页面已经可以看到内容挂载到宿主app

图片.png 3. 在渲染的时候获取数据并插入到正文中,关于数据的响应式将在之后的篇章中单独介绍,vue2.0中是在data中添加数据,可以在执行complie函数时call(data)将data中的数据指向complie,则可以在其中用this使用data中的数据

  createApp(options) {
    return {
      mount(selector) {
        const parent = querySelector(selector)
        // 如果没有手动写的render方法
        if (!options.render) {
          options.render = this.compile(parent.innerHTML)
        }
        this.data = options.data()
        const el = options.render.call(this.data) //关键步骤
        parent.insertBefore(el, null)
      },
      compile(template) {
        return function render() {
          const h3 = document.createElement('h3')
          h3.textContent = this.title
          return h3
        }
      }
    }
  }

这时候已经将data中的数据渲染到了页面上

图片.png 4. vue3.0引入了Composition API的概念,可以在setup中直接返回数据,如果在data中存在相同的属性,默认setup的优先级高于data

    mount(selector) {
      const parent = querySelector(selector)
      // 如果没有手动写的render方法
      if (!options.render) {
        options.render = this.compile(parent.innerHTML)
      }
      if (options.setup)
        this.setupState = options.setup()
      }
      this.data = options.data()
      // 代理中优先使用setup中的属性
      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, val) {
          if (key in target.setupState) {
            target.setupState[key] = val
          } else {
            target.data[key] = val
          }
        }
      })
      const el = options.render.call(this.proxy)
      insert(el, parent)
    },

图片.png

到这里,已经比较粗糙的完成了Vue3的初始化过程,附上优化过的完整代码

<body>
  <div id="app"></div>
</body>
<script>
  const Vue = {
    createApp(options) {
      const render = Vue.createRenderer({
        querySelector(selector) {
          return document.querySelector(selector)
        },
        insert(child, parent, anchor = null) {
          parent.insertBefore(child, anchor)
        }
      })
      return render.createApp(options)
    },
    //不同平台的扩展
    createRenderer({
      querySelector,
      insert
    }) {
      return {
        createApp(options) {
          return {
            mount(selector) {
              const parent = querySelector(selector)
              // 如果没有手动写的render方法
              if(!options.render) {
                options.render = this.compile(parent.innerHTML)
              }
              if (options.setup) {
                this.setupState = options.setup()
              }
              this.data = options.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, val) {
                  if(key in target.setupState) {
                    target.setupState[key] = val
                  } else {
                    target.data[key] = val
                  }
                }
              })
              const el = options.render.call(this.proxy)
              insert(el, parent)
            },
            compile(template) {
              return function render() {
                const h3 = document.createElement('h3')
                h3.textContent = this.title
                return h3
              }
            }
          }
        }
      }
    }
  }

  const {
    createApp
  } = Vue
  const app = createApp({
    // 兼容vue2.0 Options API
    data() {
      return {
        title: '我是data中的title'
      }
    },
    setup() {
      return {
        title: '我是setup中的title'
      }
    }
  })
  app.mount('#app')
</script>

如果你喜欢这篇文中麻烦点个赞
如果文中有错误的地方,麻烦各位大佬指正
--- end---