ref
ref 如何使用
在 vue3 中使用ref的时候非常多,为何ref可以实现响应式,今天来通过源码阅读来一探究竟
先看一下ref是如何使用的:
import { ref } from 'vue'
// 基本数据类型
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
// 复制数据类型
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
console.log(obj.value.nested.count) // 0
console.log(obj.value.arr) // ['foo', 'bar']
可以看到,ref不光可以定义简单数据类型,还可以定义复杂数据类型,访问的时候通过.value去获取
源码 位置: ref 。ref 与 shallowRef 实现一样,只是第二个参数不一样,一起看一下
本次查看的源码版本为 3.4.15
ref
// ref的导出函数,返回一个createRef
export function ref(value?: unknown) {
//第一个为传入的值, 第二个参数表示是否shallow,浅比较
return createRef(value, false)
}
shallowRef
export function shallowRef(value?: unknown) {
// 传入true表示为shallow
return createRef(value, true)
}
createRef
如果传入是一个ref值,则直接返回,否则创建一个RefImpl的实例
/**
*
* @param rawValue 原始值
* @param shallow 是否浅层
* @returns
*/
function createRef(rawValue: unknown, shallow: boolean) {
// 本身果然就是ref值,则直接返回
if (isRef(rawValue)) {
return rawValue
}
// 返回一个RefImpl实例
return new RefImpl(rawValue, shallow)
}
isRef
判断一个值是否是 ref 类型
export function isRef(r: any): r is Ref {
// __v_isRef 为true就说明是一个ref值
return !!(r && r.__v_isRef === true)
}
RefImpl
ref的核心实现,标记是一个ref类型, 值的获取&依赖收、 值的设置&依赖更新
class RefImpl<T> {
private _value: T // 当前值
private _rawValue: T // 原始值
public dep?: Dep = undefined // dep 依赖收集
public readonly __v_isRef = true // 标记为ref
constructor(
value: T,
public readonly __v_isShallow: boolean, // 是否浅层
) {
// 原始值。如果是浅层,则直接使用原始值,否则使用toRaw转换后的值
this._rawValue = __v_isShallow ? value : toRaw(value)
// 如果是浅层,则直接使用原始值,否则使用toReactive转换后的值
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, DirtyLevels.Dirty, newVal) // 触发依赖
}
}
}
接下来看看依赖收集的逻辑
trackRefValue
依赖收集函数
shouldTrack 与 activeEffect 存在时,将 ref.dep 放入 activeEffect.deps 中;调用 trackRefValue 时,ref.dep 不存在,所以会调用 createDep 创建一个 dep
/**
*
* @param ref ref值
*/
export function trackRefValue(ref: RefBase<any>) {
// shouldTrack 能否收集依赖,activeEffect 当前正在运行的effect
// shouldTrack 与 activeEffect 存在时,将 ref.dep 放入 activeEffect.deps 中
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
// 调用 trackRefValue 时,ref.dep 不存在,所以会调用 createDep 创建一个 dep
trackEffect(
activeEffect,
ref.dep ||
(ref.dep = createDep(
() => (ref.dep = undefined), // 清除函数
ref instanceof ComputedRefImpl ? ref : undefined, // 判断是否为computed
)),
__DEV__
? {
target: ref,
type: TrackOpTypes.GET,
key: 'value',
}
: void 0,
)
}
}
triggerRefValue
依赖派发函数
/**
*
* @param ref ref值
* @param dirtyLevel dirty 层级
3.4后 dep有改造,通过dirtyLevel 来做判断处理
export enum DirtyLevels {
NotDirty = 0, // 不是一个脏值
MaybeDirty = 1, // 可能是一个脏值
Dirty = 2, // 一般是computed的时候传入
}
* @param newVal 新值
*/
export function triggerRefValue(
ref: RefBase<any>,
dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
newVal?: any,
) {
ref = toRaw(ref)
const dep = ref.dep
if (dep) {
// 触发依赖
triggerEffects(
dep,
dirtyLevel,
__DEV__
? {
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
newValue: newVal,
}
: void 0,
)
}
}
一些辅助的定义
DirtyLevels 枚举定义
export enum DirtyLevels {
NotDirty = 0, // 不是一个脏值
MaybeDirty = 1, // 可能是一个脏值
Dirty = 2, // 一般是computed的时候传入
}
// 转为原始值
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
// Dep是一个Map类型,key是ReactiveEffect,value是number
export type Dep = Map<ReactiveEffect, number> & {
cleanup: () => void
computed?: ComputedRefImpl<any>
}
// 创建一个Dep
export const createDep = (
cleanup: () => void,
computed?: ComputedRefImpl<any>,
): Dep => {
const dep = new Map() as Dep // 创建一个Map
dep.cleanup = cleanup // cleanup是一个函数,用于清除依赖
dep.computed = computed // computed是一个计算属性
return dep
}
接下来看看依赖收集与依赖更新的代码
effect
代码位于 effect 中
trackEffect
依赖收集,把activeEffect添加到dep中
/**
*
* @param effect 当前激活的副作用函数
* @param dep 依赖列表
* @param debuggerEventExtraInfo
*/
export function trackEffect(
effect: ReactiveEffect,
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
// 判断dep中的efft是否已经被追踪过了, 未追踪过的话, 就将effect添加到dep中
if (dep.get(effect) !== effect._trackId) {
dep.set(effect, effect._trackId) // 添加effect到dep中
const oldDep = effect.deps[effect._depsLength] // 获取effect上一次追踪的dep
// 如果当前的dep和上一次的dep不一样, 就将effect从上一次的dep中移除, 并将effect添加到当前的dep中
if (oldDep !== dep) {
if (oldDep) {
// 清除掉老的dep中的的effect
cleanupDepEffect(oldDep, effect)
}
effect.deps[effect._depsLength++] = dep
} else {
// 更新_depsLength的长度
effect._depsLength++
}
if (__DEV__) {
effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!))
}
}
}
triggerEffects
遍历 dep 中收集到的 effect,
/**
*
* @param dep dep依赖收集器
* @param dirtyLevel 脏等级
* @param debuggerEventExtraInfo
*/
export function triggerEffects(
dep: Dep,
dirtyLevel: DirtyLevels,
debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
pauseScheduling() // pauseScheduleStack 自增1
// 遍历收集到的effect
for (const effect of dep.keys()) {
// 当effect的脏等级小于当前的脏等级, 并且effect的trackId等于dep的trackId
if (
effect._dirtyLevel < dirtyLevel &&
dep.get(effect) === effect._trackId
) {
// 获取effect的_dirtyLevel
const lastDirtyLevel = effect._dirtyLevel
// 设置effect的_dirtyLevel为传入的dirtyLevel
effect._dirtyLevel = dirtyLevel
// 如果effect的_dirtyLevel等于DirtyLevels.NotDirty(0)
if (lastDirtyLevel === DirtyLevels.NotDirty) {
// 设置_shouldSchedule为true
effect._shouldSchedule = true
if (__DEV__) {
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
}
// 触发effect 上的 trigger方法
effect.trigger()
}
}
}
// 遍历dep收集的effect,把effect的scheduler方法添加到queueEffectSchedulers中
scheduleEffects(dep)
resetScheduling() // pauseScheduleStack 自减1,queueEffectSchedulers 清空
}
scheduleEffects
queueEffectSchedulers添加effect中的scheduler
export function scheduleEffects(dep: Dep) {
for (const effect of dep.keys()) {
if (
effect.scheduler &&
effect._shouldSchedule &&
(!effect._runnings || effect.allowRecurse) &&
dep.get(effect) === effect._trackId
) {
// 设置effect的_shouldSchedule为false
effect._shouldSchedule = false
// 把effect的scheduler方法添加到queueEffectSchedulers中
queueEffectSchedulers.push(effect.scheduler)
}
}
}
在ref收集依赖的时候判断 activeEffect与shouldTrack是否存在,那这个activeEffect和shouldTrack是什么时候才有的呢?
在界面初始化渲染的时候 调用了ReactiveEffect,执行effect.run 赋值的、
代码位置 setupRenderEffect
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
namespace: ElementNamespace,
optimized,
) => {
// 调度函数
const componentUpdateFn = () => {
// 省略部分代码
}
// 创建一个副作用收集函数
// create reactive effect for rendering
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
NOOP,
() => queueJob(update),
instance.scope, // track it in component's effect scope
))
// 更新方法
const update: SchedulerJob = (instance.update = () => {
if (effect.dirty) {
effect.run()
}
})
update.id = instance.uid
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
toggleRecurse(instance, true)
// 调用
update()
}
ReactiveEffect
设置执行函数,调度器等,运行run方法时候把shouldTrack与activeEffect设置为当前的 this,执行完后回退到上一个执行依赖的地方,保证每一次都是最新的收集
export class ReactiveEffect<T = any> {
active = true // 是否激活
deps: Dep[] = [] // 依赖项
/**
* Can be attached after creation
* @internal
*/
computed?: ComputedRefImpl<T> // 计算属性
/**
* @internal
*/
allowRecurse?: boolean
onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void
/**
* @internal
*/
_dirtyLevel = DirtyLevels.Dirty // 脏等级
/**
* @internal
*/
_trackId = 0 // 跟踪id
/**
* @internal
*/
_runnings = 0 // 运行次数
/**
* @internal
*/
_shouldSchedule = false // 是否应该调度
/**
* @internal
*/
_depsLength = 0 // 依赖项长度
constructor(
public fn: () => T, // 函数
public trigger: () => void, // 触发器
public scheduler?: EffectScheduler, // 调度器,如传入则执行该调度器
scope?: EffectScope,
) {
recordEffectScope(this, scope)
}
// 获取effect的dirty值
public get dirty() {
if (this._dirtyLevel === DirtyLevels.MaybeDirty) {
pauseTracking()
for (let i = 0; i < this._depsLength; i++) {
const dep = this.deps[i]
if (dep.computed) {
triggerComputed(dep.computed)
if (this._dirtyLevel >= DirtyLevels.Dirty) {
break
}
}
}
if (this._dirtyLevel < DirtyLevels.Dirty) {
this._dirtyLevel = DirtyLevels.NotDirty
}
resetTracking()
}
return this._dirtyLevel >= DirtyLevels.Dirty
}
// 设置effect的dirty值
public set dirty(v) {
this._dirtyLevel = v ? DirtyLevels.Dirty : DirtyLevels.NotDirty
}
run() {
this._dirtyLevel = DirtyLevels.NotDirty
// 不是激活状态直接调用传入的方法
if (!this.active) {
return this.fn()
}
let lastShouldTrack = shouldTrack
let lastEffect = activeEffect
try {
shouldTrack = true // 设置为true
activeEffect = this // 设置当前激活的effect
this._runnings++
preCleanupEffect(this)
return this.fn() // 执行传入的方法
} finally {
postCleanupEffect(this)
this._runnings--
activeEffect = lastEffect // 恢复上一个激活的effect
shouldTrack = lastShouldTrack // 恢复上一个shouldTrack
}
}
// 停止收集
stop() {
if (this.active) {
preCleanupEffect(this)
postCleanupEffect(this)
this.onStop?.()
this.active = false
}
}
}