Wiidede's blog Wiidede's blog
  • 前端
  • Python
  • 算法
  • 生活
  • 其他
  • 分类
  • 标签
  • 归档
  • 关于我
  • 赞赏
  • 我的小站 (opens new window)
GitHub (opens new window)

Wiidede

小的的写前端
  • 前端
  • Python
  • 算法
  • 生活
  • 其他
  • 分类
  • 标签
  • 归档
  • 关于我
  • 赞赏
  • 我的小站 (opens new window)
GitHub (opens new window)
  • 整理一些css样式
  • vue隔代组件层层动态插槽并且附带数据
  • vue判断字符串是否溢出来显示弹窗、解决el-table tooltip 内过多导致无法显示,内容闪烁
  • 整理一些js写法
  • ElementUI timePicker 增加此刻按钮 引发的dom操作的学习
  • 毕业设计(水表识别)前端知识整理
  • html小知识
  • axios请求api然后下载文件
  • vue3+ts根据高度改变元素的透明度
  • vue3 + ElementPlus 换肤方案(Css变量)
  • Moment的一些使用方法
  • echarts基础vue组件
  • element UI el-date-picker 年月日切换组件
  • 可以不选择的el-radio单选框
  • vue的小技巧总结
  • 全局动态权限判断(Vue指令)
  • vue-anchor 探索
  • Deep Dive with Evan You 笔记
    • 1 - Intro
    • 2 - Rendering Mechanism
    • 3 - How & when to use Render functions
    • 4 - Compiler & Renderer API
    • 5 - Creating a Mount function
    • 6 - Implementing a Patch Function
    • 7 - Intro to Reactivity
    • 8 - Building Reactivity from Scratch
    • 9 - Building the Reactive API
    • 10 - Creating a Mini Vue
    • 11 - The Composition API
    • 12 - Code organization
    • 13 - Logic Reuse
    • 14 - A Composition API Example
    • 15 - Parting Thoughts
  • 前端基础知识查漏补缺
  • WebPack 知识总结
  • 我写的一些可以日后参考的代码
  • 接口变化后,封装接口函数,改变返回内容
  • 项目组件整理
  • 前端框架设计想发
  • 全局进度条
  • 带有token的图片vue组件:authImg,使用axios下载图片
  • 前端npm包推荐
  • 给ElInputNumber添加prefix
  • ElPagination添加页数总数
  • el-tab做成chrome类似的tab样式
  • vue-grid-layout-组件配置
  • 项目数据字典封装
  • 图表组件响应式探索
  • ElementPlus表格table列自动合并composition
  • 简单的curd组件封装
  • ElementPlus表格自定义合计列composition
  • 一些处理表格数据composition api
  • div内容溢出后,内容向左悬浮,vue组件封装
  • 文本数字溢出后,按比例缩小,vue组件封装
  • 表格使用async-validator检验composition
  • ElementPlus Form一些简单的组件整合
  • arco-design快速使用tailwind的颜色、unocss动态颜色
  • 前端
wiidede
2022-02-09

Deep Dive with Evan You 笔记

# Deep Dive with Evan You 笔记

这是一篇 Vue Mastery 上的课程,尤大带我们实现一个简单的 Vue,并且介绍了一些 Vue 3 中的一些使用技巧。

同时,我也建了一个仓库 (opens new window)来存放这些代码。

# 1 - Intro

Vue 的工作流:

  • 将 template 编译成 render function;
  • render function 会生成虚拟 DOM,也就是 vNode;
  • vNode 经过 Vue 的处理就可以生成真实的 DOM;
  • 当数据变化,就生成新的 vNode,与旧的 vNode 进行对比(patch 阶段),将有变化的地方反应到真的的 DOM 上。

Vue的三个模块:

  • reactivity module:将数据做响应式处理;
  • compiler module:将 template 编译成 render function;
  • renderer module:
    • render phase:render function 生成 vNode;
    • mount phase:vNode 转成真实的 DOM 节点挂载到网页上;
    • patch phase:比较旧的 vNode 和 新的 vNode,将有变化的部分反应到网页上;

