Vue3源码解析(V3.2.45)-渲染原理及diff算法

说明:本章内容为博主在原教程基础上添加自己的学习笔记,来源珠峰架构Vue3课程 (opens new window),版权归原作者所有。

# 渲染原理及diff算法

# vue3组件初始化过程

**vue3组件初始化流程图-图片来源知乎社区**
**vue3组件初始化流程图-图片来源珠峰社区**

# runtime介绍

vue3中将runtime模块分为runtime-core核心代码及其他平台对应的运行时。runtime-dom是解决浏览器运行时问题,此包封装了dom属性操作和节点操作的一系列接口

  • runtime-core:与平台无关的运行时核心
  • runtime-dom:浏览器运行时,包括dom-api、属性和事件处理等
  • runtime-test:运行时测试

提示

默认引入vue就是基于runtime-core和runtime-dom

# runtime-core介绍

  • runtime-core不依赖任何平台(平台代码是被传入的)

# renderer

//框架都是将组件转换成虚拟dom,然后通过虚拟dom生成真实dom,挂载到页面上
//虚拟dom可以做对比,多次操作可以一起更新

import { effect } from "@vue/reactivity"
import { ShapeFlags } from "@vue/shared"
import { createAppApi } from "./apiCreateApp"
import { createComponentInstance, setupComponent } from "./component"
import { queueJob } from "./scheduler"
import { normalizeVNode, Text } from "./vnode"

