通过@observable观察不可变对象

时间:2018-02-12 15:42:44

标签: aurelia

假设我有一个包含TimeFrame类的全局状态类,该类具有一些有用的属性

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()时。我做错了还是不可能?

1 个答案:

答案 0 :(得分:2)

是的,你做错了,我认为你误解了如何使用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>

和VM:

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);
  }
}

希望有所帮助。