Ref 全家桶 & 源码解析
示例
源码解析
Vue Core: https://github.com/vuejs/core
ref 跟 shallowRef
packages/reactivity/src/ref.ts
// ...
export function ref(value?: unknown) {
return createRef(value, false);
}
// ...
export function shallowRef(value?: unknown) {
return createRef(value, true);
}
// ...
都是调用createRef()
先调用一次isRef
判断值是否 ref, 返回原始值或者一个RefImpl
对象
// ...
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
// ...
ref
和shallowRef
: 实例化RefImpl
时向构造函数参数__v_isShallow
传入不同布尔值(false
/true
)
- 如果是
shallowRef
, 值(_value
)和原始值(_rawValue
)均为值本身 - 如果是
ref
, 值(_value
)会调用一次toReactive
将传入值转换为响应式对象, 原始值(_rawValue
)调用toRaw
返回值本身
读取ref
和shallowRef
时分别返回响应式_value
和原始value
更新时会先判断是否为浅响应式对象, 对新值使用toReactive
再包装一遍或直接使用新值, 最后对视图进行更新(triggerRefValue
)
WARNING
由于ref
跟shallowRef
都使用triggerRefValue
更新视图
如果shallowRef
跟ref
同时设置值, shallowRef
作用跟ref
一样
// ...
class RefImpl<T> {
private _value: T;
private _rawValue: T;
public dep?: Dep = undefined;
public readonly __v_isRef = true;
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value);
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
newVal = useDirectValue ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = useDirectValue ? newVal : toReactive(newVal);
triggerRefValue(this, newVal);
}
}
}
// ...
triggerRefValue
若ref.dep
存在则进行依赖更新
// ...
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
ref = toRaw(ref);
const dep = ref.dep;
if (dep) {
if (__DEV__) {
triggerEffects(dep, {
target: ref,
type: TriggerOpTypes.SET,
key: "value",
newValue: newVal,
});
} else {
triggerEffects(dep);
}
}
}
// ...
packages/reactivity/src/effect.ts
triggerEffects
触发依赖更新
TIP
原代码
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
这个变更来源于一次Issues:
Github Issues: Computed values are not consistent in watch callback
Evan you进行了一次修复:
Github Commit: fix(reactivity): ensure computed is invalidated before other effects
- 首先将传入的
dep
转换为数组形式。 - 第一次遍历执行计算属性的副作用更新。
- 第二次遍历执行普通的副作用更新。
// ...
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
const effects = isArray(dep) ? dep : [...dep];
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo);
}
}
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo);
}
}
}
// ...
triggerEffect
触发副作用
- 如果当前的
effect
不是激活状态下的activeEffect
,或者effect
允许递归调用,则执行下一步操作。 - 如果
effect
设置了scheduler
,则调用scheduler
函数,否则直接调用effect.run()
执行effect
副作用函数。 - 如果设置了
effect.onTrigger
,则调用effect.onTrigger()
函数,并传入effect
和debuggerEventExtraInfo
两个参数。
其中,effect.allowRecurse
属性用来控制副作用函数是否可以递归调用。默认情况下,为 false
,也就是不允许递归调用。这是为了避免无限递归调用的情况发生。
debuggerEventExtraInfo
参数用于调试目的,可以在触发副作用函数时传递一些额外信息,用于调试和分析代码的执行情况。
所以,triggerEffect
的作用是触发副作用函数 effect
,根据 effect
的属性和设置来控制 effect
函数的执行时机和方式。
// ...
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo));
}
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
}
// ...
代码
<template>
<!-- ref -->
<div>{{ Man }}</div>
<div>
{{ Man1 }}
</div>
<hr />
<button @click="change">修改</button>
<hr />
<!-- shallowRef -->
<div>{{ Man4 }}</div>
<hr />
<button @click="change1">修改</button>
<hr />
<!-- customRef -->
<div>{{ Man5 }}</div>
<hr />
<button @click="change2">修改</button>
<hr />
<!-- ref获取dom元素 -->
<div ref="dom" @click="change3">我是dom</div>
<hr />
</template>
<!-- <script lang="ts">
export default {
data() {
return {
// 只有data()中return的元素才是响应式的
age: 18
}
}
}
</script> -->
<script setup lang="ts">
// 深层次响应式
import { ref } from "vue";
// 浅层次响应式
import { shallowRef } from "vue";
// 检查是否为ref对象
import { isRef } from "vue";
// 更新视图
import { triggerRef } from "vue";
// 自定义Ref
import { customRef } from "vue";
import type { Ref } from "vue";
const Man = { name: "小满" };
type M = {
name: string;
};
// 自动类型推导
const Man1 = ref({ name: "小满" });
// Ref
const Man2: Ref<M> = ref({ name: "小满" });
// 自己写
const Man3 = ref<M>({ name: "小满" });
const Man4 = shallowRef({ name: "小满2" });
const change = () => {
Man.name = "大满";
Man1.value.name = "大满";
console.log(Man);
console.log(isRef(Man1));
};
const change1 = () => {
// shallowRef无法响应到value内部
// (如果跟ref一起用, 造成视图更新(调用了triggerRef), shallowRef就会响应到value内部)
Man4.value.name = "大满2";
// 通过triggerRef强制更新视图
triggerRef(Man4);
// 需要修改整个value
/* Man4.value = {
name: '大满2'
} */
};
const Man5 = MyRef<string>("小满");
// 自己实现ref
function MyRef<T>(value: T) {
// customRef接收一个回调函数
// track收集依赖
// trigger触发依赖
return customRef((track, trigger) => {
// 要求返回一个包含get,set的对象
return {
get() {
track();
return value;
},
set(newVal) {
value = newVal;
trigger();
},
};
});
}
const change2 = () => {
Man5.value = "customRef修改啦";
};
const dom = ref<HTMLDivElement>();
const change3 = () => {
console.log(dom.value?.innerText);
};
</script>