vue3源码-初始化原理解析

395 阅读3分钟

vue3的架构


vue3整体分为三个部分:程序运行时模块、响应式模块、程序编译模块,咱们今天就只先看程序运行时模块,看一下vue3在初始化时的一个流程。

\

克隆vue-next代码


git clone https://github.com/vuejs/vue-next.git

安装依赖:(node >= 12.0.0)

yarn --ignore-script

修改package.json生成sourcemap

"dev": "node scripts/dev.js --sourcemap",

启动

yarn dev

createApp


vue3初始化项目不是new Vue了,而是使用createApp().mount()这样的方式,所以要了解初始化流程 必然得先去找到createApp这个函数;

找到packages/runtime-dom/src/index.ts文件:

其中app就是应用程序的实例,是由ensureRenderer()方法创建的,还提供了一个mount方法使用。

接着去看ensureRenderer()方法:

ensureRenderer方法会看这个renderer有没有创建,如果创建直接返回,没有的话就调用createRenderer这个方法去创建renderer。这个renderer就是实例真正的创建者。所以又要去renderer.ts看createRenderer()这个方法了

createRenderer里面又返回了一个baseCreateRenderer这个方法,这个方法是一个工厂函数,总共1800多行代码,里面的东西都是渲染的核心代码,从平台特性 options 取出相关 API,实现了 patch、处理节点、处理组件、更新组件、安装组件实例等等方法,最终返回了一个对象,这里咱们重点关注他return了什么

看到这大概就清除了,其实createAppAPI才是应用程序创建的地方。

这里面有个app就是那个应用程序实例,是个对象里面有些属性。里面有些熟悉的面孔,比如use,mixin,component,directive。这里就体现了跟vue2的区别:

    1. 之前vue2都是静态方法,容易被全局配置给污染,现在实例方法就不会了。
    2. vue2没用的一些方法也会打包进去,vue3就不会了。
    1. 逻辑上来说创建了一个实例去调用实例里的方法是符合逻辑的。还可以链式调用。

\

现在进到createAppAPI方法里面看看mount方法:

这个render函数的作用其实就是将虚拟dom转变为真实dom。这个render方法是createAppAPI这个方法的参数,所以又要回去renderer.ts找render方法了

这里又调用了一个patch方法,当我们没挂载过时第一个参数会传null,再传我们的vnode和container进去

这个方法判断了我们传进去的类型。那我怎么判断我初始化它会走哪个类型呢。它判断我们传进来的是component。所以就跟着流程走下去,看setupRenderEffect这个方法

这里计算出当前dom树对应的vnode,然后传进patch函数,这个patch函数就是转换虚拟dom的函数。

到此,整个初始化流程就结束了。

简版的初始化代码


首先在vue/examples里面创建一个1.html

<!--
 * @Author: xie bin
 * @Date: 2021-05-25 17:55:42
 * @LastEditTime: 2021-05-25 18:06:56
 * @LastEditors: xie bin
 * @Description: 
 * @FilePath: /vue3/vue-next/packages/vue/examples/1.html
-->

<div id="app">
    <div>{{text}}</div>
</div>

<script>

Vue.createApp({
  data() {
    return {
      text: 'hello world !'
    }
  }
}).mount('#app')
</script>

第一步,我们需要个vue,里面有个createApp方法,这个方法根据看的源码其实就是返回renderer这个实例里的createApp方法.

// 1.创建一个vue,里面有一个createApp方法
const Vue = {
    createApp(options) {
        return renderer || renderer.createApp(options)
    }
}

第二步,声明render,这里可以看出不止可以操作dom,它可以干任何事,取决于你传啥进去。其实可以自定义操作的

// 2.声明renderer
const renderer = createRenderer({})

第三步,定义createRenderer函数

// 3.定义createRenderer函数
const createRenderer = options => {
    const render = (vnode, container) => {

    }
    return {
        render,
        createApp: createAppAPI(render)
    }
}

第四步, 定义createAppAPI方法

// 4.定义createAppAPI方法
const createAppAPI = render => {
    return function createApp(root) {
        
    }
}

现在整体的一个结构已经完了。

在createApp方法内真正创建app的是renderer。然后renderer是由createRenderer这个方法创建的。它返回了一个render和createApp。这个createApp呢又是createAppApi这个方法创建的。这个方法接受到render之后,创建一个app,定义mount方法。mount将来会调用render函数。将vnode转换为真实dom。

完整代码

<!--
 * @Author: xie bin
 * @Date: 2021-05-25 17:55:42
 * @LastEditTime: 2021-05-26 11:41:45
 * @LastEditors: xie bin
 * @Description: 
 * @FilePath: /vue3/vue-next/packages/vue/examples/1.html
-->

<div id="app">
</div>

<script>


// 4.定义createAppAPI方法
const createAppAPI = render => {
    return function createApp(root) {
        const app = {
            mount(container) {
                // 实际上vnode应该是跟组件render函数的返回值
                const vnode = {
                    tag: 'h2',
                    props: null,
                    children: root.data().text
                }

                render(vnode, document.querySelector(container));
            }
        };
        return app
    }
}

// 3.定义createRenderer函数
const createRenderer = options => {
    const render = (vnode, container) => {
        // 解析vnode => dom
        // 常见vnode对应真实dom
        const child = options.createElement(vnode.tag);
        if (typeof vnode.children === 'string') {
            child.textContent = vnode.children;
        } else {
            // 递归,省略了多个子元素
        }
        // 插入节点
        options.insert(child, container);
    }
    return {
        render,
        createApp: createAppAPI(render)
    }
}

// 2.声明renderer
const renderer = createRenderer({
    createElement(tag) {
        return document.createElement(tag);
    },
    insert(child, container) {
        container.appendChild(child);
    }
})

// 1.创建一个vue,里面有一个createApp方法
const Vue = {
    createApp(options) {
        return renderer.createApp(options)
    }
}

Vue.createApp({
  data() {
    return {
      text: 'hello world !'
    }
  }
}).mount('#app')
</script>