通过@observable 观察不可变对象
Observing immutable object via @observable
假设我有一个全局状态 class,其中包含一个时间帧 class,它具有一些有用的属性
export class StateContainer {
public timeframe: TimeFrame;
public setTimeframe(startTime: Date, endTime: Date) {
// treat this.timeframe as immutable
this.timeframe = { startTime: startTime, endTime: endTime };
}
}
export class TimeFrame {
public readonly startTime: Date;
public readonly endTime: Date;
}
然后,我需要在其他地方使用这个状态,所以我通过 DI 这样做,然后使用 bindingEngine.propertyObserver
来获取 timeframe
对象上的更改。
但是,如果可能的话,我喜欢能够做类似于以下的事情:
@autoinject
export class Display {
@observable
timeFrame: TimeFrame = this.state.timeframe;
constructor(private state: StateContainer) {
}
timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) {
...
// everytime this.state.timeFrame is changed via setTimeFrame()
// this change handler should fire, and so should any
// bindings from this component for this.timeFrame
}
}
但是,当我执行前面的操作时,我只会在初始创建时收到 timeFrameChanged(...)
通知,而不会在我调用 setTimeFrame()
时收到通知。我是做错了什么还是这不可能?
是的,您做错了什么,我认为您误解了 observable
装饰器和相应的 timeFrameChanged
方法的用途。
让我们一点一点的来吧。看看这些行:
@observable
timeFrame: TimeFrame = this.state.timeframe;
timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) { ... }
observable
装饰器告诉Aurelia,每当它所应用的属性发生变化时,执行相应的方法。除非另有配置,否则对应的方法是 nameOfInvolvedProperty + "Changed"
(在这种情况下,正如您所做的那样,timeFrameChanged
)。
但是,您实际上并没有改变 属性 的值!如果你真的改变了 属性,它会起作用:
<button click.delegate="changeProp()">
Change prop
</button>
和虚拟机:
changeProp() {
this.timeFrame = null;
this.timeFrame = this.state.timeframe;
}
现在您会看到处理程序正确触发。
但在您的代码中,此 属性 仅分配一次,此处:
timeFrame: TimeFrame = this.state.timeframe;
记住,这只是取消引用。换句话说,此代码告诉应用程序获取 this.state.timeframe
的值,将它的 瞬时 值存储在该变量中并忘记 this.state.timeframe
。所以它不会在 this.state.timeframe
更新时更新。
如documentation中所述,您可以在一定程度上配置observable
装饰器,但是,据我所知,没有办法以这样一种方式配置它,将其设置为观察嵌套属性(或者可能只是我不知道该怎么做)。
即使可能,我认为处理此类情况的更简洁的方法是使用 event aggregator。这使您能够采用订阅机制——每当您感兴趣的值发生变化时,您都会发布一个事件,并且任何对该变化感兴趣的组件都可以订阅它并相应地更新自身。
一个简单的实现:
import { Router, RouterConfiguration } from 'aurelia-router';
import { autoinject } from 'aurelia-framework';
import { observable } from 'aurelia-binding';
import { StateContainer } from './state-container';
import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
@autoinject
export class App {
subscription: Subscription = null;
constructor(private state: StateContainer, private eventAggregator: EventAggregator) {
}
activate() {
this.subscription = this.eventAggregator.subscribe('state:timeframe', (e) => {
console.log('Timeframe changed!', e);
});
}
deactivate() {
// Make sure to dispose of the subscription to avoid memory leaks.
this.subscription.dispose();
}
change() {
this.state.setTimeframe(new Date(), new Date());
}
}
和StateContainer
:
import { TimeFrame } from "./time-frame";
import { autoinject } from "aurelia-framework";
import { EventAggregator } from "aurelia-event-aggregator";
@autoinject
export class StateContainer {
public timeframe: TimeFrame = null;
constructor(private eventAggregator: EventAggregator) {
}
public setTimeframe(startTime: Date, endTime: Date) {
// treat this.timeframe as immutable
this.timeframe = { startTime: startTime, endTime: endTime };
this.eventAggregator.publish('state:timeframe', this.timeframe);
}
}
希望对您有所帮助。
假设我有一个全局状态 class,其中包含一个时间帧 class,它具有一些有用的属性
export class StateContainer {
public timeframe: TimeFrame;
public setTimeframe(startTime: Date, endTime: Date) {
// treat this.timeframe as immutable
this.timeframe = { startTime: startTime, endTime: endTime };
}
}
export class TimeFrame {
public readonly startTime: Date;
public readonly endTime: Date;
}
然后,我需要在其他地方使用这个状态,所以我通过 DI 这样做,然后使用 bindingEngine.propertyObserver
来获取 timeframe
对象上的更改。
但是,如果可能的话,我喜欢能够做类似于以下的事情:
@autoinject
export class Display {
@observable
timeFrame: TimeFrame = this.state.timeframe;
constructor(private state: StateContainer) {
}
timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) {
...
// everytime this.state.timeFrame is changed via setTimeFrame()
// this change handler should fire, and so should any
// bindings from this component for this.timeFrame
}
}
但是,当我执行前面的操作时,我只会在初始创建时收到 timeFrameChanged(...)
通知,而不会在我调用 setTimeFrame()
时收到通知。我是做错了什么还是这不可能?
是的,您做错了什么,我认为您误解了 observable
装饰器和相应的 timeFrameChanged
方法的用途。
让我们一点一点的来吧。看看这些行:
@observable
timeFrame: TimeFrame = this.state.timeframe;
timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) { ... }
observable
装饰器告诉Aurelia,每当它所应用的属性发生变化时,执行相应的方法。除非另有配置,否则对应的方法是 nameOfInvolvedProperty + "Changed"
(在这种情况下,正如您所做的那样,timeFrameChanged
)。
但是,您实际上并没有改变 属性 的值!如果你真的改变了 属性,它会起作用:
<button click.delegate="changeProp()">
Change prop
</button>
和虚拟机:
changeProp() {
this.timeFrame = null;
this.timeFrame = this.state.timeframe;
}
现在您会看到处理程序正确触发。
但在您的代码中,此 属性 仅分配一次,此处:
timeFrame: TimeFrame = this.state.timeframe;
记住,这只是取消引用。换句话说,此代码告诉应用程序获取 this.state.timeframe
的值,将它的 瞬时 值存储在该变量中并忘记 this.state.timeframe
。所以它不会在 this.state.timeframe
更新时更新。
如documentation中所述,您可以在一定程度上配置observable
装饰器,但是,据我所知,没有办法以这样一种方式配置它,将其设置为观察嵌套属性(或者可能只是我不知道该怎么做)。
即使可能,我认为处理此类情况的更简洁的方法是使用 event aggregator。这使您能够采用订阅机制——每当您感兴趣的值发生变化时,您都会发布一个事件,并且任何对该变化感兴趣的组件都可以订阅它并相应地更新自身。
一个简单的实现:
import { Router, RouterConfiguration } from 'aurelia-router';
import { autoinject } from 'aurelia-framework';
import { observable } from 'aurelia-binding';
import { StateContainer } from './state-container';
import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
@autoinject
export class App {
subscription: Subscription = null;
constructor(private state: StateContainer, private eventAggregator: EventAggregator) {
}
activate() {
this.subscription = this.eventAggregator.subscribe('state:timeframe', (e) => {
console.log('Timeframe changed!', e);
});
}
deactivate() {
// Make sure to dispose of the subscription to avoid memory leaks.
this.subscription.dispose();
}
change() {
this.state.setTimeframe(new Date(), new Date());
}
}
和StateContainer
:
import { TimeFrame } from "./time-frame";
import { autoinject } from "aurelia-framework";
import { EventAggregator } from "aurelia-event-aggregator";
@autoinject
export class StateContainer {
public timeframe: TimeFrame = null;
constructor(private eventAggregator: EventAggregator) {
}
public setTimeframe(startTime: Date, endTime: Date) {
// treat this.timeframe as immutable
this.timeframe = { startTime: startTime, endTime: endTime };
this.eventAggregator.publish('state:timeframe', this.timeframe);
}
}
希望对您有所帮助。