代码之家  ›  专栏  ›  技术社区  ›  Fedoranimus

通过@observable观察不可变对象

  •  2
  • Fedoranimus  · 技术社区  · 7 年前

    让我们假设我有一个全局state类,它包含一个具有一些有用属性的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 回复  |  直到 7 年前
        1
  •  2
  •   Balázs    7 年前

    是的,你做错了什么,我想你误解了 observable 装饰师和相应的 timeFrameChanged 方法是要使用的。

    让我们一点一点地来。请看以下几行:

    @observable
    timeFrame: TimeFrame = this.state.timeframe;
    
    timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) { ... }
    

    这个 可观察到 decorator告诉Aurelia,只要应用它的属性发生更改,就执行相应的方法。除非另有配置,否则相应的方法为 nameOfInvolvedProperty + "Changed" (在这种情况下,正如您所做的那样, 时间框架已更改 ).

    但是,您从未真正更改该属性的值!如果您实际更改了该属性,它将起作用:

    <button click.delegate="changeProp()">
      Change prop
    </button>
    

    和虚拟机:

    changeProp() {
      this.timeFrame = null;
      this.timeFrame = this.state.timeframe;
    }
    

    现在您将看到处理程序正确地触发。

    但在您的代码中,此属性仅分配一次,如下所示:

    timeFrame: TimeFrame = this.state.timeframe;
    

    记住,这只是取消引用。换句话说,此代码告诉应用程序获取 this.state.timeframe ,存储 暂时的 它在该变量中的值,并忽略 这状态时间框架 . 所以它不会在任何时候更新 这状态时间框架 已更新。

    如中所述 documentation ,您可以配置 可观察到 然而,在某种程度上,装饰者, 据我所知 ,无法将其配置为观察嵌套属性(或者可能是我不知道如何进行)。

    即使有可能,我认为处理这种情况的更干净的方法是使用 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);
      }
    }
    

    希望这有帮助。