# 2 - Rendering Mechanism

渲染机制

vnode的好处:

  • 组件渲染逻辑与真实的 DOM 节点解耦
  • 复用框架更直接(比如可以自定义渲染方案)
  • 在生成渲染函数前去构建出想要的 DOM 结构(比 template 更灵活地去构造一些特殊的结构)

渲染函数,Vue 3 的改进:

import { h } from 'vue' // 从全局引入 h 函数,而不再作为 render 函数的第一个参数

function render() {
    return h('div', { // 扁平化的对象结构
        id: 'foo',
        onClick: this.onClick // on 开头的会绑定一个 listener
    }, 'hello')
}
1
2
3
4
5
6
7
8

# 3 - How & when to use Render functions

v-if --> if / ?:

v-for --> list.map(() => {return h(...)})

就是说 render 函数里是纯粹的 javascript 语句。

如果你觉得使用 template 无法满足你想要表达的逻辑,使用 javascript 能够更好地组织你要写的代码的时候,你可以尝试使用 render 函数。

也就是说,日常开发,你可以用 template,因为他更简单直观,并且 Vue 在编译的时候做了很好地优化,如果你在开发一些底层组件,逻辑比较复杂的时候,就可以使用 render。

以下是一个使用 render 函数的例子,使用 render 函数,给 Stack 组件的所有子元素都包裹在一个 div 中,可以看到使用 render 函数去实现是比较简单的方法,相较于 template。

<script src="https://unpkg.com/vue"></script>
<style>
    .mt-4 {
        margin: 10px;
    }
</style>

<div id="app"></div>

<script>
    const {h, createApp} = Vue

    // 定义 Stack 组件
    const Stack = {
        props: ['size'], // 视频中尤大貌似没有指明 props
        render() {
            // 获取默认插槽
            const slot = this.$slots.default
                ? this.$slots.default()
                : []

            // 将插槽中的所有内容套上 div,并且读取 props 上的 size 属性,
            // 并构成类名
            return h('div', {class: 'stack'}, slot.map(child => {
                return h('div', {class: `mt-${this.$props.size}`}, [
                    child
                ])
            }))
        }
    }

    // App 组件
    const App = {
        template: `
            <Stack size="4">
            <div>hello</div>
            <Stack size="4">
                <div>hello</div>
                <div>hello</div>
            </Stack>
            </Stack>
        `,
        components: {
            Stack
        }
    }

    // 创建 vue 实例并挂载到 DOM 上
    createApp(App).mount('#app')
</script>
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

# 4 - Compiler & Renderer API

Vue 3 Template Explorer

使用 Vue 3 Template Explorer 就可以看到 Vue 3 把模板编译成了什么样的渲染函数。

一些特性(性能优化,在编译成 render function 时给 compiler 足够的提示,告诉 compiler ,什么需要处理,什么不需要处理)(这也是为什么写 template 比直接写 render 更高效的原因):

  • 标记静态节点(对象只会在初始化的时候创建一次、diff时直接不比较)
  • 标记出会动态变化的属性(只检查动态变化的属性,跳过静态的属性)
  • 缓存 event (这样 v-on 就不会被标记为动态变化的属性;如果在子组件上写 v-on 事件,在 patch 阶段并不会刷新整个子组件,而 Vue 2 是会的)
  • 引入 block 的概念(需要动态更新的节点会被添加到 block 上,无论这个节点有多深;v-if 会开启一个新的 block,这个 block 又被其父 block 跟踪;总的来说就是在 diff 的时候不需要在去深度遍历判断,而是从 block 记录的动态节点数组上,去遍历会变化的 vNode)
  • 动态节点的标记有不同的类型(更方便的 diff)

所以,从一个更高的角度来看 Vue 就是想实现:

<div id="app"></div>

<script>
    function h(tag, props, children) {

    }

    function mount(vnode, container) {

    }

    const vdom = h('div', {class: 'red'}, [
        h('span', null, ['hello'])
    ])

    mount(vdom, document.getElementById('app'))
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

