VUE3核心源码

一、vue-next 源码

结构目录

lerna

npm i lerna

管理包的工具

二、手写reactivity响应式模块

  • @vue/reactivity响应式模块
  • reactive把对象变为响应式对象
  • effect 副作用 effect里的变量发生变化时默认重新执行
  • computed 计算属性
  • ref

使用模块 use reactivity

1
2
3
4
5
6
7
8
9
10
11
// import { reactive, effect, computed, ref} from '@vue/reactivity'
// 手写
import { reactive, effect, computed, ref} from './index.js'
const state = reactive({name:'zf',age:'30'});
effect(()=>{ //默认执行一次 改变后又执行一次
console.log(state.name);
})
state.name = 'jw';

// watchEffect 基于effect (批量更新的策略)
// vue3兼容vue2

手写reactive.js

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
export function reactive(target){
// 创建响应式对象 目标对象不一定是数组 或者 对象
return createReactiveObject(target,{
get(target,key,receiver){ // proxy + reflect ES6 API
const res = Reflect.get(target,key,receiver);// === target[key]
track(target,'get');
if(isObject(res)){
return reactive(res);
}
return res;
},
set(){
// 需要判断修改的属性还是增加的属性 如果是原来的值则不做任何操作
const hadKey = Object.prototype.hasOwnPrototype.call(target,key);
const oldValue = target[key];
const res = Reflect.set(target,key,receiver);// === target[key]

if(!hadKey){
console.log('新增操作');
trigger(target,'add',key,value); //触发依赖新增
}else if(value !== oldValue){
console.log('修改操作');
trigger(target,'set',key,value); //触发依赖更新
}


return res;
}
// 除了代理这些方法之外 可能还有很多逻辑
});
}

function createReactiveObject(target,baseHandler){
if(!isObject(target)){
return target;
}
//是对象 根据当前对象进行代理
const observed = new Proxy(target,baseHanlder);
return observed;
}

