2

在各大前端框架中,响应式系统无疑是核心。今天我们就来实现一个简易版的 Angular Signals。

先看结果:

// 创建 Signal
const s1 = signal(2),
    s2 = signal('s2')

// 副作用
const clear = effect(() => {
    console.log('signal is:', s1(), s2())
})
// 第一次运行,输出 signal is: 2 s2

// 更新值
s1.set(0) // signal is: 0 s2
s1.update(p => p + 1) // signal is: 1 s2
s2.set('new s2') // signal is: 1 new s2

clear()

s1.set(1024)
s2.set('s2 after clear')
console.log(s1(), s2()) // 1024 s2 after clear

下面我们就来实现 signaleffect 这两个函数。

signal 函数应该接受一个初始值,返回一个 Signal 对象,像这样:

function signal<T>(initial: T): Signal<T>

Signal 对象应该是一个函数,调用它将返回内部值。并且有两个方法 setupdate。除此之外,它的内部还应该保存一个值和副作用函数列表:

interface Signal<T> {
    (): T
    set(value: T): T
    update(updater: (prev: T) => T): T
    _value: T
    _effect: Set<() => void>
}

现在让我们来实现 signal 函数:

function signal<T>(initial: T): Signal<T> {
    const s = (() => s._value) as Signal<T>
    s._value = initial
    s._effect = new Set()
    s.set = v => (s._value = v)
    s.update = updater => s.set(updater(s()))

    return s
}

看上去很简单,这是因为我们还没有追踪响应式依赖。为了追踪依赖,我们需要一个全局变量保存副作用函数和所用到的信号:

let globalState:
    | {
            effect: () => void    // 当前的副作用函数
            signals: Set<Signal<unknown>>    // 当前副作用函数所依赖的信号
      }
    | undefined

我们可以先来实现 effect 函数:

function effect(func: () => void) {
    globalState = {
        effect: func,
        signals: new Set()
    }
    const signals = globalState.signals
    func()
    globalState = undefined
    return () => signals.forEach(s => s._effect.delete(func))
}

现在你知道我们应该怎样做来实现状态跟踪吗?答案是:在读取值的时候将当前副作用函数添加到信号的副作用函数列表中,在设置值的时候调用信号的副作用函数:

const s = (() => {
    if (globalState) {
        globalState.signals.add(s)
        s._effect.add(globalState.effect)
    }
    return s._value
}) as Signal<T>

s.set = v => {
    s._value = v
    s._effect.forEach(cb => cb())
    return v
}

如此,我们就可以自动追踪副作用的依赖,并在信号更新时自动调用副作用函数了。

全部代码如下:

interface Signal<T> {
    (): T
    set(value: T): T
    update(updater: (prev: T) => T): T
    _value: T
    _effect: Set<() => void>
}

let globalState:
    | {
            effect: () => void
            signals: Set<Signal<unknown>>
      }
    | undefined

function signal<T>(initial: T): Signal<T> {
    const s = (() => {
        if (globalState) {
            globalState.signals.add(s)
            s._effect.add(globalState.effect)
        }
        return s._value
    }) as Signal<T>
    s._value = initial
    s._effect = new Set()
    s.set = v => {
        s._value = v
        s._effect.forEach(cb => cb())
        return v
    }
    s.update = updater => s.set(updater(s()))

    return s
}

function effect(func: () => void) {
    globalState = {
        effect: func,
        signals: new Set()
    }
    const signals = globalState.signals
    func()
    globalState = undefined
    return () => signals.forEach(s => s._effect.delete(func))
}

// 测试

const s1 = signal(2),
    s2 = signal('s2'),
    clear = effect(() => {
        console.log('signal is:', s1(), s2())
    })

s1.set(0)
s1.update(p => p + 1)
s2.set('new s2')

clear()

s1.set(1024)
s2.set('s2 after clear')
console.log(s1(), s2())

运行结果:

signal is: 2 s2
signal is: 0 s2
signal is: 1 s2
signal is: 1 new s2
1024 s2 after clear

42
3.8k 声望3.8k 粉丝