tc39 / proposal-grouped-and-auto-accessors

Grouped Accessors and Auto-Accessors for ECMAScript
https://tc39.es/proposal-grouped-and-auto-accessors
MIT License
57 stars 5 forks source link

Use cases for observing accessor pairs #11

Open littledan opened 2 years ago

littledan commented 2 years ago

A big new capability that this feature enables is allowing decorators to work across a getter/setter pair. The README here mentions observer decorators. Do you have any more concrete examples of when observing or otherwise decorating a getter/setter pair is useful? The observer decorators I've seen so far would make more sense in conjunction with a simple accessor x;, so I'm just missing context on the use cases here.

rbuckton commented 2 years ago

I'll put together some concrete examples. I've seen this come up in MVVM (model-view-viewmodel), where the majority use case of an @observe may be field-like definitions, but there are also cases where you need to evaluate logic during the get or set to provide reactivity, such as:

class MyViewModel {
  // non-reactive:
  // @observe
  // accessor format = "simple";

  // reactive:
  #format;

  // I still want to observe changes to `format`
  @observe
  accessor format {
    // getter that uses lazy init
    get() { return this.#format ??= getDefaultFormat(); } 

    // setter that updates other observed values
    set(value) {
      if (this.#format !== value) {
        const oldFormat = this.#format;
        this.#format = value;
        this.text = this.reformat(this.text, this.#format, oldFormat);
      }
    }
  }

  // I want to observe changes to `text`
  @observe
  accessor text = "";
}
rbuckton commented 2 years ago

The workarounds for this both would be a poor developer experience:

// using `accessor`
class MyViewModel {
  // NOTE: decorator application order is *very* important here.
  @observe
  @onSet(function(oldFormat, newFormat) {
    if (oldFormat !== newFormat) {
      this.text = this.reformat(this.text, oldFormat, newFormat);
    }
  })
  @lazyInit(() => getDefaultFormat());
  accessor format;

  ...
}

// entangling getter/setter
const observeFormat = createAccesorPairDecorator(observe); // and having to do this for 10+ properties...

class MyViewModel {
  #format;

  @observeFormat.getter
  get format() { ... }

  @observeFormat.setter
  set format(value) { ... }

  ...
}
littledan commented 2 years ago

Thanks for these interesting examples. I agree that it looks cleaner with this feature.