-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy path页面初始化流程.html
406 lines (385 loc) · 13.3 KB
/
页面初始化流程.html
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
<!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>
<h1>
页面初始化逻辑梳理
</h1>
<div id="app"></div>
<script src="../packages/vue/dist/vue.global.js"></script>
<script>
//具体实现逻辑实例:
let activeEffectScope
const effectScopeStack = []
class EffectScope {
// active 的作用是为了防止执行stop销毁之再次执行当前实例的方法依然生效
active = true
effects = []
cleanups = []
parent
scopes
index
constructor(detached = false) {
if (!detached && activeEffectScope) {
this.parent = activeEffectScope
this.index =
(activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
this
) - 1
}
}
run(fn) {
if (this.active) {
try {
this.on()
return fn()
} finally {
this.off()
}
}
}
// 依赖追踪使用
on() {
if (this.active) {
effectScopeStack.push(this)
activeEffectScope = this
}
}
off() {
if (this.active) {
effectScopeStack.pop()
activeEffectScope = effectScopeStack[effectScopeStack.length - 1]
}
}
stop(fromParent) {
if (this.active) {
this.effects.forEach(e => e.stop())
this.cleanups.forEach(cleanup => cleanup())
if (this.scopes) {
this.scopes.forEach(e => e.stop(true))
}
this.active = false
}
}
}
const nodeOps = {
insert: (child, parent) => {
parent.insertBefore(child, null)
},
createElement: (tag) => {
return document.createElement(tag)
},
setElementText: (el, text) => {
el.textContent = text
},
}
// 这里我们只考虑 事件patch 的情况,其他暂不处理
function patchEvent(el, key, val) {
el.addEventListener(key.slice(2).toLowerCase(), function () {
val()
})
}
// 处理props
function patchProp(el, key, val) {
patchEvent(el, key, val)
}
// 判断是不是ref
function isRef(r) {
return Boolean(r && r.__v_isRef === true)
}
// 返回正确的值
function unref(ref) {
return isRef(ref) ? ref.value : ref
}
const toDisplayString = (val) => {
return val == null
? ''
: String(val)
}
const computed = Vue.computed
Vue.computed = function (fn) {
const val = computed(fn)
// 模拟拿到effect
recordEffectScope(val.effect)
return val
}
const ref = Vue.ref
Vue.ref = function (val) {
const newRef = ref(val)
// 模拟拿到effect
recordEffectScope(val.effect)
return newRef
}
// 建立关联的函数,当前函数在computed、watch、watchEffect等函数中使用
function recordEffectScope(effect, scope) {
scope = scope || activeEffectScope
if (scope && scope.active) {
scope.effects.push(effect)
}
}
// vue的内部使用
// 在页面内部使用EffectScope是为了在组件销毁的时候卸载依赖
//当前实例
let currentInstance = null
// 设置当前实例
const setCurrentInstance = (instance) => {
currentInstance = instance
instance.scope.on()
}
function parentNode(node) {
return node.parentNode
}
// 容错处理
function callWithErrorHandling(
fn,
instancel,
args
) {
let res
try {
res = args ? fn(...args) : fn()
} catch (err) {
console.error(err)
}
return res
}
// 建立响应式
function proxyRefs(objectWithRefs) {
return new Proxy(objectWithRefs, {
get: (target, key, receiver) => {
return unref(Reflect.get(target, key, receiver))
},
set: (target, key, value, receiver) => {
return Reflect.set(target, key, value, receiver)
}
})
}
// 赋值render函数,咱们这里暂不处理编译相关,只赋值render 函数即可
function finishComponentSetup(instance) {
Component = instance.type
// 中间可能有编译过程,暂时省略不处理
instance.render = Component.render
}
function handleSetupResult(instance, setupResult) {
instance.setupState = proxyRefs(setupResult)
// 下方还有编译的内容
finishComponentSetup(instance)
}
// 创建组件实例
function createComponentInstance(vnode, parent) {
// 拿到类型
const type = vnode.type
const instance = {
vnode,
type,
parent,
// 此时已经创建了一个EffectScope 为了批量处理依赖
scope: new EffectScope(true /* detached */),
render: null,
subTree: null,
effect: null,
update: null,
}
return instance
}
// 组件类型的处理,里面包含mount和update
function processComponent(n1, n2, container) {
if (n1 == null) {
mountComponent(n2, container)
} else {
updateComponent()
}
}
const processText = (n1, n2, container, anchor) => {
if (n1 == null) {
nodeOps.insert(
(n2.el = createText(n2.children)),
container
)
}
}
// 节点初始化
function mountElement(n2, container, parentComponent) {
const { type, props, children, shapeFlag } = n2
let el = nodeOps.createElement(type)
// 判断出来带文本子节点的内容
if (n2.shapeFlag === 9) {
nodeOps.setElementText(el, children)
}
// 如果有props 的情况
if (props) {
for (key in props) {
// 注意这里只处理事件的情况,暂不处理样式等情况
patchProp(el, key, props[key])
}
}
nodeOps.insert(el, container)
}
// 节点类型的处理
function processElement(n1, n2, container, parentComponent) {
if (n1 == null) {
// 如果第一次没有,那么就是走mountelement的类型
mountElement(
n2,
container,
parentComponent
)
} else {
// 接下来就是组件更新,走的是patch的内容,内部包含diff,也就是最核心的内容
}
}
// 组件销毁方法
function unmountComponent() {
}
//创建vnode
function createVNode(type, props, children, shapeFlag = 1) {
if (typeof children === "string") {
shapeFlag = 9
}
const vnode = {
type,
props,
children,
shapeFlag,
component: null,
}
return vnode
}
// patch 包含各种组件,节点,注释等内容的挂载,这里为了分析依赖追踪,我们只分析组件的挂载
function patch(n1, n2, container, parentComponent = null) {
// 如果相等就返回表示没变
if (n1 === n2) {
return
}
const { type, shapeFlag } = n2
switch (type) {
// 前面逻辑省略,我们只处理组件类型和普通节点类型
default:
if (shapeFlag & 1) {
processElement(n1, n2, container, parentComponent)
} else {
processComponent(n1, n2, container)
}
}
}
function setupStatefulComponent(instance) {
const Component = instance.type
const { setup } = Component
if (setup) {
setCurrentInstance(instance)
// 拿到setup结果
const setupResult = callWithErrorHandling(
setup,
instance,
[instance.props]
)
// 这里我们只判断返回对象的情况,不考虑别的情况
handleSetupResult(instance, setupResult)
}
}
// 执行setup函数
function setupComponent(instance) {
// 上方的一些不重要的逻辑暂不处理,执行核心逻辑
const setupResult = setupStatefulComponent(instance)
return setupResult
}
// 拿到vnode
function renderComponentRoot(instance) {
// 源码中使用一个Proxy来代理所有的响应式内容的访问和修改 并且存入 instance.proxy中,统一处理
// 我们这里只是为了梳理主流程 只需要setupState即可 porps 暂不考虑
const {
type: Component,
render,
setupState,
} = instance
debugger
result = render.call(
setupState,
setupState,
)
return result
}
// 依赖收集 生成effect
function setupRenderEffect(instance, n2, container) {
const componentUpdateFn = () => {
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = nextTree
// 这是组件内部的patch走diff
patch(
prevTree,
nextTree,
container,
instance
)
}
// 这里直接调用内部的ReactiveEffect即可不用自己重写
const effect = (instance.effect = new Vue.ReactiveEffect(
componentUpdateFn,
null,
instance.scope // track it in component's effect scope 在组件的影响范围内跟踪它 依赖追踪使用
))
const update = (instance.update = effect.run.bind(effect))
update()
}
// 组件初始化方法
function mountComponent(initialVNode, container, parentComponent = null) {
const instance =
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
))
// 执行setup
setupComponent(instance)
// 依赖收集
setupRenderEffect(instance, initialVNode, container)
}
// 组件更新
function updateComponent() {
// 更新逻辑咱暂时不看
}
// render 组件的初始化主要就是执行path
function createApp(rootComponent, rootProps = null) {
// 这里我们直接传值,源码中是根据type 去判断的
const vnode = createVNode(rootComponent, rootProps, null, 4)
const mount = (container) => {
if (typeof container === 'string') {
container = document.querySelector(container)
}
patch(null, vnode, container)
}
return { mount }
}
// 初始化
createApp({
setup() {
const num = Vue.ref(1)
console.log(num)
const double = Vue.computed(() => {
console.log(1111)
debugger
return num.value * 2
})
function handleClick() {
num.value++
}
return {
num,
double,
handleClick
}
},
render(_ctx) {
with (_ctx) {
return createVNode("div", { onClick: handleClick }, toDisplayString(double))
}
}
}).mount('#app')
</script>
</body>
</html>