假设我有这样一个"类型":
{
a: {
b: {
c: {
d: string
e: boolean
}
},
x: string
y: number
z: string
}
}
在每个对象 node ,如果所有子对象都被"解析"为一个值,我希望得到通知.因此,例如:
const a = new TreeObject()
a.watch((a) => console.log('a resolved', a))
const b = a.createObject('b')
b.watch((b) => console.log('b resolved', b))
const c = b.createObject('c')
c.watch((c) => console.log('c resolved', c))
const d = c.createLiteral('d')
d.watch((d) => console.log('d resolved', d))
const e = c.createLiteral('e')
e.watch((e) => console.log('e resolved', e))
const x = a.createLiteral('x')
x.watch((x) => console.log('x resolved', x))
const y = a.createLiteral('y')
y.watch((y) => console.log('y resolved', y))
d.set('foo')
// logs:
// d resolved
e.set('bar')
// logs:
// e resolved
// c resolved
// b resolved
y.set('hello')
// logs:
// y resolved
x.set('world')
// logs:
// x resolved
// a resolved
这是最基本的情况.更复杂的情况,也就是我一直试图解决的问题,是匹配属性的子集,如下所示:
// receive 'b' only if b.c.d is resolved.
// '3' for 3 args
a.watch3('b', {
c: {
d: true
}
}, () => {
console.log('b with b.c.d resolved')
})
每个属性 node 可以有多个"观察者",如下所示:
a.watch3('b', { c: { d: true } }, () => {
console.log('get b with b.c.d resolved')
})
a.watch3('b', { c: { e: true } }, () => {
console.log('get b with b.c.e resolved')
})
a.watch2('x', () => {
console.log('get x when resolved')
})
// now if were were to start from scratch setting properties fresh:
x.set('foo')
// logs:
// get x when resolved
e.set('bar')
// logs:
// get b with b.c.e resolved
你怎么才能巧妙地安排好呢?我已经try 了很长时间,但没有走远(就像这TS playground个人所看到的那样).
type Matcher = {
[key: string]: true | Matcher
}
type Callback = () => void
class TreeObject {
properties: Record<string, unknown>
callbacks: Record<string, Array<{ matcher?: Matcher, callback: Callback }>>
parent?: TreeObject
resolved: Array<Callback>
constructor(parent?: TreeObject) {
this.properties = {}
this.callbacks = {}
this.parent = parent
this.resolved = []
}
createObject(name: string) {
const tree = new TreeObject(this)
this.properties[name] = tree
return tree
}
createLiteral(name: string) {
const tree = new TreeLiteral(this, () => {
// somehow start keeping track of decrementing what we have matched so far
// and when it is fully decremented, trigger the callback up the chain.
})
this.properties[name] = tree
return tree
}
watch3(name: string, matcher: Matcher, callback: Callback) {
const list = this.callbacks[name] ??= []
list.push({ matcher, callback })
}
watch2(name: string, callback: Callback) {
const list = this.callbacks[name] ??= []
list.push({ callback })
}
watch(callback: Callback) {
this.resolved.push(callback)
}
}
class TreeLiteral {
value: any
parent: TreeObject
callback: () => void
resolved: Array<Callback>
constructor(parent: TreeObject, callback: () => void) {
this.value = undefined
this.parent = parent
this.callback = callback
this.resolved = []
}
set(value: any) {
this.value = value
this.resolved.forEach(resolve => resolve())
this.callback()
}
watch(callback: Callback) {
this.resolved.push(callback)
}
}
const a = new TreeObject()
a.watch(() => console.log('a resolved'))
const b = a.createObject('b')
b.watch(() => console.log('b resolved'))
const c = b.createObject('c')
c.watch(() => console.log('c resolved'))
const d = c.createLiteral('d')
d.watch(() => console.log('d resolved'))
const e = c.createLiteral('e')
e.watch(() => console.log('e resolved'))
const x = a.createLiteral('x')
x.watch(() => console.log('x resolved'))
const y = a.createLiteral('y')
y.watch(() => console.log('y resolved'))
d.set('foo')
// logs:
// d resolved
e.set('bar')
// logs:
// e resolved
// c resolved
// b resolved
y.set('hello')
// logs:
// y resolved
x.set('world')
// logs:
// x resolved
// a resolved
如何定义watch3
和相关方法来接受它们的"匹配器"和回调,并在匹配器的所有属性都满足时正确地调用回调?
它变得很棘手,因为您可以在两个方向上工作:
- 在您添加观察者/监听器之前,该值可能已经解析为in the past.在这种情况下,仍然应该立即通知它.
- 在添加观察者之后,该值可以解析为in the future.只有在完成后才应通知它.
注意,"Matcher"语法有点像GraphQL查询,您只需构建一棵对象树,将所需对象的叶子设置为true
即可.