export function createRender(rendererOptions) {//告诉core怎么渲染
    const {
        insert: hostInsert,
        remove: hostRemove,
        patchProp: hostPatchProp,
        createElement: hostCreateElement,
        createText: hostCreateText,
        createComment: hostCreateComment,
        setText: hostSetText,
        setElementText: hostSetElementText,
        parentNode: hostParentNode,
        nextSibling: hostNextSibling,
    } = rendererOptions

    const setupRenderEffect = (instance, container) => {
        //需要创建一个effect方法 在effect中调用render,render中拿到的数据会收集这个effect,属性更新时effect会重新执行
        effect(function componentEffect() {//每个组件都有一个effect,vue3是组件级effect,数据变化重新执行对应组件的effect
            if (!instance.isMounted) {//表示初次挂载,初次渲染
                let proxyToUse = instance.proxy
                let subTree = instance.subTree = instance.render.call(proxyToUse, proxyToUse)
                //用render的返回值继续渲染
                patch(null, subTree, container)
                instance.isMounted = true
                console.log(subTree)
            } else {//更新
                const prevTree = instance.subTree
                let proxyToUse = instance.proxy
                const nextTree = instance.render.call(proxyToUse, proxyToUse)
                patch(prevTree, nextTree, container)
            }
        }, {
            scheduler: queueJob
        })
        instance.render()
    }

    const render = (vnode, container) => {
        //core的核心方法
        //根据不同的虚拟节点创建真是的元素

        //默认调用初始化流程
        patch(null, vnode, container)
    }

    const mountComponent = (initalizeVNode, container) => {
        console.log(initalizeVNode, container)
        //组件的渲染流程
        //先创建实例
        const instance = (initalizeVNode.component = createComponentInstance(initalizeVNode))
        //需要的数据解析到实例上
        setupComponent(instance)
        //创建一个effect让render函数执行
        setupRenderEffect(instance, container)
    }

    const patchProps = (oldProps, newProps, el) => {
        if (oldProps === newProps) {
            return
        }
        for (const key in newProps) {
            const prev = oldProps[key]
            const next = newProps[key]
            if (prev !== next) {
                hostPatchProp(el, key, prev, next)
            }
        }
        for (const key in oldProps) {
            if (!(key in newProps)) {
                hostPatchProp(el, key, oldProps[key], null)
            }
        }

    }

    const unmountChildren = (children) => {
        for (let i = 0; i < children.length; i++) {
            unmount(children[i])
        }
    }

    //比较两个key
    const patchKeyedChildren = (c1, c2, el) => {
        //vue3对特殊情况进行优化
        let i = 0
        let e1 = c1.length - 1
        let e2 = c2.length - 1

        //尽可能减少比对的情况

        while (i <= e1 && i <= e2) {
            const n1 = c1[i]
            const n2 = c2[i]
            if (isSameNVodeType(n1, n2)) {
                patch(n1, n2, el)
            } else {
                break
            }
            i++
        }

        while (i <= e1 && i <= e2) {
            const n1 = c1[e1]
            const n2 = c2[e2]
            if (isSameNVodeType(n1, n2)) {
                patch(n1, n2, el)
            } else {
                break
            }
            e1--
            e2--
        }

        //比较后,有一方已经完全比对完毕了

        if (i > e1) {//老的少  新的多
            if (i <= e2) {//表示有新增部分
                //想知道是向前插入还是向后插入
                const nextPos = e2 + 1
                const anchor = nextPos < c2.length ? c2[nextPos].el : null
                while (i <= e2) {
                    patch(null, c2[i], el, anchor)//只是向后追加
                    i++
                }
            }
        } else if (i > e2) {//老的多新的少
            if (i <= e1) {
                while (i <= e1) {
                    unmount(c1[i])
                    i++
                }
            } else {
                //乱序比较,需要尽可能复用,用新的元素做成一个映射表去老的里查找,一样的就复用,不一样的要不插入要不删除
                let s1 = i
                let s2 = i
                //vue3用的是新的做映射表  vue2用的是老的做映射表
                const keyToNewIndexMap = new Map()

                for (let i = s2; i <= e2; i++) {
                    const childVnode = c2[i]
                    keyToNewIndexMap.set(childVnode.key, i)
                }

                const toBePatched = e2 - s2 + 1
                const newIndexToOldIndexMap = new Array(toBePatched).fill(0)

                for (let i = s1; i <= e1; i++) {
                    const oldVnode = c1[i]
                    let newIndex = keyToNewIndexMap.get(oldVnode.key)
                    if (newIndex === undefined) {
                        unmount(oldVnode)
                    } else {
                        newIndexToOldIndexMap[newIndex - s2] = i + 1
                        patch(oldVnode, c2[newIndex], el)//patch的操作,是不是会复用元素,更新属性,比较儿子
                    }
                }

                let increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)
                let j = increasingNewIndexSequence.length - 1
                console.info(increasingNewIndexSequence)

                for (let i = toBePatched - 1; i >= 0; i--) {
                    let currentIndex = i + s2//找到索引
                    let child = c2[currentIndex]//找到索引对应的项
                    let anchor = currentIndex + 1 < c2.length ? c2[currentIndex + 1].el : null
                    //第一次插入h后,h是第一个虚拟节点,同时插入后,虚拟节点会拥有真实节点

                    if (newIndexToOldIndexMap[i] == 0) {//如果自己是0说明没有被patch过
                        patch(null, child, el, anchor)
                    } else {

                        if (i == increasingNewIndexSequence[j]) {
                            j--//跳过不需要移动的物体,为了减少移动,需要这个最长子序列递增算法
                        } else {
                            hostInsert(child.el, el, anchor)//操作当前的d 以d下一个作为参照物插入
                        }
                    }
                }

                //最后就移动节点并且将新增节点插入


                //最长递增子序列
            }
        }
    }

    const patchChildren = (oldNode, newNode, el) => {
        const c1 = oldNode.children
        const c2 = newNode.children

        //老的有儿子  新的没儿子  新的有儿子老的没儿子  新老都有儿子  新老都是文本
        const prevShapeFlag = oldNode.shapeFlags
        const shapeFlag = newNode.shapeFlags
        if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {//case1 现在是文本之前是数组
            //老的是n个孩子,但是新的文本
            if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
                unmountChildren(c1)
            }

            //两个都是文本的情况
            if (c2 != c1) {//case2 两个都是文本
                hostSetElementText(el, c2)
            }
        } else {
            //现在是元素 上一次可能是文本或者数组
            //两个数组的比对(diff算法)*****************比较复杂
            if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {//上次是数组//case3 两个都是数组
                if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {//这次也是数组
                    patchKeyedChildren(c1, c2, el)
                } else {//没有孩子
                    unmountChildren(c1)
                }
            } else {
                //上一次是文本
                if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {//case4 现在是数组之前是文本
                    hostSetElementText(el, "")
                }
                //这次是数组
                if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
                    mountChildren(c2, el)
                }
            }
        }
    }

    const getSequence = (arr) => {
        const len = arr.length
        const result = [0]//默认第0个为参照物
        let p = arr.slice(0)//里面内容无所谓 和原本的数组相同 用来存放索引
        let start
        let end
        let middle

        for (let i = 0; i < len; i++) {
            const arrI = arr[i]
            if (arrI !== 0) {
                let resultLastIndex = result[result.length - 1]
                if (arr[resultLastIndex] < arrI) {
                    p[i] = resultLastIndex//标记当前前一个对应的索引
                    result.push(i)
                    continue
                }
            }

            //二分查找,找到比当前值大的那一个
            start = 0
            end = result.length - 1
            while (start < end) {
                middle = ((start + end) / 2) | 0
                if (arr[result[middle]] < arrI) {
                    start = middle + 1
                } else {
                    end = middle
                }//找到结果集中比当前这一项大的数

                if (arrI < arr[result[start]]) {
                    if (start > 0) {
                        p[i] = result[start - 1]//将它替换成前一个
                    }
                    result[start] = i
                }
            }
        }

        let len1 = result.length
        let last = result[len1 - 1]
        while (len1-- > 0) {//根据前驱节点一个个向前查找
            result[len1] = last
            last = p[last]
        }
        return result;
    }

    const patchElement = (oldNode, newNode, container) => {
        //元素是相同根节点
        let el = (oldNode.el = newNode.el)
        //更新属性  更新儿子
        const oldProps = oldNode.props || {}
        const newProps = newNode.props || {}

        patchProps(oldProps, newProps, el)

        patchChildren(oldNode, newNode, el)
    }

    const processComponent = (oldNode, newNode, container) => {
        if (oldNode == null) {//组件没有上次的虚拟节点
            mountComponent(newNode, container)
        } else {
            //组件更新流程
            patchElement(oldNode, newNode, container)
        }
    }

    const mountChildren = (children, container) => {
        for (let i = 0; i < children.length; i++) {
            let child = normalizeVNode(children[i])
            patch(null, child, container)
        }
    }

    const mountElement = (vnode, container, anchor = null) => {
        const { props, shapeFlags, type, children } = vnode
        let el = (vnode.el = hostCreateElement(type))
        if (props) {
            for (const key in props) {
                hostPatchProp(el, key, null, props[key])
            }
        }
        if (shapeFlags & ShapeFlags.TEXT_CHILDREN) {
            hostSetElementText(el, children)
        } else if (shapeFlags & ShapeFlags.ARRAY_CHILDREN) {
            mountChildren(children, el)
        }

        hostInsert(el, container, anchor)
    }

    const processElement = (oldNode, newNode, container, anchor) => {
        if (oldNode == null) {
            mountElement(newNode, container, anchor)
        }
    }

    const processText = (oldNode, newNode, container) => {
        if (oldNode == null) {
            hostInsert((newNode.el = hostCreateText(newNode.children)), container)
        }
    }

    const isSameNVodeType = (oldNode, newNode) => {
        return oldNode.type === newNode.type && oldNode.key === newNode.key
    }

    const unmount = (oldNode) => {
        hostRemove(oldNode.el)
    }

    const patch = (oldNode, newNode, container, anchor = null) => {
        //针对不同类型做初始化操作
        const { shapeFlags, type } = newNode

        if (oldNode && !isSameNVodeType(oldNode, newNode)) {
            anchor = hostNextSibling(oldNode.el)
            //把以前的删掉换成新的
            unmount(oldNode)
            oldNode = null//重新渲染 
        }

        switch (type) {
            case Text:
                processText(oldNode, newNode, container)
                break;
            default:
                if (shapeFlags & ShapeFlags.ELEMENT) {//判断下shapeflags是否是元素
                    console.log('元素')
                    processElement(oldNode, newNode, container, anchor)
                } else if (shapeFlags & shapeFlags.STATEFUL_COMPONENT) {
                    console.log('组件')
                    processComponent(oldNode, newNode, container)
                }
        }
    }

    return {
        createApp: createAppApi(render)
    }
}

