vue3.o源码分析
# 前言
调研的预览版本,很多api还没完善,但大致架构已经成形, 暂时还不能用于生产。
优势
- 函数式编程,函数对ts兼容性好【react-hooks】
- static tree hoisting 功能 (检测静态语法,进行提升) 【diff】
- 基于 Proxy 实现的数据变更检测 【不需要递归了,节省内存】
- 支持 Fragments 【 react 空标签,当根元素用】
- 支持 Portals 【react 允许在DOM的其它位置进行渲染】
- 同时支持 Composition API 和 Options API 【单文件兼容vue2.o】
- Custom Renderer API 【自定义渲染器API】
# 源码目录
├── packages
│ ├── compiler-core # 所有平台的编译器(weex也是基于这个)
│ ├── compiler-dom # 针对浏览器的编译器
│ ├── compiler-ssr # ssr
│ ├── compiler-sfc # 针对单文件
│ ├── reactivity # 数据响应式系统
│ ├── runtime-core # 渲染器,一些核心的api
│ ├── runtime-dom # 针对浏览器的runtime,包括处理原生DOM
│ ├── runtime-test # 专门为测试写的runtime
│ ├── server-renderer # 用于SSR
│ ├── shared # 帮助方法
│ ├── template-explorer
│ └── vue # 构建vue
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# composition-api
Composition API纯粹是添加的,不会影响/弃用任何现有的2.x API,它是可以单独导入到项目中的
动机
- Vue 3 使用ts实现了类型推断,新版api全部采用普通函数,在编写代码时可以享受完整的类型推断(避免使用装饰器)
- 解决了多组件间逻辑重用问题 (解决:高阶组件、mixin、作用域插槽)
- 如果项目过于复杂的时候,mixin中的代码和外部组件的代码存在命名冲突的时候会被覆盖,而且如果有相同的生命周期函数也会被覆盖,所以会导致代码难以维护,容易出现bug
- 代码分散
// 安装
yarn add @vue/composition-api
// 使用
import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';
Vue.use(VueCompositionApi);
# reactive
# Object.defineProperty()
let oldProtoMehtods = Array.prototype;
let proto = Object.create(oldProtoMehtods);
// 针对数组
['push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'].forEach(method => {
proto[method] = function () { // 函数劫持,把内部的函数重写,数组方法还是继续调用老的方法
updateView();
observer(...arguments);
oldProtoMehtods[method].call(this, ...arguments)
}
});
function observer(target) {
if (!isObject(target)) return target;
if (Array.isArray(target)) {
target.__proto__ = proto; // 同上
// 给数组中的每一项进行observer
for (let i = 0; i < target.length; i++) {
observer(target[i])
}
return
}
for (let key in target) {
defineReactive(target, key, target[key])
}
}
function defineReactive(target, key, value) {
observer(value); // 有可能对象类型是多层,递归劫持
Object.defineProperty(target, key, {
get() {
// 在get 方法中收集依赖
return value
},
set(newVal) {
console.log('set方法', newVal);
if (newVal !== value) {
updateView(); // 在set方法中触发更新
observer(newVal); // 防止传进来是一个二级对象,不能对以后的数据监测
}
}
})
}
let data = {ary: [1, 2, {age: 22}]};
let result = observer(data);
data.ary.push({age: 33});
data.ary.push([1, 2, 3]);
data.ary[4].push(1);
data.ary[4].splice(1, 1, 22222);
// data.ary[4][1] = 2222; 不会触发更新,值会改变
console.log(data.ary);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
缺点
- 无法监听数组的变化
- 需要递归深度遍历,浪费内存
// 无法响应式
vm.items[indexOfItem] = newValue
vm.items.length = newLength
// 解决办法
vm.$set(vm.items, indexOfItem, newValue)
vm.items.splice(indexOfItem, 1, newValue)
vm.items.splice(newLength)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# Proxy
1
# effect
# ref
下面是自己提炼的的核心代码,源码分析看这
// ref
const convert = (val) => {
return isObject(val) ? reactive(val) : val
};
function ref(rawValue) {
let value = convert(rawValue);
const result = {
__v_isRef: true,
get value() {
return value
},
set value(newVal) {
console.log('视图更新');
value = convert(newVal);
}
};
return result
}
// ---------------
// toRefs
function toRefs(object) {
const ret = {};
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}
function toRef(object, key) {
return {
__v_isRef: true,
get value() {
return object[key]
},
set value(newVal) {
object[key] = newVal
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# computed
# Beta升级使用
写demo可以,公司正式项目不建议,后期少不了小改动
# 安装3.o新版本
yarn add vue@next vue-router@next vuex@next yarn add @vue/compiler-sfc@next eslint-plugin-vue@next vue-cli-plugin-vue-next -D
# 逻辑复用
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
// 在组件中使用:
import { useMousePosition } from './mouse'
export default {
setup() {
return { ...useMousePosition() }
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 参考文档
vue-next (opens new window) vue-cli-plugin-vue-nex (opens new window) vue-composition-api文档 (opens new window) vue3的编译工具 (opens new window) 尤雨溪在Vue3.0Beta直播里聊到了这些 (opens new window) juejin (opens new window) segmentfault (opens new window)