Last active
June 4, 2025 12:46
-
-
Save Misaka-0x447f/0c37018ae7bd944cbff54d27b6d4fd9f to your computer and use it in GitHub Desktop.
(此 gist 已进化为 npm 包,请前往仓库查看:https://github.com/Misaka-0x447f/createTypedEvent )
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
/** | |
* A modern eventManager, but typed to prevent errors. | |
* 更现代的事件管理器,同时具备内建 payload 类型机制以减少错误和帮助自动填充。 | |
* @example | |
* type Payload = {ready: boolean} | |
* const networkStateChange = createTypedEvent<Payload>() | |
* const handler = (payload: Payload) => console.log(payload) | |
* networkStateChange.sub(handler) | |
* networkStateChange.dispatch({ready: true}) | |
* networkStateChange.unsub(handler) | |
* @example | |
* const misakaStateChange = createTypedEvent<{selfDestructionInProgress: boolean}>() | |
* const unsub = createTypedEvent.sub(console.log) // returns unsub function without define handler outside. | |
* unsub() | |
* @example | |
* export const eventBus = { | |
* alice: createTypedEvent(), | |
* bob: createTypedEvent<{isE2eEncryption: boolean}>() | |
* } | |
* eventBus.bob.dispatch({isE2eEncryption: true}) | |
* | |
* @member sub Subscribe to event. Returns an unsub method that does not require original callback. | |
* @member unsub Unsubscribe to event. Require original callback. | |
* @member dispatch Simply dispatch payload to every subscriber. | |
* @member once Only subscribe once. | |
* @member value Get the latest value. | |
* | |
* @param dispatchLastValueOnSubscribe If true, dispatch last value to new subscriber. | |
* | |
* @member sub 订阅事件。返回一个取消订阅方法,以便在不需要原始回调函数的情况下取消订阅。 | |
* @member unsub 取消订阅事件。需要原始回调。 | |
* @member dispatch 仅将 payload 分发给每个订阅者。 | |
* @member once 仅订阅一次。 | |
* @member value 获取最新的值。 | |
*/ | |
import { useMemo } from 'react'; | |
import { useBeforeMount } from '@/utils/hooks'; | |
import { useHybridState } from '@/utils/useHybridState'; | |
import { logRuntimeError } from '@/utils/telemetry'; | |
type cb<T> = (payload: T) => void; | |
export const createTypedEvent = <T = void>({ | |
dispatchLastValueOnSubscribe = false, | |
initialValue, | |
}: { | |
dispatchLastValueOnSubscribe?: boolean; | |
initialValue?: T; | |
} = {}) => { | |
const history: T[] = initialValue ? [initialValue] : []; | |
const cbs: Array<cb<T>> = []; | |
const instance = { | |
sub: (cb: cb<T>) => { | |
cbs.push(cb); | |
if (dispatchLastValueOnSubscribe && history.length > 0) cb(history[0]); | |
return () => instance.unsub(cb); | |
}, | |
unsub: (cb: cb<T>) => { | |
const index = cbs.indexOf(cb); | |
if (index === -1) return; | |
cbs.splice(index, 1); | |
}, | |
dispatch: (payload: T) => { | |
cbs.map(v => v(payload)); | |
history[0] = payload; | |
}, | |
once: (cb: cb<T>) => { | |
instance.sub((arg: T) => { | |
cb(arg); | |
instance.unsub(cb); | |
}); | |
return () => instance.unsub(cb); | |
}, | |
get value() { | |
return history[0]; | |
}, | |
set value(_) { | |
logRuntimeError('createTypedEvent.value is read-only.'); | |
}, | |
}; | |
return instance; | |
}; | |
export const createTypedEventMemorized: typeof createTypedEvent = (...args) => | |
useMemo(() => createTypedEvent(...args), []); | |
/** | |
* @description | |
* 获取 TypedEvent 事件管线中的最新 value。例如以下用法可以获得最新的市场价格: | |
* @example | |
* const [marketPrice] = useTypedEventValue(marketPriceUpdateEvent); | |
*/ | |
export const useTypedEventValue = <T = void>(event: TypedEvent<T>) => { | |
const [value, setValue, valueRef] = useHybridState<T>(event.value); | |
useBeforeMount(() => { | |
return event.sub(setValue); | |
}); | |
return [value, event.dispatch, valueRef] as const; | |
}; | |
export type TypedEvent<T = void> = ReturnType<typeof createTypedEvent<T>>; |
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
/** | |
* An eventManager, but typed to prevent errors. | |
* @example | |
* type Payload = {ready: boolean} | |
* const networkStateChange = new TypedEvent<Payload>() | |
* const handler = (payload: Payload) => console.log(payload) | |
* networkStateChange.sub(handler) | |
* networkStateChange.dispatch({ready: true}) | |
* networkStateChange.unsub(handler) | |
* @example | |
* const misakaStateChange = new TypedEvent<{selfDestructionInProgress: boolean}>() | |
* const unsub = misakaStateChange.sub(console.log) // returns unsub function without define handler outside. | |
* unsub() | |
* @example | |
* export const eventBus = { | |
* alice: new TypedEvent(), | |
* bob: new TypedEvent<{isE2eEncryption: boolean}>() | |
* } | |
* eventBus.bob.dispatch({isE2eEncryption: true}) | |
* | |
* @class TypedEvent | |
* @member sub Subscribe to event. Returns an unsub method that does not require original callback. | |
* @member unsub Unsubscribe to event. Require original callback. | |
* @member dispatch Simply dispatch payload to every subscriber. | |
* @member once Only subscribe once. | |
*/ | |
type cb<T> = (payload: T) => void; | |
export class TypedEvent<T = void> { | |
constructor(dispatchLastValueOnSubscribe?: boolean) { | |
this.enableHistory = !!dispatchLastValueOnSubscribe; | |
} | |
private readonly enableHistory: boolean = false; | |
private history: T[] = []; | |
private cbs: Array<cb<T>> = []; | |
public sub(cb: cb<T>) { | |
this.cbs.push(cb); | |
if (this.enableHistory && this.history.length > 0) cb(this.history[0]); | |
return () => this.unsub(cb); | |
} | |
public unsub(cb: cb<T>) { | |
const index = this.cbs.indexOf(cb); | |
if (index === -1) return; | |
this.cbs.splice(index, 1); | |
} | |
public dispatch(payload: T) { | |
this.cbs.map(v => v(payload)); | |
if (this.enableHistory) this.history = [payload]; | |
} | |
public once(cb: cb<T>) { | |
this.sub((arg: T) => { | |
cb(arg); | |
this.unsub(cb); | |
}); | |
} | |
public get lastValue() { | |
return history[0]; | |
}, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment