vue3 响应式数据库—— reactive
Posted twinkle||cll
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue3 响应式数据库—— reactive相关的知识,希望对你有一定的参考价值。
请仔细阅读下面代码,思考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();
自己实现
说了那么多,不如自己来实现一遍简单的响应式系统
以上是关于vue3 响应式数据库—— reactive的主要内容,如果未能解决你的问题,请参考以下文章
vue3.2 响应式之 ref reactive toRef toRefs
vue3.2 响应式之 ref reactive toRef toRefs
Vue3通透教程Vue3中的响应式数据 reactive函数ref函数