即:h 生成 vdom,将 vdom 挂载到真实 dom 上

# 5 - Creating a Mount function

实现一个简单的 mount

<style>
    .red {
        color: red;
    }
</style>
<div id="app"></div>

<script>
    // 渲染函数 直接返回一个简单的 vNode
    function h(tag, props, children) {
        return {
            tag,
            props,
            children
        }
    }

    // 将 vNode 挂载到真实的 DOM 上面
    function mount(vnode, container) {
        // 根据 vNode 信息创建 DOM
        const el = vnode.el = document.createElement(vnode.tag)

        // props
        if (vnode.props) {
            for (const key in vnode.props) {
                const value = vnode.props[key];
                // 设置 DOM 的属性
                el.setAttribute(key, value)
            }
        }
        // children
        if (vnode.children) {
            // 如果是字符串,直接修改 textContent
            if (typeof vnode.children === 'string') {
                el.textContent = vnode.children
            } else {
                // 数组里面默认都是 vNode
                vnode.children.forEach(child => {
                    mount(child, el)
                })
            }
        }

        // 将创建的 DOM 元素放到 container 中
        container.appendChild(el)
    }

    // 直接 h、mount 即可看到页面上已经出现了红色的 hello
    const vdom = h('div', {class: 'red'}, [
        h('span', null, 'hello')
    ])

    mount(vdom, document.getElementById('app'))
</script>
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

# 6 - Implementing a Patch Function

注意这里是从第5节课的基础上扩展出来的,可以直接从 patch 阶段开始看。

<style>
    .red {
        color: red;
    }

    .green {
        color: green;
    }
</style>
<div id="app"></div>

<script>
    // 渲染函数 直接返回一个简单的 vNode
    function h(tag, props, children) {
        return {
            tag,
            props,
            children
        }
    }

    // 将 vNode 挂载到真实的 DOM 上面
    function mount(vnode, container) {
        // 根据 vNode 信息创建 DOM
        const el = vnode.el = document.createElement(vnode.tag)

        // props
        if (vnode.props) {
            for (const key in vnode.props) {
                const value = vnode.props[key];
                // 设置 DOM 的属性
                el.setAttribute(key, value)
            }
        }
        // children
        if (vnode.children) {
            // 如果是字符串,直接修改 textContent
            if (typeof vnode.children === 'string') {
                el.textContent = vnode.children
            } else {
                // 数组里面默认都是 vNode
                vnode.children.forEach(child => {
                    mount(child, el)
                })
            }
        }

        // 将创建的 DOM 元素放到 container 中
        container.appendChild(el)
    }

    // 直接 h、mount 即可看到页面上已经出现了红色的 hello
    const vdom = h('div', {class: 'red'}, [
        h('span', null, 'hello')
    ])

    mount(vdom, document.getElementById('app'))

    // patch 阶段
    function patch(n1, n2) {
        // 如果两个 node 是相同类型的
        if (n1.tag === n2.tag) {
            // 拿到旧的节点的 DOM
            const el = n2.el = n1.el
            // props
            const oldProps = n1.props || {}
            const newProps = n2.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') {
                    // 新的孩子和旧的孩子都是 string 类型,即都是文本类型
                    if (newChildren !== oldChildren) {
                        // 直接替换 textContent
                        el.textContent = newChildren
                    }
                } else {
                    // 新孩子是文本节点,但是旧孩子是数组,直接设置 textContent,
                    // 这样会覆盖旧的子 DOM 节点,并丢弃它们
                    el.textContent = newChildren
                }
            } else {
                if (typeof oldChildren === 'string') {
                    // 新孩子是数组,但是旧孩子是文本节点
                    // 清空旧的文本节点
                    el.innerText = ''
                    // 遍历数组,将子 vNode 挂载到 el 上
                    newChildren.forEach(child => {
                        mount(child, el)
                    })
                } else {
                    // 新孩子和旧孩子都为数组
                    // diff 阶段
                    // diff 在 Vue 内部有两种方式,一种是通过 key,一种是遍历比较
                    // 这里直接遍历
                    // 获取共有的长度,直接每个子节点都进行 patch
                    // 这样可能非常低效,但是足够直观并且能保证一致性
                    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) {
                        // 如果新孩子更长,将新孩子多余的节点添加到 DOM 上
                        newChildren.slice(oldChildren.length).forEach(child => {
                            mount(child, el)
                        })
                    } else if (newChildren.length < oldChildren.length) {
                        // 如果旧孩子更长,将旧孩子多余的节点从 DOM 上移除
                        oldChildren.slice(newChildren.length).forEach(child => {
                            el.removeChild(child.el)
                        })
                    }
                }
            }

        } else {
            // 如果两个 node 是不同类型的,则需要用新的 node 替换 旧的 node
            // 这里省略
            // replace
        }
    }

    const vdom2 = h('div', {class: 'green'}, [
        h('span', null, 'changed!')
    ])

    patch(vdom, vdom2)
