网站首页 > 博客文章 正文
请仔细阅读下面代码,思考vue3是如何做响应式数据的?
let temp
// reactive 是使对象变成一个代理
const counter = reactive({ num: 0 });
// effect主要职责是开启依赖收集,等待get的调用完成正常的依赖存储
effect(() => (temp = counter.num));
// 触发更新
counter.num = 1;
复制代码
在这里是不是有的人要说,咋们在日常开发中,直接在 vue 模板上里面写一个ref 自动帮我们进行了开启依赖收集,当我们调用get的时候去存储依赖。事出反常必有妖
在vue源码中的renderer.ts 中有这么这么个代码片段
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// ...省略其他
// 这里会调用一个方法setupRenderEffect
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
}
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
const componentUpdateFn = () => {
// 省略组件更新逻辑
}
// 这一段话的意思是创建一个 ReactiveEffect 来保存每一个独立的proxy,
// 和effect的效果是一样的
const effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update),
instance.scope // track it in component's effect scope
)
}
复制代码
总结下,咋们在模板中使用 ref,reactive等是vue本身在渲染的时候就会把整个组件放入ReactiveEffect中进行依赖收集,对外抛出一个run方法,run方法用于决定是否需要进行依赖收集哦,对于ref处理普通数据准备另开篇幅
reactive
reactive 是用于把对象变成一个代理对象,proxy
function createReactiveObject(
target: Target,
baseHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>) {
// 核心就是 proxy
// 目的是可以侦听到用户 get 或者 set 的动作
// 如果命中的话就直接返回就好了
// 使用缓存做的优化点
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
const proxy = new Proxy(target, baseHandlers);
// 把创建好的 proxy 给存起来,
proxyMap.set(target, proxy);
return proxy;
}
复制代码
effect
effect 函数的作用是用于开启收集依赖,返回run函数
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
const _effect = new ReactiveEffect(fn);
// 合并选项
extend(_effect, options);
// 进来时候就对开启执行run开启依赖收集
_effect.run();
// 把 _effect.run 这个方法返回
// 让用户可以自行选择调用的时机(调用 fn)
const runner: any = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
// 在ReactiveEffect run函数的内容是开启允许依赖收集
export class ReactiveEffect {
active = true;
deps = [];
public onStop?: () => void;
constructor(public fn, public scheduler?) {
console.log("创建 ReactiveEffect 对象");
}
run() {
// 运行 run 的时候,可以控制 要不要执行后续收集依赖的一步
// 目前来看的话,只要执行了 fn 那么就默认执行了收集依赖
// 这里就需要控制了
// 是不是收集依赖的变量
// 执行 fn 但是不收集依赖
if (!this.active) {
return this.fn();
}
// 执行 fn 收集依赖
// 可以开始收集依赖了
shouldTrack = true;
// 执行的时候给全局的 activeEffect 赋值
// 利用全局属性来获取当前的 effect
activeEffect = this as any;
// 执行用户传入的 fn
const result = this.fn();
// 重置
shouldTrack = false;
activeEffect = undefined;
return result;
}
}
复制代码
baseHandlers
baseHandlers 是用于处理proxy里面的get和set的,当proxy调用get和set的时候就会去触发对应的函数
get操作的流程如下:
set 操作的流程如下:
export const mutableHandlers: ProxyHandler<object> = {
get:(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
// 获取对target的key的值
const res = Reflect.get(target, key, receiver);
// 问题:为什么是 readonly 的时候不做依赖收集呢
// readonly 的话,是不可以被 set 的, 那不可以被 set 就意味着不会触发 trigger
// 所有就没有收集依赖的必要了
if (!isReadonly) {
// 在触发 get 的时候进行依赖收集
track(target, "get", key);
}
// 把内部所有的是 object 的值都用 reactive 包裹,变成响应式对象
// 如果说这个 res 值是一个对象的话,那么我们需要把获取到的 res 也转换成 reactive
if (isObject(res)) {
// res 等于 target[key]
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
},
// set值的时候触发
set:(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 在触发 set 的时候进行触发依赖
trigger(target, "set", key);
return result;
};,
};
复制代码
track
在get的时候,通知进行依赖收集,
在进行依赖收集的时候,缓存传入的对象:
这里在缓存依赖的步骤是:
- 第一步: 当运行counter.num 的时候会触发proxy的get方法
- 第二步: 全局有一个targetMap是一个weakMap 来记录couter这个对象是否存在,并且weakMap的key是 couter这个对象,存在则使用,不存在则创建,counter的value又是一个Map,里面记录着counter里面的key和counter对象的key是一一对应的
- 第三步: 在map中判断key是 num的是否存在,存在则使用,不存在则创建一个Set来保存当前的ReactiveEffect 对象,ReactiveEffect 对象含有run方法等待trigger触发
export function track(target, type, key) {
if (!isTracking()) {
return;
}
// 1. 先基于 target 找到对应的 dep
// 如果是第一次的话,那么就需要初始化
let depsMap = targetMap.get(target);
if (!depsMap) {
// 初始化 depsMap 的逻辑
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = createDep();
depsMap.set(key, dep);
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
(activeEffect as any).deps.push(dep);
}
}
复制代码
trigger
当对于track来说,trigger需要做的事情就会简单许多
export function trigger(target, type, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
// 暂时只实现了 GET 类型
// get 类型只需要取出来就可以
const dep = depsMap.get(key);
// 省略其他逻辑
// 触发run函数
for (const effect of dep) {
if (effect.scheduler) {
// scheduler 可以让用户自己选择调用的时机
// 这样就可以灵活的控制调用了
// 在 runtime-core 中,就是使用了 scheduler 实现了在 next ticker 中调用的逻辑
effect.scheduler();
} else {
// 触发函数
effect.run();
}
}
}
复制代码
自己实现
说了那么多,不如自己来实现一遍简单的响应式系统
源码地址
猜你喜欢
- 2024-10-26 尤雨溪在直播中讲到的Vue3.0 Beta的那些特性,快记笔记了
- 2024-10-26 Vue Conf 2023 精彩回顾,新语法草案助 Vue 继续封神
- 2024-10-26 vue3 和Vu2的区别有哪些?(vue3和vue2的优缺点)
- 2024-10-26 基于vue3+ts+vite封装的动态表单,支持编辑生成页面表单配置渲染
- 2024-10-26 什么是Vue 3 “Vapor Mode”(转)(vue3 provider)
- 2024-10-26 Vue 3源码公布,89%的人收藏了它(vue3 源码解读)
- 2024-10-26 Vue 3 源码开放,你学习了吗?(vue源码讲解)
- 2024-10-26 Vue3迁移之路,你已准备起航?(vuereal转移技术原理)
- 2024-10-26 Vue3.3 + TS4 ,自主打造媲美 ElementPlus 的组件库(超清完结)
- 2024-10-26 记一次 Vue2 迁移 Vue3 的实践总结
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)