// vue3组件创建的过程
// 将组件变成虚拟节点vnode,再将vnode变成真实dom,然后插入到页面中
// render方法的作用是可以渲染一个虚拟节点,将他挂载在具体的dom元素上
// vue3执行的核心就在patch这个方法上

// 创建组件的流程
// 先创建一个instance--->初始化
// 根据用户传入的组件,拿到对应的内容,来填充这个instance这个对象
// 创建effect方法并调用render方法,数据会将对应的effect收集起来
// 拿到render方法的返回结果,再次走渲染流程->patch

// 组件渲染的顺序是先父后子,执行顺序是深度优先

// 每个组件都是一个effect函数
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
renderer.ts

# component

//组件中所有方法

import { isFunction, isObject, ShapeFlags } from "@vue/shared"
import { PublicInstanceProxyHandlers } from "./componentPublicInstance"

export function createComponentInstance(vnode) {
    const instance = { //组件的实例
        vnode,
        type: vnode.type,
        props: {},
        atrrs: {},
        slots: {},
        ctx: null,
        setupState: {},//如果setup返回一个对象,那么这个对象作为setupState
        isMounted: false,//表示组件是否被挂载
    }
    instance.ctx = { _: instance }
    return instance
}

export function setupComponent(instance) {
    const { props, children } = instance.vnode
    //根据props解析出props和attrs
    instance.children = children//插槽的解析initSlot()
    instance.props = props//初始化initProps()

    //需要看下当前组件是不是有状态的组件,函数组件
    let isStateful = instance.vnode.shapeFlags & ShapeFlags.STATEFUL_COMPONENT
    if (isStateful) {//表示现在是一个带状态的组件
        //调用当前的setup方法,用setup返回值填充setupState和对应的render方法
        setupStatefulComponent(instance)
    }

}

