大家好,我是卡颂。
周末没啥事,准备找个优秀且代码量不多的库学习下。最终选择了最近发布的petite-vue,原因如下:
- 代码量少(只有5.8kb),且源码模块化程度高(相比于
React
),易读 - 基于
Vite
构建,执行yarn dev
就能开始调试源码 - 没有
虚拟DOM
、编译时方案,可以作为读Vue
源码的铺垫 - 底层的响应式更新原理同样适用于
Mobx
、SolidJS
等库,一次阅读多份收获
就决定是他了。
但是周末时间这么宝贵,而且我都4年没用过Vue
了,如何才能高效学习源码呢?
最好是在不看源码的情况下把源码学了。
接下来,我以petite-vue
为例为大家示范学源码的正确姿势。
怎么快怎么来
可以将petite-vue
理解为:用真实DOM
取代Vue
模版的简易Vue
。
比如如下Demo
:
<script type="module">
import { createApp } from '../src'
createApp({count: 0}).mount()
</script>
<div v-scope>
<button @click="count++">add 1</button>
<p>{{count}}</p>
</div>
div
及其子孙节点是真实的DOM
标签,所以页面初始化时如下:
接着执行如下代码,完成petite-vue
初始化:
createApp({count: 0}).mount()
此时页面:
读框架源码切忌一上手就从入口函数一路调试,很容易就懵逼了。正确的方式是像剥洋葱一样一层一层剥开:
所以,让我们先从Performance
面板看看首屏渲染的调用栈:
调用栈大体分为蓝框、红框两部分,先看左边蓝框部分:
通过createContext
与reactive
关键词判断大概是创建响应式上下文
。至于响应式
的含义,我们还不清楚。
接着看右边红框部分:
从调用栈深度、页面渲染的效果我们猜测,这部分做的工作包括:
- 遍历
DOM
- 完成数据与视图的双向绑定
- 初始化渲染
接下来,我们来验证猜想。
注意,到目前为止,我们一行源码都还没看
验证遍历DOM
调用栈中walk
与walkChildren
被调用多次,大概率他们就是具体遍历工作执行的方法,让我们确认下。
在源码walk
方法中打上log
:
export const walk = (node: Node, ctx: Context): ChildNode | null | void => {
console.log('walk', node);
// ...
}
排除换行符"\n "
对应的文本节点,打印顺序如下:
walk div
walk <button>add 1</button>
walk "add 1"
walk <p>0</p>
walk "0"
从打印结果看,这是个深度优先遍历(如果有子节点就遍历子节点,没有子节点就遍历兄弟节点)
显然,petite-vue
mount
时采用深度优先遍历,并对遍历到的每个与上下文状态相关的DOM
节点进行处理。
在Demo
中,上下文包含状态{count: 0}
:
createApp({count: 0}).mount()
在遍历后<p>{{count}}</p>
变为<p>0</p>
。
确定双向绑定的粒度
接下来我们需要确认双向绑定的作用范围,即:
触发更新后,多大范围的DOM
会被重新遍历并执行相应DOM
操作?
打开Performance
后,点击<button>add 1</button>
触发更新:
可以看到,没有任何walk
、walkChildren
(或类似遍历过程),只调用了reactiveEffect
一个方法就更新了DOM
。
这意味着mount
时的深度优先遍历建立了状态
与更新DOM的方法
之间一一对应的关系。
因为对应关系确定了,就不再需要额外的遍历过程确定需要变化的DOM
。
当更新状态
后,只需要找到与他有关系的更新DOM的方法
执行就行。
比如:将count
状态与如下函数建立联系:
function setCount(value) {
p.textContent = value;
}
每当count
变化后调用setCount(count)
就能更新p
对应DOM
。
所以,petite-vue
的工作原理,主要包括两点:
mount
时深度优先遍历DOM
,对有状态的DOM
(比如<p>{{count}}</p>
)建立状态
与更新DOM的方法
之间一一对应的关系update
时找到该状态
对应的更新DOM的方法
并执行
可以看到,即使不深入源码,也能大体了解工作流程。
如果你想更进一步,比如了解关系是如何建立的(涉及到响应式更新),那么就需要深入源码了。
这里推荐Vue Mastery
的Vue 3 Reactivity
课程,可以补齐响应式更新这块知识。
总结
本文介绍了复杂框架源码的阅读办法 —— 即从抽象到具体。
- 从
mount
时与update
时的调用栈推导出整体工作流程
- 从
整体工作流程
中发现核心知识 —— 响应式更新
当掌握整体工作流程
与响应式更新
后,再阅读自己感兴趣的部分才不至于陷入庞大的代码量中。
你,学废了么?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。