computed.js

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
export function computed(getterOrOptions){
let getter;
let setter;

if(isFunction(getterOrOptions)){
getter = getterOrOptions
setter = () =>{}
}else{
getter = getterOrOptions.get;
setter = getterOrOptions.get;

let dirty = true; //默认第一次取值执行getter

let computed;
// 计算属性也是一个effect
let value;
let runner = effect(getter,{
lazy:true,// 懒加载
computed:true, //仅仅是个标识是个计算属性而已
scheduler:() =>{
if(!dirty) {
dirty = true; // 等会就算属性依赖的值发生变化后 就会执行这个scheduler
trigger(computed,TriggerOpTypes.SET,'value')
}
}
})
let value;
computed = {
get value(){
if(dirty) { //多次取值不会重新执行effect
value = runner();
dirty = false;
track(computed,TrackOpTypes.GET,'value')
}
return value;
},
set value(newValue){
setter(newValue);
}
}
return computed;
}

ref.js

1
2
3
export function ref(){

}

effect.js

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
export function effect(fn,options = {}){
const effect = createReactiveEffect(fn,options);
if(!optinos.lazy){
effect(); //默认就要执行
}
return effect;
}
// 创建响应式的方法
let uid = 0;
let activeEffect;
const effectStack =[]; // 栈结构

function createReactiveEffect(fn,options){
const effect = function reactiveEffect(){
if(!effectStack.includes(effect)){ // 避免死循环
try{
effectStack.push(effect);
activeEffect = effect;// 将effect放到activeEffect
return fn();
}finally{
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
}
effect.options = options;
effect.id = uid++;
effect.deps = [];
return effect;
}

const targetMap = new WeakMap(); // 用法和MAP一致 但弱引用 不会导致内存泄露
export function track(target,type,key){
if(activeEffect === undefined){
return; //说明 取值的属性不依赖于effect
}
let depsMap = targetMap.get(target);

if(!depsMap){
targetMap.set(target,(depsMap = new Map()))
}
let dep = depsMap.get(key)
if(!dep){
depsMap.set(key,(dep = new Set()))
}
if(!dep.has(activeEffect)){
dep.add(activeEffect)
acctiveEffect.deps.push(dep)
}
}
export function trigger(target,type,key,value,oldValue){
let depsMap = targetMap.get(target); // 获取当前对应的map

if(!depsMap){
return
}
// 计算属性要优先于effect执行
const effects = new Set();
const computedRunners = new Set();

const add =(effectsToAdd) =>{
if(effectsToAdd){
effectsToAdd.forEach(res=>{
if(res.options.computed){
computedRuners.push(effect);
}else{
effects.add();
}
})
}
}

//const run = (effects){
// if(effects) effects.forEach(res => effect())
//}

if(key !== null){
add(depsMap.get(key))
}

if(key === 'add'){
// 数组新增属性,会触发length 对应的依赖 在取值的时候回对length属性进行依赖收集
add(depsMap.get(Array.isArray(target)?'length':''))
}
const run = (effect) => {
if (effect.options.scheduler) {
effect.options.scheduler();
} else {
effect();
}
}
computedRunners.forEach(run);
effects.forEach(run);
}

index.js 整合以上模块

1
2
3
4
export {computed} from './computed'
export {effect} from './effect'
export {ref} from './ref'
export {reactive} from './reactive'

三、手写createApp()

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// import { createApp } from 'vue';  // 高阶函数的使用 内部会自动传入dom操作的方法
import App from './App.vue'
import { createRenderer, createApp,nextTick } from '@vue/runtime-dom'; // 拿到创建渲染器的方法
// 原生vue3 中可以直接创建应用 , 我希望底层变成小程序的
// vue3 的底层还是操作dom元素 setData()
// createApp(App).mount('#app')
let canvas;
let ctx;
const d2a = (n)=>{
return n * Math.PI / 180
}

const drawCircle = (start,end,color,cx,cy,r)=>{
// Math.cos sin 他都是基于弧度来计算的
let x = cx + Math.cos(d2a(start)) * r; // 已知 角度 和 斜边 求临边 cos
// 将角度转化成弧度
let y = cy + Math.sin(d2a(start)) * r; // 已知 角度 和 斜边 求对标 sin
ctx.beginPath(); // 固定用法
ctx.moveTo(cx,cy);
ctx.lineTo(x,y);
ctx.arc(cx,cy,r,d2a(start),d2a(end));
ctx.fillStyle = color;
ctx.fill();
ctx.stroke(); // 线条方式展示
ctx.closePath();
}
const draw = (el,noClear) => {
// 要识别哪些我能渲染
// div 我就不识别了 没有意义
if(!noClear){
ctx.clearRect(0,0,canvas.width,canvas.height);
}
if (el.tag == 'circle') {
// 这是我要画个圆
let { data, x, y, r } = el;
// console.log(data, x, y, r); // 用canvas 来画饼图

// 饼图 总共数量 每个占比
let total = data.reduce((memo, current) => memo + current.count, 0)

// 一共360° 每个占多少°
let start = 0,
end = 0;

data.forEach(item => {
end += item.count / total * 360;
drawCircle(start, end, item.color, x, y, r);
start = end; // 不停的向下便宜
});

}
if (el.tag === 'rect') { // 柱状图
console.log('开始绘制矩形')
}
el.childs && el.childs.forEach(child => { // 渲染儿子
draw(child,true)
});
}

const ops = { // 自己重新定义的方法 mpvue源码, 这里我可以增添自己的渲染逻辑
insert: (child, parent, anchor) => {
// 插入的过程是由子及父 先将circle 插入到div 中 在将div中插入到canvas
// 插入时 先创建父子关系
child.parent = parent;
// dom元素的children只能读不能写, 树的操作 内部创建时会遍历节点,遍历的时候 我就可以创建父子关系,等会渲染的时候 我需要这个父子关系
if (!parent.childs) {
parent.childs = [child]
} else {
parent.childs.push(child);
}
if (parent.nodeType == 1) { // 遇到canvas了,我就需要重新将这棵树渲染到canvas中
// canvas > div > circle
draw(child);

if(child.onClick) {
canvas.addEventListener('click',()=>{
child.onClick(); // 触发定义的事件
nextTick(()=>{
draw(child); // 重新绘制canvas
})
})
}
}
},
remove: child => {},
createElement: (tag, isSVG, is) => {
// 创建元素的过程 深度遍历的过程 有儿子 先创建儿子 将儿子绑定到父亲上
return { tag }; // 虚拟节点 这个{} 是对象
},
createText: text => {},
createComment: text => {},
setText: (node, text) => {},
setElementText: (el, text) => {},
parentNode: node => {},
nextSibling: node => {},
querySelector: selector => {},
setScopeId(el, id) {},
cloneNode(el) {},
insertStaticContent(content, parent, anchor, isSVG) {},
patchProp(el, key, prevValue, nextValue) {
// 这里以前可能做的是 样式处理 事件处理
el[key] = nextValue; // 相当于给自己自定义的对象添加了属性

// draw() 方法
}
}
const createCanvasApp = (...args) => {
// 这里创建渲染器时可以传入底层你想操作的方法
let app = createRenderer(ops).createApp(...args); // app.mount
let { mount } = app; // 内部的mount方法
app.mount = function(selector) {
let el = document.querySelector(selector);
// todo...
// 实现canvas 得有canavas的画布
canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
el.appendChild(canvas);
ctx = canvas.getContext('2d')
mount(canvas);
}
return app;
}
createCanvasApp(App).mount('#app');

四、手写vite

1
2
3
4
// 安装
npm install create-vite-app -g
create-vite-app projectName
npm init vite-app projectName
  1. 默认采用的是es6原生模块 import语法会默认发送一个请求
  2. 默认会给VUE的模块增加一个前缀/@modules
  3. 把.vue文件在后端给解析成一个对象了 唯一就是编译了.vue文件
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
const Koa = require('koa');
const {serveStaticPlugin} = require('./plugins/serverPluginServeStatic');
const {moduleRewritePlugin} = require('./plugins/serverPluginModuleRewrite.js');
const {moduleResolvePlugin} = require('./plugins/serverPluginModuleResolve.js');
const {htmlRewritePlugin} = require('./plugins/serverPluginHtml');
const {vuePlugin} = require('./plugins/serverPluginVue')

function createServer(){
const app = new Koa(); // 创建一个koa实例
const root = process.cwd();
// 当用户运行 npm run my-dev时 会创建服务
// koa是基于中间件来运行的
const context = {
app,
root // 当前的根的位置
}
// koa的中间件的执行顺序 中间件的原理
const resolvedPlugins = [ // 插件的集合
htmlRewritePlugin,
// 2) 解析import重写路径
moduleRewritePlugin,
// 3) 解析 以/@modules文件开头的内容 找到对应的结果
moduleResolvePlugin,
// 1) 要实现静态服务的功能
vuePlugin,
serveStaticPlugin // 功能是读取文件将文件的结果放到了ctx.body上
];

resolvedPlugins.forEach(plugin=>plugin(context))
return app; // 返回app 中有listen方法
}

module.exports = createServer;