音频裁剪大师最新版
142.21MB · 2025-09-18
我们的代码虽然已经可以运作,但 RefImpl
这个类同时处理了数据存储和链表管理,不易于扩展,所以需要调整代码结构。尽管前几章的代码已经能正常工作,但它存在一个很大的问题:RefImpl
这个类承担了太多的责任。
它既要负责存储数值 (_value
),又要管理一整套复杂的链表操作。
这种设计违反了软件工程中的 “单一职责原则 (Single Responsibility Principle)” ,会导致代码难以阅读、维护和扩展。
首先,我们把 RefImpl
中的链表操作抽离出来,创建两个独立的函数:
trackRef
:收集依赖triggerRef
:触发更新class RefImpl {
_value;
[ReactiveFlags.IS_REF] = true
subs: Link
subsTail: Link
constructor(value){
this._value = value
}
get value(){
if(activeSub){
trackRef(this)
}
return this._value
}
set value(newValue){
this._value = newValue
triggerRef(this)
}
}
/*
* 这里的 dep 是 ref
* 收集依赖,建立 ref 和 effect 之间的链表关系
*/
export function trackRef(dep){
const newLink = {
sub: activeSub,
nextSub: undefined,
prevSub: undefined
}
if(dep.subsTail){
dep.subsTail.nextSub = newLink
newLink.prevSub = dep.subsTail
dep.subsTail = newLink
} else {
dep.subs = newLink
dep.subsTail = newLink
}
}
/*
* 触发 ref 关联的 effect,使其重新执行
*/
export function triggerRef(dep){
let link = dep.subs
let queuedEffect = []
while (link){
queuedEffect.push(link.sub)
link = link.nextSub
}
queuedEffect.forEach(effect => effect())
}
接着新建一个 system.ts
文件,用于存放链表相关逻辑,进行再次拆分:
trackRef
:作为收集依赖的入口函数,判断是否存在 activeSub
,如果存在,则建立链表关系。
effect(fn)
在调用 fn()
前会把自己设为 activeSub
,在 fn()
结束后清空。所以我们使用 activeSub
来判断它是否为当前正在执行的 effect(fn)
。triggerRef
:作为触发更新的入口函数,需要找到并通知曾经订阅过这个 dep
的所有 effect
。因此我们判断,如果 dep
存在 subs
,它就触发更新。
dep
(dependency) = 被依赖的对象(如 ref
、reactive
)
sub
(subscriber) = 订阅者(如 effect
、watch
)
// system.ts
import { ReactiveEffect } from './effect'
export interface Link {
sub: ReactiveEffect
nextSub: Link
prevSub: Link
}
/* * 建立链表关系
* dep 是依赖项,例如 ref/computed/reactive
* sub 是订阅者,例如 effect
* 当依赖项(ref)变化时,需要通知订阅者(effect)
*/
export function link(dep, sub){
// 建立新的链表节点
const newLink: Link = {
sub, // 指向目前的订阅者 (activeSub)
nextSub: undefined, // 指向下一个节点 (初始化为空)
prevSub: undefined // 指向前一个节点 (初始化为空)
}
// 如果 dep 已经有尾端订阅者 (代表链表不是空的)
if(dep.subsTail){
// 把尾端节点的 next 指向新的节点
dep.subsTail.nextSub = newLink
// 新节点的 prev 指向原本的尾端
newLink.prevSub = dep.subsTail
// 更新 dep 的尾端指标为新节点
dep.subsTail = newLink
} else {
// 如果 dep 还没有任何订阅者 (第一次建立链表)
dep.subs = newLink // 链表的头指向新节点
dep.subsTail = newLink // 链表的尾也指向新节点
}
}
/* * 传播更新的函数
*/
export function propagate(subs){
let link = subs
let queuedEffect = []
while (link){
queuedEffect.push(link.sub)
link = link.nextSub
}
queuedEffect.forEach(effect => effect.run())
}
// ref.ts
import { activeSub, ReactiveEffect } from './effect'
import { Link, link, propagate } from './system'
enum ReactiveFlags {
IS_REF = '__v_isRef'
}
class RefImpl {
_value;
[ReactiveFlags.IS_REF] = true
subs: Link
subsTail: Link
constructor(value){
this._value = value
}
get value(){
if(activeSub){
trackRef(this)
}
return this._value
}
set value(newValue){
this._value = newValue
triggerRef(this)
}
}
export function ref(value){
return new RefImpl(value)
}
export function isRef(value){
return !!(value && value[ReactiveFlags.IS_REF])
}
/*
* 这里的 dep 是 ref
* 收集依赖,建立 ref 和 effect 之间的链表关系
*/
export function trackRef(dep){
if(activeSub){
link(dep, activeSub)
}
}
/*
* 触发 ref 关联的 effect,使其重新执行
*/
export function triggerRef(dep){
if(dep.subs){
propagate(dep.subs)
}
}
// effect.ts
// 用于保存当前正在执行的 effect
export let activeSub;
export function effect(fn){
activeSub = fn
activeSub()
activeSub = undefined
}
我们将 effect
函数重构为一个类,并给它一个 run
方法:
// effect.ts
import { Link } from './system'
export let activeSub: ReactiveEffect;
export class ReactiveEffect {
constructor(public fn: Function) {}
run(){
// 每次执行 fn 之前,把 this 实例放到 activeSub 上
activeSub = this
try {
return this.fn()
} finally {
// 执行完毕后,清空 activeSub
activeSub = undefined
}
}
}
export function effect(fn){
const e = new ReactiveEffect(fn)
e.run()
}
effect
重构为 ReactiveEffect
类?主要有三大好处:
effect
本身其实是有状态的(例如它依赖了谁、是否正在执行等)。类是封装这些状态和相关行为的最好办法。effect
成为一个类后,当有需要时,我们可以轻松地为它新增更多方法,比如 run()
就是一个很好的例子。this
指向:在 run()
方法中,activeSub
被赋值为 this
(也就是 ReactiveEffect
的实例),方便后续我们从 effect
实例上获取更多需要的信息。也正因如此,effect
从函数变成了对象,所以我们要调整一下调用方式。
// system.ts
import { ReactiveEffect } from './effect'
export interface Link {
// 由于调整,effect 是对象
sub: ReactiveEffect
nextSub: Link
prevSub: Link
}
//...
//...
export function propagate(subs){
//...
// effect 变成了对象,改调用其 run 方法
queuedEffect.forEach(effect => effect.run())
}
回顾我们今天完成的事:我们将 RefImpl
中复杂的依赖追踪逻辑,拆分到了独立的 system.ts
模块,并且把 effect
重构成一个更易于维护的 ReactiveEffect
类。
现在,我们的响应式核心由三部分组成:RefImpl
(负责数据内容)、ReactiveEffect
(负责副作用)、以及 system.ts
(连接它们的桥梁)。
明天我们就可以开始处理 effect
相关的其他问题了。
想了解更多 Vue 的相关知识,抖音、B站搜索我师父「远方os」,一起跟日安当同学。