假设我有这样的“类型”:
{
a: {
b: {
c: {
d: string
e: boolean
}
},
x: string
y: number
z: string
}
}
在每个对象节点上,如果所有的子节点都被“解析”为一个值,我希望得到通知。例如:
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')
})
每个属性节点可以有多个“观察者”,如下所示:
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
你怎么能把这个布置得这么整齐呢?我一直在尝试很长一段时间,以包裹我的头围绕它,但没有走远(如看到在这个TSPlayground。
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
和相关方法来接受它们的“匹配器”和回调,并在匹配器的所有属性都满足时正确调用回调?
它变得棘手,因为你可以在两个方向上工作:
1.在添加监视器/侦听器之前,该值可能已经在过去 * 被解析。在这种情况下,仍然应该立即通知它。
1.在添加了监视器之后,可以在将来 * 解析该值。只有在完成后才应通知。
请注意,“matcher”语法有点像GraphQL查询,在这里,您只需构建一个对象树,叶子设置为true
。
1条答案
按热度按时间7kqas0il1#
一些初步想法:
a.watch3('b', {c: {d: true}}, cb)
的第一个参数是一个必须与其中一个属性匹配的名称,Matcher对象应该“Map”到该属性的值。但是我建议将Matcher对象与 current 对象(this
)Map,并将b
放入Matcher中,这样你就可以省略第一个参数:watch
方法,其中回调总是第一个参数,Matcher对象是可选的第二个参数。在我看来,名称论证没有必要(上一点)。这个假设意味着回调一旦被调用就可以/必须被 * 未注册 *。
true
值,而实际的对象结构有一个更深层次的嵌套对象结构,而不是一个文字,那么true
将被解释为“所有低于此点”必须有接收值。下面是如何编码的:
在TS Playground上用一些测试功能看看吧