</script>
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

这里其实会有很多判断的分支,就是 patch 阶段会有各种情况。

Vue 有自己的测试 Vue next coverage,表明有多少代码被测试覆盖到了。

# 7 - Intro to Reactivity

响应式的能力:某个值发生变化,相应的 值 / 副作用 也发生变化。

依赖追踪(dependency tracking)的基础形式:

onStateChanged(() => {
    console.log(state.count)
})
1
2
3

即 state 发生变化,就自动执行相应的语句。

Vue 3's Reactivity API:

import { ractive, watchEffect } from 'vue'

// 创建一个响应式对象
const state = reactive({
    count: 0
})

// 会收集所有的依赖,在执行过程,如果某个响应式属性被使用,那么整个函数就会执行
// 相当于上面提到的 onStateChanged
watchEffect(() => {
    console.log(state.count)
}) // 0

state.count++ // 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 8 - Building Reactivity from Scratch

从头开始构建 Reactivity:

<script>
    // 记录当前的 effect
    let activeEffect;

    // 创建一个 Dep 类
    class Dep {
        // 构造的时候,记录 effect 到 subscribers 集合中
        // 这里直接把值记录在 Dep 中
        constructor(value) {
            this.subscribers = new Set();
            this._value = value;
        }

        // 获取的时候收集依赖
        get value() {
            this.depend();
            return this._value;
        }

        // 赋值的时候通知更新
        set value(newValue) {
            this._value = newValue;
            this.notify();
        }

        // 将当前的 effect 放入 subscribers 集合
        // 使用集合可以避免重复收集依赖
        depend() {
            if (activeEffect) {
                this.subscribers.add(activeEffect);
            }
        }

        // 更新的时候,把集合里的 effect 都执行一遍
        notify() {
            this.subscribers.forEach(effect => {
                effect()
            });
        }
    }

    // 把传入进来的回调函数设置成当前的 effect
    // 然后立马执行一遍回调函数
    // 执行完后清空当前的 effect
    function watchEffect(effect) {
        activeEffect = effect;
        effect();
        activeEffect = null;
    }


    // 接下来是测试的代码
    const ok = new Dep(true);
    const count = new Dep(0);

    watchEffect(() => {
        if (ok.value) {
            console.log(count.value);
        } else {
            console.log('error branch');
        }
    })

    count.value++;
    ok.value = false;
    // 尤大希望执行这一遍后,count 的依赖被删除,
    // 因为 watchEffect 中不会再执行到 count.value,
    // 但接下来改变 count 还是会触发 watchEffect,因为 count 的依赖还存在,
    // 所以触发了 console.log('error branch')
    // 所以 Vue 3 的源码中,每次都会整理依赖
    count.value++;
</script>

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

# 9 - Building the Reactive API

先用 Vue 2 的方法,也就是 Object.defineProperty 来实现:

