0%

mini-vue

虚拟结点+diff算法部分

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

function h(tag,props,children){
return {
tag,
props,
children
}
}
function mount(vnode,container){
//创建相应的真实DOM结点
const el=vnode.el=document.createElement(vnode.tag);
//props
if(vnode.props){
for(const key in vnode.props){
const value=vnode.props[key];
el.setAttribute(key,value);

}
}
//children

if(vnode.children){
if(typeof vnode.children=='string'){
el.textContent=vnode.children;

}
else{
vnode.children.forEach(child=>{
mount(child,el)//递归
})
}
}
container.appendChild(el);


}
const vdom=h('div',{class:'red'},[
h('span',null,['hello'])
])
const vdom2=h('div',{class:'red'},[
h('span',null,['hi'])
])
mount(vdom,document.getElementById('app'))
function patch(n1,n2){
const el=n2.el=n1.el;
if(n1.tag===n2.tag){

//props
const oldProps=n1.props||{};
const newProps=n1.props||{};
for(const key in newProps){
const oldValue=oldProps[key];
const newValue=newProps[key]
if(newValue!==oldValue){
el.setAttribute(key,newValue)
}
}
for(const key in oldProps){
if(!key in newProps){
el.removeAttribute(key)
}
}
//children
const oldChildren=n1.children;
const newChildren=n2.children;
if(typeof newChildren==='string'){
if(typeof oldChildren==='string'){
if(newChildren!==oldChildren){
el.textContent=newChildren;
}
}else{
el.textContent=newChildren
}
}else {
//if newChildren is array
if(typeof oldChildren==='string'){
el.innerHTML=''
newChildren.forEach(child=>{
mount(child,el)
})
}else{
const commonLength=Math.min(oldChildren.length,newChildren.length)
for(let i=0;i<commonLength;i++){
patch(oldChildren[i],newChildren[i])
}
if(newChildren.length>oldChildren.length){
newChildren.slice(oldChildren.length).forEach(child=>{
mount(child,el)
})

}else if(newChildren.length<oldChildren.length){
oldChildren.slice(newChildren.length).forEach(child=>{
el.removeChild(child.el);
})
}
}
}

}else{
//replace

}

}
patch(vdom,vdom2);

reactive部分

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

let activeEffect;
class Dep{
subscribers=new Set()
depend(){
if(activeEffect){
this.subscribers.add(activeEffect)
}
}
notify(){
this.subscribers.forEach(effect=>{
effect()
})
}

}
//Vue2
/*function reactive(raw){
Object.keys(raw).forEach(key=>{
const dep=new Dep()
let value=raw[key]
Object.defineProperty(raw,key,{
get(){
dep.depend()
return value

},
set(newValue){
value=newValue
dep.notify()

}
})
})
return raw

}*/
const targetMap=new WeakMap()//键值可以是对象,而且会被自动垃圾回收
function getDep(target,key){
let depsMap=targetMap.get(target)
if(!depsMap){
depsMap=new Map()
targetMap.set(target,depsMap)
}
let dep=depsMap.get(key)
if(!dep){
dep=new Dep()
depsMap.set(key,dep)
}

return dep;

}
const reactiveHandler={
get(target,key,receiver){
let dep=getDep(target,key)
dep.depend()
return Reflect.get(target,key,receiver)//Reflect不会抛出异常,只会抛出真假
},
set(target,key,value,receiver){
let dep=getDep(target,key)
const result=Reflect.set(target,key,value,receiver)
dep.notify()
return result

}

}
function reactive(raw){
return new Proxy(raw,reactiveHandler)//Proxy 会触发set get 有利于数组观测,不必多写一些数组内置方法
}

function watchEffect(effect){
activeEffect=effect
effect()
activeEffect=null
}
const dep=new Dep()
const state=reactive({
count:0
})
watchEffect(()=>{
console.log(state.count)
})//effect run

state.count++;

完整mini-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
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script>

