前言
为了深入理解vue3框架实现的原理,我打算抽时间学习一下vue3源码。之前一直想过要学习的,但由于项目忙、静不下心、懒等原因,慢慢也就搁置了。之前也只是简单的了解过一点vue3的核心源码,只是看了看网上的文章,也没有手写过;这一次打算只保留最核心的代码,手写实现一个极简的 mini-vue3
。
虽然自己在学习vue3源码过程中也是一直在参考网上一些优秀作者的技术文章一点一滴过来的。但我觉得学习一个新事物,只是去看、去了解是不够的!只有自己动手实现一遍,从0到1走过这个过程,且在这个过程中加入自己的思考,这样就算是看别人的文章、案例实现出来的也能学到很多东西。
学会吸取别人精华之处,在理解透彻的基础上自己去总结、复盘,慢慢也就会变成自己的知识。有时候学习也要学会站在巨人的肩膀上前行。
文章如果存在问题,欢迎大家评论区指点、讨论。如果对你有帮助,你的点赞、评论、收藏是我最大的动力。
谨以此篇记录自己的学习过程,同时,也希望能够帮助到其他同学。
mini-vue3源码地址 持续完善中,你的star是对我最大的支持!
专栏系列:
从0到1手写mini-vue3系列------(1)源码工程初始化 - 掘金 (juejin.cn)
遇到的问题
在上一篇中完成了源码工程的初始化。 本篇开始来分析并手写完成vue3核心api ------reactive
,但在写代码调试时遇到了问题。
源码打包调试使用时报错xxx of undefined及警告
-
在使用
rollup
打包后,在html中引入使用时,访问导出的具体方法时报错 -
终端报
Unresolved dependencies
警告
报错原因
与 webpack
不同的是 rollup
并不知道如何寻找路径以外的依赖 ,比如 node_module
中的依赖。 所以需要借助 @rollup/plugin-node-resolve
插件帮助程序可以在项目依赖中找到对应文件。
安装@rollup/plugin-node-resolve解决
- @rollup/plugin-node-resolve安装报错
翻译:
运行此命令会将依赖项添加到工作区根目录中,这可能不是您想要的-如果您真的这么想,请使用-w标志(或--workspaceroot)再次运行此命令,使其明确。如果您不想再看到此警告,可以将忽略工作区根目录检查设置设置为true。
加入-w再次执行即可安装成功
pnpm -w i @rollup/plugin-node-resolve -D
rollup配置文件:
js
import resolve from '@rollup/plugin-node-resolve'
export default {
plugins: [
resolve(),
]
}
重新打包后,在 packages/vue/examples
提供的案例中 index.html
,引入,查看打印发现一切正常:
reactive分析
js
const obj = reactive({
num1: 1,
num2: 2,
name: '王五',
info: {
age: 20,
},
});
在vue3中通过reactive
API创建包裹的对象都是具备响应式的;这里有个重要的点 reactive
对象属性发生改变时 会造成副作用。
这里的副作用也就是 副作用API(effect) ,如果effect 内部依赖了reactive ,则reactive的改变会重新触发effect。
这一篇主要看看 reactive
是如何实现的,下篇来研究副作用 effect
的实现。
reactive初始化
reactive 实际上就是返回了一个 proxy 代理后的对象。仿照vue3源码格式,我们看看它做了些什么?
reactive核心源码:
js
import { mutableHandlers } from './baseHandlers'
/** 使用 weakMap 缓存 proxy */
const reactiveMap = new WeakMap()
/**
* @description: reactive 响应式api 入口函数
* @param {*} target 要代理的目标对象
* @return {*}
*/
export const reactive = (target) => {
return createReactiveObject(target, reactiveMap, mutableHandlers)
}
/**
* @description: 创建处理代理对象
* 核心是proxy,目的是可以监听到用户get、set操作
* @param {*} target
* @param {*} proxyMap
* @param {*} baseHandlers
* @return {*}
*/
function createReactiveObject (target, proxyMap, baseHandlers) {
// 1、get: 如果已经代理过直接返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 2、如果没有被代理,直接创建一个 proxy进行代理
const proxy = new Proxy(target, baseHandlers)
// 3、把创建好的 proxy 给存储到全局 reactiveMap
proxyMap.set(target, proxy)
return proxy
}
看上面源码发现 reactive
API内部其实是 return
了一个叫 createReactiveObject
的函数; createReactiveObject
函数接收了3个参数:
- target
目标对象也就是reactive
传入的对象 - proxyMap
全局变量WeakMap
,用于存储proxy
- baseHandlers
proxy
的拦截器(下面单独说)
该函数主要判断目标对象是否被代理过,如果代理过直接返回;没代理过直接创建 proxy对象返回,并且将该 proxy
存储到全局变量 reactiveMap(WeakMap) 中。
使用 WeakMap 数据结构来缓存 proxy
此时,全局变量 reactiveMap
的key-value对应关系:
- key-> target(原目标对象)
- value->proxy(proxy代理后的对象)
使用 WeakMap
进行了一次缓存,这样同一个值再次进行reactive的时候就会读取缓存中的值。
baseHandlers
packages/reactivity/src/baseHandlers.js
js
const get = createGetter()
const set = createSetter()
export const mutableHandlers = {
get,
set
}
// ...
上面 createReactiveObject
函数中传入的baseHandlers
, 其实就是导出了 get\set
两个函数,用于 proxy
中的拦截器。
getter函数
get 阶段通过Reflect 返回结果,并触发 track(依赖收集)
js
function createGetter () {
return function get (target, key, receiver) {
// console.log('proxy get读取')
// 正常访问,使用 Reflect 反射对象
const result = Reflect.get(target,key,receiver)
// 核心:依赖收集
track(target, key)
// 嵌套结构对象父级对象属于读取操作,如子类没有代理,将不会监听到子类对象的变化,
// 就无法触发effect,所有进行递归处理嵌套对象
if (isObject(result)) {
return reactive(result)
}
return result
}
}
setter函数
set 阶段通过Reflect 完成赋值,并触发 trigger(依赖触发)
js
function createSetter () {
return function set (target, key, newValue, receiver) {
// console.log('proxy set设置')
const result = Reflect.set(target, key, newValue, receiver)
// 核心:依赖触发
trigger(target, key)
return result
}
}
总结
reactive
返回使用 proxy 代理后的对象 。其中在拦截器 getter、setter
中配合 Es6提供的 Reflect
(反射)去读取、设置对象的返回值。
reactive
函数核心其实就是proxy + Reflect的基础使用。
下一篇将分析 getter、setter
阶段最重要的 track依赖收集、 trigger依赖触发、以及副作用 effect
。