Vue3是怎样实现初始化的?手写实现vue3初始化流程

vue3优化了哪些地方/解决了什么问题?

  • ts类型支持友好(createApp等 函数式编程)
  • tree-shaking(use()使用插件,没有用到不会被打包)
  • API简化、⼀致性:render函数,sync修饰符,指令定义等
  • composition api 组织代码、复用性
  • 性能优化:响应式(Object.defineProperty() -> Proxy)、编译优化
  • 扩展性:⾃定义渲染器 (customRenderer)

Vue3初始化

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

    <script src="https://unpkg.com/vue@next"></script>
    <script>
      const { createApp } = Vue;
      const app = createApp({
        data() {
          return {
            title: "option api",
          };
        },
        setup() {
          // 无reactive响应式
          const state = {
            title: "composition api",
          };
          return state;
        },
      });
      app.mount("#app");
    </script>
复制代码

上面的代码实现了一个最小的vue3程序,运行到浏览器后 可以看到页面中显示的是composition api

上述代码在setupdata中都返回了title,那为什么最终是setup中的生效了呢?这是因为vue3在内部做了一些事情,让vue3可以兼容vue2的options api写法。在发生冲突 如上述代码中setupdata中返回的字段相同时,优先使用setup中的字段。

下面手写vue3初始化流程时,也会实现兼容options api

手写vue3初始化

把上个步骤的代码作为测试用例,采用TDD驱动测试的方式,不再引入vue3的js文件,来自己手写vue3初始化流程,如果实现和上述代码一样的功能就说明我们成功了。

初始化流程整体思路

  • 基本结构 creatApp() mount()
  • 挂载 mount
  • 编译 compile() render()
  • 兼容options apisetup
  • 扩展性 createRenderer

基本结构

根据测试用例可以知道,Vue对象内会有一个createApp创建Vue实例,然后mount方法负责初始化。代码如下:

    <script>
        const Vue = {
            createApp(options){
                // 返回一个App实例
                return {
                    mount(selector){
                        // 1.获取宿主元素
                        // 2.渲染
                        // 3.追加
                    }
                }
            }
        }
    </script>
复制代码

options参数作为配置对象被传入,selector参数为宿主元素。

挂载和编译渲染

<script>
	const Vue = {
		createApp(options) {
			// 返回一个App实例
			return {
				mount(selector) {
					// 1.获取宿主元素
					const parent = document.querySelector(selector);
					// 2.渲染(检查到测试用例没有写render渲染函数)
					if(!options.render){
						options.render = this.compile(parent.innerHTML);
					}
					// 3.执行render获取视图并追加
					const el = options.render.call(options.data());
					parent.innerHTML = '';
					parent.appendChild(el);
				},
				compile(template) {
					// compile的目的是得到render
					return function render(){
						const h3 = document.createElement('h3');
						h3.textContent = this.title;
						return h3;
					}
				}
			};
		},
	};
</script>
复制代码

渲染的时候发现没有写render渲染函数,这时就需要调用compile()方法。实际上compile()会将template转换为render函数,这个过程很复杂,所以在本文中我们直接让它返回h3标签元素,把这个步骤简化,本文主要实现初始化的过程。

上面的代码已经实现了vue3的初始化过程,运行测试用例 观察到页面显示了option api。但是 vue3的setup优先级应该是高于options api的,应该让页面显示composition api。所以 下一步来实现兼容vue2的options api 当两者同时存在的时候,让setup生效。

兼容vue2的options api

根据测试用例可以发现,createApp的参数里面分别有datasetup,可以在mount()中做一些判断,代码如下:

// 1.获取宿主元素
// ...
// 2.渲染(检查到测试用例没有写render渲染函数)
// ...

// 兼容data setup优先级问题
if(options.setup){
	// 保存setup结果在app实例上面
	this.superState = options.setup();
}
// 如果有vue2的data也要处理
if(options.data){
	this.data = options.data();
}
// 构造一个render函数的上下文
this.proxy = new Proxy(this, {
	get(target, key){
            // 如果key在setupState中存在就用这个,否则就用data中的
            if(key in target.superState){
			return target.superState[key]
		} else {
			return target.data[key]
		}
	},
	set(target, key, val){
		// 本文中不做更新
	}
})
// 3.执行render获取试图并追加
// const el = options.render.call(options.data());
const el = options.render.call(this.proxy);
parent.innerHTML = '';
parent.appendChild(el);
复制代码

上述代码 通过new Proxy代理构造了一个render函数的上下文,这样render函数要用到的上下文就交给了proxy处理,就这样实现了兼容写法。

createRenderer 扩展性

为了给第三方平台(类似uniapp)提供扩展性,vue3中实现了一个自定义渲染器API createRenderer,使得在不同的平台可以做不同的操作。

createRenderer提供了一个可以传入参数的机会,改写上述的示例假设querySelector是第三方平台特有的方法,将它传给createRenderer实现自定义定制。

改写成具有createRenderer可扩展性的全部代码如下:

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

<script>
	const Vue = {
		// 提供新的渲染器PAI
		createRenderer({ querySelector }) {
		// 给用户返回一个渲染器对象renderer
		// 用户可以传入平台特性操作
			return {
			// 返回的方法其实就是createApp
				createApp(options) {
					return {
                                            mount(selector) {
							const parent = querySelector(selector);
							if (!options.render) {
								options.render = this.compile(parent.innerHTML);
							}
							if (options.setup) {
								this.superState = options.setup();
							}
							if (options.data) {
								this.data = options.data();
                                                    }
							this.proxy = new Proxy(this, {
								get(target, key) {
									if (key in target.superState) {
										return target.superState[key];
									} else {
										return target.data[key];
									}
								},
								set(target, key, val) {
									// 本文中不做更新
								},
							});
							const el = options.render.call(this.proxy);
							parent.innerHTML = "";
							parent.appendChild(el);
						},
						compile(template) {
							return function render() {
								const h3 = document.createElement("h3");
								h3.textContent = this.title;
								return h3;
							};
						},
					};
				},
			};
		},
		// 给web平台提供一个createApp
		createApp(options) {
			const renderer = this.createRenderer({
				querySelector(selector) {
					return document.querySelector(selector);
				},
			});
			return renderer.createApp(options);
		},
	};
</script>
<script>
	const app = Vue.createApp({
		data() {
			return {
				title: "option api",
			};
		},
		setup() {
			// 无reactive响应式
			const state = {
				title: "composition api",
			};
			return state;
		},
	});
	app.mount("#app");
</script>
复制代码

相关文章

vue3相关文章

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享