function setupStatefulComponent(instance) {
    //1代理,传递给函数的参数
    instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers as any)
    //2获取组件的类型,拿到组件的setup方法
    let component = instance.type
    let { setup } = component
    let setupContext = createSetupContext(instance)//instance中的props attrs slots emit expose都被提取出来
    if (setup) {
        let setupResult = setup(instance.props, setupContext)
        // component.render(instance.proxy)测试用
        handleSetupResult(instance, setupResult)
    } else {
        finishComponentSetup(instance)//完成组件的启动
    }
}

function handleSetupResult(instance, setupResult) {
    if (isFunction(setupResult)) {
        instance.render = setupResult
    } else if (isObject(setupResult)) {
        instance.setupState = setupResult
    }
    finishComponentSetup(instance)
}

function finishComponentSetup(instance) {
    let component = instance.type
    const { render } = component
    if (!instance.render) {//对template模板进行编译产生render函数
        //instance.render = render//将生成的render函数放在实例上
        if (!component.render && component.template) {
         //编译将结果赋给component.render 
         //再赋给instace.render
        }
        instance.render = component.render
    }
    //对vue2.x做了兼容
    //applyOptions
}

function createSetupContext(instance) {
    return {
        attrs: instance.atrrs,
        slots: instance.slots,
        // props: instance.props,
        emit: () => { },
        expose: () => { }
    }
}

//instance 表示组件的状态,各种各样的状态
//context 是为了开发时使用
//proxy 主要是为了取值方便
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
component.ts

# h函数

import { isArray, isObject } from "@vue/shared"
import { createVNode, isVNode } from "./vnode"

export function h(type, propsOrChildren, children) {
    let l = arguments.length//儿子节点要么是字符串要么是数组,针对的是createVNode
    if (l == 2) {//类型+属性  类型+孩子
        //如果第二个参数不是对象,那就是孩子
        //如果是数组直接作为第三个参数
        if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
            if (isVNode(propsOrChildren)) {
                return createVNode(type, null, [propsOrChildren])
            }
            return createVNode(type, propsOrChildren)

        } else {
            return createVNode(type, null, propsOrChildren)
        }
    }
    else {
        if (l > 3) {
            children = Array.prototype.slice.call(arguments, 2)
        } else if (l === 3 && isVNode(children)) {
            children = [children]
        }

        return createVNode(type, propsOrChildren, children)
    }
}
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
h.ts