function h(tag,props,children){
return {
tag,
props,
children
}
}
function mount(vnode,container){
//创建相应的真实DOM结点
const el=vnode.el=document.createElement(vnode.tag);
//props
if(vnode.props){
for(const key in vnode.props){
const value=vnode.props[key];
if(key.startsWith('on')){
el.addEventListener(key.slice(2).toLowerCase(),value)//事件机制
}else{
el.setAttribute(key,value);
}

}
}
//children
if(vnode.children){
if(typeof vnode.children==='string'){
el.textContent=vnode.children;
}
else{
vnode.children.forEach(child=>{
mount(child,el)//递归
})
}
}
container.appendChild(el);
}
function patch(n1,n2){
const el=n2.el=n1.el;
if(n1.tag===n2.tag){
//props
const oldProps=n1.props||{};
const newProps=n1.props||{};
for(const key in newProps){
const oldValue=oldProps[key];
const newValue=newProps[key]
if(newValue!==oldValue){
el.setAttribute(key,newValue)
}
}
for(const key in oldProps){
if(!key in newProps){
el.removeAttribute(key)
}
}
//children
const oldChildren=n1.children;
const newChildren=n2.children;
if(typeof newChildren==='string'){
if(typeof oldChildren==='string'){
if(newChildren!==oldChildren){
el.textContent=newChildren;
}
}else{
el.textContent=newChildren
}
}else {
//if newChildren is not string
if(typeof oldChildren==='string'){
el.innerHTML=''
newChildren.forEach(child=>{
mount(child,el)
})
}else{
const commonLength=Math.min(oldChildren.length,newChildren.length)
for(let i=0;i<commonLength;i++){
patch(oldChildren[i],newChildren[i])
}
if(newChildren.length>oldChildren.length){
newChildren.slice(oldChildren.length).forEach(child=>{
mount(child,el)
})

}else if(newChildren.length<oldChildren.length){
oldChildren.slice(newChildren.length).forEach(child=>{
el.removeChild(child.el);
})
}
}
}

}else{
//replace

}

}

let activeEffect;
class Dep{
subscribers=new Set()
depend(){
if(activeEffect){
this.subscribers.add(activeEffect)
}
}
notify(){
this.subscribers.forEach(effect=>{
effect()
})
}

}

const targetMap=new WeakMap()//只接受键值是对象,不接受其他类型的值作为键值,本身不可以从任何代码访问,而且会被自动垃圾回收,不可以迭代
function getDep(target,key){
let depsMap=targetMap.get(target)
if(!depsMap){
depsMap=new Map()//可以迭代键
targetMap.set(target,depsMap)
}
let dep=depsMap.get(key)
if(!dep){
dep=new Dep()
depsMap.set(key,dep)
}

return dep;

}
const reactiveHandler={
get(target,key,receiver){
let dep=getDep(target,key)
dep.depend()
return Reflect.get(target,key,receiver)//Reflect不会抛出异常,只会抛出真假
},
set(target,key,value,receiver){
let dep=getDep(target,key)
const result=Reflect.set(target,key,value,receiver)
dep.notify()
return result

}

}
function reactive(raw){
return new Proxy(raw,reactiveHandler)//Proxy 会触发set get 有利于数组观测,不必多写一些数组内置方法
}

function watchEffect(effect){
activeEffect=effect
effect()
activeEffect=null
}
const dep=new Dep()
const App={
data:reactive({
count:0
}),
render(){
return h('div',{
onClick:()=>{
this.data.count++;
}
},
String(this.data.count))
}
}
function mountApp(component,container){
let isMounted=false
let prevVdom
watchEffect(()=>{
if(!isMounted){
prevVdom=component.render()
mount(prevVdom,container)
isMounted=true
}else{
const newVdom=component.render()
patch(prevVdom,newVdom)
prevVdom=newVdom
}
})
}
mountApp(App,document.getElementById('app'))
</script>
</body>
</html>