<script>
    let activeEffect;

    // 有了 reactive,Dep 中无需记录值,值就在原来的对象上
    // Dep 只是单纯地收集依赖,执行 effect
    class Dep {
        subscribers = new Set();

        depend() {
            if (activeEffect) {
                this.subscribers.add(activeEffect);
            }
        }

        notify() {
            this.subscribers.forEach(effect => {
                effect()
            });
        }
    }

    function watchEffect(effect) {
        activeEffect = effect;
        effect();
        activeEffect = null;
    }


    // Vue 2 采用 Object.defineProperty 为对象上的每一个 key 添加响应式
    // 这样做的缺点就是新增的属性无法自动添加响应式
    function reactive(raw) {
        Object.keys(raw).forEach(key => {
            const dep = new Dep();
            let value = raw[key];

            Object.defineProperty(raw, key, {
                get() {
                    // 获取 key 对应的值时添加依赖
                    dep.depend();
                    return value;
                },
                set(newValue) {
                    // 设置 key 对应的值时进行更新
                    value = newValue;
                    dep.notify();
                }
            })
        })

        return raw
    }

    const state = reactive({
        count: 0
    })

    watchEffect(() => {
        console.log(state.count)
    });

    state.count++
</script>
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

接下来是 Vue 3 的做法,也就是修改 reactive 的实现。

Vue 2 采用 Object.defineProperty,对每一个 key 做响应式处理,而 Vue 3 使用 Proxy,是直接对整个对象本身做响应式处理,这样的好处就是新添加的属性,也可以被自动处理,并且使用 Proxy 可以直接处理数组,而不是像 Vue 2,针对数组特殊处理,修改数组的原型链,这种比较 hack 的方式进行处理。

<script>
    let activeEffect;

    class Dep {
        subscribers = new Set();

        depend() {
            if (activeEffect) {
                this.subscribers.add(activeEffect);
            }
        }

        notify() {
            this.subscribers.forEach(effect => {
                effect()
            });
        }
    }

    function watchEffect(effect) {
        activeEffect = effect;
        effect();
        activeEffect = null;
    }

    // 创建一个存放 deps 的弱引用 Map,key 为 target 本身
    // 即需要响应式处理的对象本身
    // WeakMap 只能用 object 作为 key,并且无法被遍历
    // 当 target 不再需要的时候,可以正确地被垃圾处理机制回收
    const targetMap = new WeakMap();

    function getDep(target, key) {
        // 获取 target 对应的 deps,不存在就创建
        let depsMap = targetMap.get(target);
        if (!depsMap) {
            depsMap = new Map();
            targetMap.set(target, depsMap);
        }
        // 获取 target[key] 对应的 dep,不存在就创建
        let dep = depsMap.get(key);
        if (!dep) {
            dep = new Dep();
            depsMap.set(key, dep);
        }
        return dep
    }

    const reactiveHandlers = {
        // 因为 get 和 set 里都需要获取 dep,故抽成一个获取 dep 的函数
        get(target, key, receiver) {
            const dep = getDep(target, key);
            // 收集依赖
            dep.depend();
            // 使用 Reflect 确保与原始的 get 一致
            return Reflect.get(target, key, receiver);
        },
        set(target, key, value, receiver) {
            const dep = getDep(target, key);
            // Proxy 的 set 需要返回一个值
            const result = Reflect.set(target, key, value, receiver);
            // 通知更新
            dep.notify();
            return result;
        },
    }

    function reactive(raw) {
        // 使用 Proxy,更方便地拦截处理
        return new Proxy(raw, reactiveHandlers);
    }

    const state = reactive({
        count: 0
    })

    watchEffect(() => {
        console.log(state.count)
    });

    state.count++
</script>
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

# 10 - Creating a Mini Vue

将之前 vdom 和 reactivity 的代码都复制过来,并且删除演示用的代码。

你可以在这里 (opens new window)查看完整的源代码。

大体结构如下:

<script>
    // vdom

    function h(tag, props, children) {...}

    function mount(vnode, container) {...}

    function patch(n1, n2) {...}

    // reactivity

    let activeEffect;

    class Dep {...}

    function watchEffect(effect) {...}

    const targetMap = new WeakMap();

    function getDep(target, key) {...}

    const reactiveHandlers = {...}

    function reactive(raw) {...}