# apiCreateApp

import { createVNode } from "./vnode"

export function createAppApi(render) {
    return function createApp(rootComponent, rootProps) {//告诉core用哪个组件哪个属性创建应用
        const app = {
            _rootProps: rootProps,
            _rootComponent: rootComponent,
            _container: null,
            mount(container) {//挂载的地方
                // let vnode = {}

                //流程
                //1根据组件创建虚拟节点
                //2虚拟节点和容器获取到后,调用renderer方法

                //创建虚拟节点
                const vnode = createVNode(rootComponent, rootProps)
                console.info(vnode)
                //调用render方法
                render(vnode, container)//将vnode渲染后容器上
                app._container = container
            }
        }
        return app
    }
}
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
apiCreateApp.ts

# componentPublicInstance

import { hasOwn } from "@vue/shared"

export const PublicInstanceProxyHandlers = {
    get({ _: instance }, key) {
        const { setupState, props, data } = instance
        if (key[0] === "$") return //不能访问$开头的变量
        if (hasOwn(setupState, key)) {
            return setupState[key];
        } else if (hasOwn(props, key)) {
            return props[key]
        } else if (hasOwn(data, key)) {
            return data[key]
        } else {
            return undefined
        }
    },
    set({ _: instance }, key, value) {
        const { setupState, props, data } = instance
        if (hasOwn(setupState, key)) {
            setupState[key] = value;
        } else if (hasOwn(props, key)) {
            props[key] = value
        } else if (hasOwn(data, key)) {
            data[key] = value
        } else {
            return false
        }

        return true
    }
}
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
componentPublicInstance.ts

# index

export { createRender } from './renderer'
export { h } from './h'

export * from "@vue/reactivity"
1
2
3
4
index.ts

# scheduler

let queue = []
export function queueJob(job) {
    if (!queue.includes(job)) {
        queue.push(job)
        queueFlush()
    }
}

let isFlushPeding = false

export function queueFlush() {
    if (!isFlushPeding) {
        isFlushPeding = true
        Promise.resolve().then(flushJobs)
    }
}

function flushJobs() {
    isFlushPeding = false
    //先刷新父再刷新子
    queue.sort((a, b) => a.id - b.id)

    for (let i = 0; i < queue.length; i++) {
        const job = queue[i]
        job()
    }

    queue.length = 0
}
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
scheduler.ts

# vnode


//h方法就是创建虚拟节点的vnode

import { isArray, isObject, isString, ShapeFlags } from "@vue/shared"

export function isVNode(vnode) {
    return vnode.__v_isNode
}

export function createVNode(type, props, children = null) {
    //根据type来区分是元素还是对象

    //根据type来区分是元素(字符串)还是组件(对象)
    const shapeFlags = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0


    //给虚拟节点加一个类型
    const vnode = {//一个对象来描述对应的内容,虚拟节点具有跨平台的功能
        __v_isNode: true,
        type,
        props,
        children,
        component: null,//存放组件对应的实例
        el: null,//稍后会将虚拟节点和真是节点对应起来
        key: props && props.key,//diff算法会用到key
        shapeFlags
    }
    normalizeChildren(vnode, children)
    return vnode
}

function normalizeChildren(vnode, children) {
    let type = 0
    if (children == null) {//不对儿子进行处理

    } else if (isArray(children)) {
        type = ShapeFlags.ARRAY_CHILDREN//表示儿子是个数组
    } else {
        type = ShapeFlags.TEXT_CHILDREN
    }
    vnode.shapeFlags = vnode.shapeFlags | type
}

export const Text = Symbol('Text')

export function normalizeVNode(child) {
    if (isObject(child)) return child
    return createVNode(Text, null, String(child))
}
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
vnode.ts

# sequence

const arr = [2, 3, 1, 5, 6, 8, 7, 9, 4]

