在各大前端框架中,响应式系统无疑是核心。今天我们就来实现一个简易版的 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
下面我们就来实现 signal
和 effect
这两个函数。
signal
函数应该接受一个初始值,返回一个 Signal
对象,像这样:
function signal<T>(initial: T): Signal<T>
Signal
对象应该是一个函数,调用它将返回内部值。并且有两个方法 set
和 update
。除此之外,它的内部还应该保存一个值和副作用函数列表:
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。