</script>
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

然后添加:

<div id="app"></div>
<script>
    // vdom
    // reactivity

    // App 组件
    const App = {
        data: reactive({
            count: 0
        }),
        render() {
            return h('div', {
                // 这里需要在 mount 中添加事件处理,之前没有实现
                onClick: () => {
                    this.data.count++
                }
            }, String(this.data.count)) // 第三个参数这里只支持 String
        }
    }

    // 挂载 App
    function mountApp(component, container) {
        let isMounted = false
        let prevVdom
        // component 组件中有响应式对象发生变化,便会执行以下函数
        watchEffect(() => {
            if (!isMounted) {
                // 没有挂载,即初始化
                // 记录旧的 vdom
                prevVdom = component.render()
                // 挂载
                mount(prevVdom, container)
                isMounted = true
            } else {
                // 获取新的 vdom
                const newVdom = component.render()
                // patch
                patch(prevVdom, newVdom)
                prevVdom = newVdom
            }
        })
    }

    mountApp(App, document.getElementById('app'))
</script>
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

至此,一个 mini-vue 就完成了。

# 11 - The Composition API

这节课开始更少地记录,更多的可以阅读 Vue 的官方文档。

Composition API = Reactivity API + Lifecycle hooks

  • ref
  • setup
  • effect
  • watch
  • usexxx

# 12 - Code organization

usexxx,更容易抽离,几乎都是函数式的调用。

export setup

# 13 - Logic Reuse

minix

higher-order component 高阶组件 (React 的做法)

scoped slot / render props

Composition API

# 14 - A Composition API Example

你甚至可以在usexxx()中使用 watchEffect

<script src="https://unpkg.com/vue"></script>

<div id="app"></div>

<script>
    const {createApp, ref, watchEffect} = Vue

    // 进一步简化在组件中的 use
    function usePost(getId) {
        return useFetch(() => `https://jsonplaceholder.typicode.com/todos/${getId()}`)
    }

    // 抽出 fetch,并且你可以在的 useFetch 中使用 watchEffect 来监听传进来的值的变化
    function useFetch(getUrl) {
        const data = ref(null)
        const error = ref(null)
        const isPending = ref(true)

        watchEffect(() => {
            // reset
            data.value = null
            error.value = null
            isPending.value = true
            // fetch
            fetch(getUrl())
                .then(res => res.json())
                .then(_data => {
                    data.value = _data
                })
                .catch(err => {
                    error.value = err
                })
                .finally(() => {
                    isPending.value = false
                })
        })

        return {
            data,
            error,
            isPending
        }
    }

    const Post = {
        template: `
            <div v-if="isPending">loading</div>
            <div v-else-if="data">{{ data }}</div>
            <div v-else-if="error">Something went Wrong: {{ error.message }}</div>
        `,
        props: ['id'],
        setup(props) {
            // prop.id 被传到了 useFetch 的 watchEffect 中
            // 所以 prop.id 变化,即可重新 fetch
            const {data, error, isPending} = usePost(() => props.id)

            return {
                data,
                error,
                isPending
            }
        }
    }

    const App = {
        components: {Post},
        data() {
            return {
                id: 1
            }
        },
        template: `
            <button @click="id++">change ID</button>
            <Post :id="id"></Post>
        `
    }

    createApp(App).mount('#app')
</script>

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

# 15 - Parting Thoughts

  • Vue 3 源码使用 TypeScript,掌握它很有帮助
  • React hooks 和 Composition API 的逻辑很类似,可以比较轻松的移植
  • render / template
#前端#Vue#源码
上次更新: 2023/06/01, 12:40:50

← vue-anchor 探索 前端基础知识查漏补缺→

Theme by Vdoing | Copyright © 2021-2023 Wiidede | Website use MIT License | Article content & logo use CC-BY-SA-4.0 License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式