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
。
上述代码在setup
和data
中都返回了title
,那为什么最终是setup
中的生效了呢?这是因为vue3在内部做了一些事情,让vue3可以兼容vue2的options api
写法。在发生冲突 如上述代码中setup
和data
中返回的字段相同时,优先使用setup
中的字段。
下面手写vue3初始化流程时,也会实现兼容options api
。
手写vue3初始化
把上个步骤的代码作为测试用例,采用TDD驱动测试的方式,不再引入vue3的js文件,来自己手写vue3初始化流程,如果实现和上述代码一样的功能就说明我们成功了。
初始化流程整体思路
- 基本结构
creatApp()
mount()
- 挂载 mount
- 编译
compile()
render()
- 兼容
options api
和setup
- 扩展性
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
的参数里面分别有data
和setup
,可以在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>
复制代码