function getSequence(arr) {
    const len = arr.length
    const result = [0]//默认第0个为参照物
    let p = arr.slice(0)//里面内容无所谓 和原本的数组相同 用来存放索引
    let start
    let end
    let middle

    for (let i = 0; i < len; i++) {
        const arrI = arr[i]
        if (arrI !== 0) {
            let resultLastIndex = result[result.length - 1]
            if (arr[resultLastIndex] < arrI) {
                p[i] = resultLastIndex//标记当前前一个对应的索引
                result.push(i)
                continue
            }
        }

        //二分查找,找到比当前值大的那一个
        start = 0
        end = result.length - 1
        while (start < end) {
            middle = ((start + end) / 2) | 0
            if (arr[result[middle]] < arrI) {
                start = middle + 1
            } else {
                end = middle
            }//找到结果集中比当前这一项大的数

            if (arrI < arr[result[start]]) {
                if (start > 0) {
                    p[i] = result[start - 1]//将它替换成前一个
                }
                result[start] = i
            }
        }
    }

    let len1 = result.length
    let last = result[len1 - 1]
    while (len1-- > 0) {//根据前驱节点一个个向前查找
        result[len1] = last
        last = p[last]
    }
    return result;
}

console.log(getSequence(arr))
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
sequence.ts

# runtime-dom介绍

  • 需要涵盖dom操作api,属性操作api,事件操作api,并将这些api传入到runtime-core中运行
  • 我们在渲染页面的时候进行的节点操作的一系列方法

# index

//这个包的核心就是提供dom api的,操作节点、操作属性的更新
//1节点操作无非就是增删改查
//2属性操作无非就是样式、类、事件、普通事件、其他属性的增删改

import { extend } from "@vue/shared";
import { nodePos } from "./nodeOps";
import { patchProp } from "./patchProp";
import { createRender } from "@vue/runtime-core"

//渲染时用到的所有方法
export const rendererOptions = extend({ patchProp }, nodePos)

// vue-runtime-core里提供了核心的渲染方法,用来处理渲染,他会使用runtime-dom中的api进行渲染
export function createApp(rootComponent, rootProps = null) {
    const app: any = createRender(rendererOptions).createApp(rootComponent, rootProps)
    let { mount } = app
    app.mount = function (container) {
        container = document.querySelector(container)
        container.innerHTML = ''
        //将组件渲染成dom元素进行挂载
        mount(container)
    }
    return app
}

export * from '@vue/runtime-core'
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
index.ts

# nodeOps

export const nodePos = {
    //createElement,不同的平台创建元素的方式不同

    //元素
    createElement: (tagName) => document.createElement(tagName),//增加
    remove: (child) => {//删除
        const parent = child.parentNode
        if (parent) {
            parent.removeChild(child)
        }
    },
    insert: (child, parent, anchor = null) => {
        parent.insertBefore(child, anchor)//如果anchor为空,相当于appendChild
    },
    querySelector: (selector) => document.querySelector(selector),//查询
    setElementText: (el, text) => el.textContent = text,//设置文本内容
    nextSibling: (node) => node.nextSibling,
    
    //文本操作,创建文本
    createText: (text) => document.createTextNode(text),//创建文本
    setText: (node, text) => node.nodeValue = text,//给节点设置文本
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
nodeOps.ts

# patchProp

//这里面针对是一系列属性操作

import { patchAttr } from "./modules/attr"
import { patchClass } from "./modules/class"
import { patchEvents } from "./modules/events"
import { patchStyle } from "./modules/style"

export const patchProp = (el, key, preValue, nextValue) => {
    switch (key) {
        case 'class':
            patchClass(el, nextValue)
            break
        case 'style':
            patchStyle(el, preValue, nextValue)
            break
        default:
            if (/^on[^a-z]/.test(key)) {
                patchEvents(el, key, nextValue)
            } else {
                patchAttr(el, key, nextValue)
            }
            break
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
patchProp.ts
上次更新: 2025/02/15, 13:42:25
最近更新
01
Git问题集合
01-29
02
安装 Nginx 服务器
01-25
03
安装 Docker 容器
01-25
更多文章>
×
×