从零手写Vue3核心原理
一.虚拟DOM转真实DOM
let { render } = Vue
const state = { count: 0 };
const vnode = {
tag: 'div',
props: {
style: { color: 'red' }
},
children: [{
tag: 'p',
props: null,
children: `vue@3- 计数器 ${state.count}`
}, {
tag: 'button',
props: {
onClick: () => alert(state.count)
},
children: '点我啊'
}]
}
render(vnode, app);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
先创建一个虚拟节点对象,调用render方法将虚拟节点转化为真实节点。
实现DOM操作方法
export const nodeOps = {
insert: (child, parent, anchor) => {
if (anchor) {
parent.insertBefore(child, anchor);
} else {
parent.appendChild(child);
}
},
remove: (child) => {
const parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
},
createElement: (tag) => document.createElement(tag),
setElementText: (el, text) => el.textContent = text
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
将虚拟节点转化为真实节点
import { nodeOps } from './runtime-dom';
export function render(vnode, container) {
// 渲染分为两种 一种是初始化 一种是是diff
patch(null, vnode, container);
}
function patch(n1, n2, container) {
if (typeof n2.tag == 'string') {
// 将虚拟节点挂载到对应的容器中
mountElement(n2, container);
}
}
function mountElement(vnode, container) {
const { tag, props, children } = vnode;
let el = nodeOps.createElement(tag);
if (typeof children === 'string') {
nodeOps.setElementText(el, children);
} else if (Array.isArray(children)) {
mountChildren(children, el);
}
nodeOps.insert(el, container, null);
}
// 循环挂载子元素
function mountChildren(children, container) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
patch(null, child, container);
}
}
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
处理DOM中的属性
const onRe = /^on[^a-z]/;
export const nodeOps = {
// ...
hostPatchProps: (el, key, value) => {
const isOn = key => onRe.test(key);
if (isOn(key)) { // 事件添加
const name = key.slice(2).toLowerCase();
el.addEventListener(name, value);
} else {
if (key === 'style') { // 样式处理
for (let key in value) {
el.style[key] = value[key];
}
} else {
el.setAttribute(key, value);
}
}
}
}
function mountElement(vnode, container) {
const { tag, props, children } = vnode;
let el = (vnode.el = nodeOps.createElement(tag));
if (props) {
// 循环所有属性添加属性
for (let key in props) {
nodeOps.hostPatchProps(el, key, props[key]);
}
}
if (typeof children === 'string') {
nodeOps.setElementText(el, children);
} else if (Array.isArray(children)) {
mountChildren(children, el);
}
nodeOps.insert(el, container, null);
}
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
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
二. Vue3.0组件的实现
const MyComponent = {
setup() {
return () => ({
tag: 'div',
props: { style: { color: 'blue' } },
children: '我是一个组件' + state.count
})
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Vue3.x中组件拥有setup方法,当组件渲染前会先调用此方法
const vnode = {
tag: 'div',
props: {
style: { color: 'red' }
},
children: [{
tag: 'p',
props: null,
children: `vue@3- 计数器 ${state.count}`
}, {
tag: 'button',
props: {
onClick: () => alert(state.count)
},
children: '点我啊'
},
{ tag: MyComponent, props: null, children: '' },
{ tag: MyComponent, props: null, children: '' }
]
}
render(vnode, app);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
将组件同时传入children属性中。内部根据tag类型做不同的初始化操作
function patch(n1, n2, container) {
if (typeof n2.tag == 'string') {
// 将虚拟节点挂载到对应的容器中
mountElement(n2, container);
}else if (typeof n2.tag === 'object') {
// 组件的挂载
mountComponent(n2, container);
}
}
function mountComponent(vnode, container) {
const instance = { // 创建元素实例
vnode,
tag: vnode.tag,
render: null, // setup返回的结果
subTree: null, // 子元素
}
const Component = instance.tag;
instance.render = Component.setup(); // 调用setUp方法
instance.subTree = instance.render && instance.render();
patch(null, instance.subTree, container); // 将子树挂载在元素上
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
三.Vue3.0响应式原理
effect(() => {
const vnode = {
tag: 'div',
props: {
style: { color: 'red' }
},
children: [{
tag: 'p',
props: null,
children: `vue@3- 计数器` + state.count
}, {
tag: 'button',
props: {
onClick: () => state.count++
},
children: '点我啊'
},
{ tag: MyComponent, props: null, children: '' },
{ tag: MyComponent, props: null, children: '' }
]
}
render(vnode, app);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注入副作用函数,当数据变化时可以自动重新执行。
let activeEffect;
export function effect(fn) {
activeEffect = fn;
fn();
}
export function reactive(target) {
return new Proxy(target, {
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
activeEffect();
return res;
},
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
return res;
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
通过proxy代理数据当数据更新时,重新执行effect函数。
依赖收集原理
let activeEffect;
export function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null; // 当前effect置为空
}
const targetMap = new WeakMap();
function track(target,key){ // 依赖收集
let depsMap = targetMap.get(target);
if(!depsMap){ // 属性对应依赖关系
targetMap.set(target,(depsMap = new Map()));
}
let deps = depsMap.get(key);
if(!deps){ // 设置set 存放effect
depsMap.set(key,(deps=new Set()));
}
if(activeEffect && !deps.has(activeEffect)){
deps.add(activeEffect);
}
}
function trigger(target,key,val){
const depsMap = targetMap.get(target);
if(!depsMap){
return;
}
const effects = depsMap.get(key);
effects && effects.forEach(effect =>effect());
}
export function reactive(target) {
return new Proxy(target, {
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
trigger(target,key,value); // 触发更新
return res;
},
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target,key); // 收集依赖
return res;
}
})
}
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
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
四. 组件级更新
function mountComponent(vnode, container) {
const instance = {
vnode,
tag: vnode.tag,
render: null, // setup返回的结果
subTree: null, // 子元素
}
const Component = instance.tag;
instance.render = Component.setup(); // 调用setUp方法
effect(()=>{
// 给组件添加effect,组件中的属性变化时只执行对应方法
instance.subTree = instance.render && instance.render();
patch(null, instance.subTree, container); // 将子树挂载在元素上
})
}
const MyComponent = {
setup() {
return () => ({
tag: 'div',
props: { style: { color: 'blue' } },
children: [
{
tag: 'h3',
children: '姓名:' + state.name
},
{
tag: 'button',
children: '更新',
props: {
onClick:()=>state.name = 'jw'
}
}
]
})
}
}
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
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
只更新组件对应区域