This commit is contained in:
2025-09-22 13:23:12 +08:00
parent e3ff2045ea
commit 16b4b27352
14 changed files with 167 additions and 169 deletions

View File

@@ -81,8 +81,13 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
/** 全量订阅函数集合 */
private listeners: Set<TObservableListener<T>> = new Set()
/** 字段订阅函数集合 */
private keyListeners: Map<keyof T, Set<TObservableKeyListener<T, keyof T>>> = new Map()
/**
* 字段订阅函数集合
* 新结构:
* Map<TObservableKeyListener, Array<keyof T>>
* 记录每个回调订阅的字段数组,保证多字段订阅 always 返回所有订阅字段值
*/
private keyListeners: Map<TObservableKeyListener<T, keyof T>, Array<keyof T>> = new Map()
/** 待通知的字段集合 */
private pendingKeys: Set<keyof T> = new Set()
@@ -161,7 +166,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
/** 安排下一次通知(微任务合并) */
private scheduleNotify(): void {
if (!this.notifyScheduled && !this.disposed) {
if (!this.notifyScheduled && !this.disposed && this.pendingKeys.size > 0) {
this.notifyScheduled = true
Promise.resolve().then(() => this.flushNotify())
}
@@ -171,7 +176,6 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
private flushNotify(): void {
if (this.disposed) return
const keys = Array.from(this.pendingKeys)
this.pendingKeys.clear()
this.notifyScheduled = false
@@ -180,30 +184,13 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
try {
fn(this.state as unknown as T)
} catch (err) {
// 可以根据需要把错误上报或自定义处理
// 这里简单打印以便调试
// eslint-disable-next-line no-console
console.error("Observable listener error:", err)
}
}
// 字段订阅:把同一个回调的多个 key 聚合到一次调用里
const fnMap: Map<TObservableKeyListener<T, keyof T>, Array<keyof T>> = new Map()
for (const key of keys) {
const set = this.keyListeners.get(key)
if (!set) continue
for (const fn of set) {
const existing = fnMap.get(fn)
if (existing === undefined) {
fnMap.set(fn, [key])
} else {
existing.push(key)
}
}
}
// 调用每个字段订阅回调
fnMap.forEach((subKeys, fn) => {
// ================== 字段订阅 ==================
// 遍历所有回调,每个回调都返回它订阅的字段(即使只有部分字段变化)
this.keyListeners.forEach((subKeys, fn) => {
try {
// 构造 Pick<T, K> 风格的结果对象:结果类型为 Pick<T, (typeof subKeys)[number]>
const result = {} as Pick<T, (typeof subKeys)[number]>
@@ -214,7 +201,6 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
// 调用时类型上兼容 TObservableKeyListener<T, K>,因为我们传的是对应 key 的 Pick
fn(result as Pick<T, (typeof subKeys)[number]>)
} catch (err) {
// eslint-disable-next-line no-console
console.error("Observable keyListener error:", err)
}
})
@@ -227,7 +213,6 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
try {
fn(this.state as unknown as T)
} catch (err) {
// eslint-disable-next-line no-console
console.error("Observable subscribe immediate error:", err)
}
}
@@ -236,19 +221,18 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
}
}
/** 订阅指定字段变化 */
/** 订阅指定字段变化(多字段订阅 always 返回所有字段值) */
public subscribeKey<K extends keyof T>(
keys: K | K[],
fn: TObservableKeyListener<T, K>,
options: { immediate?: boolean } = {}
): () => void {
const keyArray = Array.isArray(keys) ? keys : [keys]
for (const key of keyArray) {
if (!this.keyListeners.has(key)) this.keyListeners.set(key, new Set())
// 存储为 Set<TObservableKeyListener<T, keyof T>>
this.keyListeners.get(key)!.add(fn as TObservableKeyListener<T, keyof T>)
}
// ================== 存储回调和它订阅的字段数组 ==================
this.keyListeners.set(fn as TObservableKeyListener<T, keyof T>, keyArray as (keyof T)[])
// ================== 立即调用 ==================
if (options.immediate) {
const result = {} as Pick<T, K>
keyArray.forEach(k => {
@@ -257,30 +241,26 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
try {
fn(result)
} catch (err) {
// eslint-disable-next-line no-console
console.error("Observable subscribeKey immediate error:", err)
}
}
// ================== 返回取消订阅函数 ==================
return () => {
for (const key of keyArray) {
this.keyListeners.get(key)?.delete(fn as TObservableKeyListener<T, keyof T>)
}
this.keyListeners.delete(fn as TObservableKeyListener<T, keyof T>)
}
}
/** 批量更新状态(避免重复 schedule */
public patch(values: Partial<T>): void {
let changed = false
// 用 for..in 保持和对象字面量兼容(跳过原型链)
for (const key in values) {
if (Object.prototype.hasOwnProperty.call(values, key)) {
const typedKey = key as keyof T
const oldValue = (this.state as Record<keyof T, unknown>)[typedKey]
const newValue = values[typedKey] as unknown
if (oldValue !== newValue) {
// 直接写入 state会被 Proxy 的 set 捕获并安排通知
;(this.state as Record<keyof T, unknown>)[typedKey] = newValue
(this.state as Record<keyof T, unknown>)[typedKey] = newValue
changed = true
}
}
@@ -322,3 +302,4 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
}
}