Created
September 20, 2024 08:05
-
-
Save e7h4n/09ab3ebf89e2a8c432e67b54844273c3 to your computer and use it in GitHub Desktop.
mobx + abortSignal 的一点点例子
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 这个测试里我们用 mobx 来建立一个应用程序 | |
// 这个程序中,当 dialog 的 show 状态变为 true 时,我们开启一个定时器 | |
// 这个定时器每秒给一个全局自增 counter+1 | |
// 当 dialog 的状态变为 false 时,我们重置这个全局定时器 | |
// 在这个例子中,所有的清理工作都应该用 transaction / abortSignal 来完成,而不是调用方主动重置 | |
import { afterEach, beforeEach, expect, test, vi } from "vitest"; | |
import { makeAutoObservable, reaction } from "mobx" | |
import { interval } from "signal-timers"; | |
import { transaction } from "signal-transaction"; | |
beforeEach(() => { | |
vi.useFakeTimers() | |
}) | |
afterEach(() => { | |
vi.useRealTimers() | |
}) | |
test('简单的 setter + 复杂的响应式计算', async () => { | |
class Store { | |
dialog = { | |
show: false | |
} | |
counter = 0 | |
timer: ReturnType<typeof setInterval> | null = null | |
abortTimer?: () => void | |
constructor() { | |
makeAutoObservable(this) | |
reaction( | |
() => this.dialog.show, | |
(show) => { | |
if (show) { | |
const controller = new AbortController() | |
const signal = controller.signal; | |
this.timer = setInterval(() => { | |
if (!signal.aborted) { | |
this.counter++ | |
} | |
}, 1000) | |
signal.addEventListener('abort', () => { | |
if (this.timer) { | |
clearInterval(this.timer) | |
} | |
this.counter = 0 | |
}) | |
this.abortTimer = () => { controller.abort() } | |
} else { | |
this.abortTimer?.() | |
} | |
} | |
) | |
} | |
showDialog() { | |
this.dialog.show = true | |
} | |
hideDialog() { | |
this.dialog.show = false | |
} | |
} | |
const store = new Store() | |
store.showDialog() | |
await vi.advanceTimersByTimeAsync(3000) | |
expect(store.counter).toBe(3) | |
store.hideDialog() | |
expect(store.counter).toBe(0) | |
}) | |
test('复杂的 setter', async () => { | |
class Store { | |
dialog = { | |
show: false | |
} | |
counter = 0 | |
private dialogController?: AbortController | |
showDialog(signal: AbortSignal) { | |
this.dialogController?.abort() | |
this.dialogController = new AbortController() | |
const dialogSignal = AbortSignal.any([this.dialogController.signal, signal]) | |
const { act } = transaction(dialogSignal) | |
act(() => { | |
this.dialog.show = true | |
return () => { | |
this.dialog.show = false | |
this.counter = 0 | |
} | |
}) | |
interval(() => { | |
act(() => { | |
this.counter += 1 | |
}) | |
}, 1000, { signal: dialogSignal }) | |
} | |
hideDialog() { | |
this.dialogController?.abort() | |
} | |
} | |
const rootController = new AbortController() | |
const store = new Store() | |
// 重复 show 也不会有多的定时器 | |
store.showDialog(rootController.signal) | |
store.showDialog(rootController.signal) | |
store.showDialog(rootController.signal) | |
await vi.advanceTimersByTimeAsync(3000) | |
expect(store.counter).toBe(3) | |
store.showDialog(rootController.signal) | |
expect(store.counter).toBe(0) | |
await vi.advanceTimersByTimeAsync(3000) | |
expect(store.counter).toBe(3) | |
store.hideDialog() | |
expect(store.counter).toBe(0) | |
expect(store.dialog.show).toBeFalsy() | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment