简体   繁体   中英

emberjs glimmer object set() with variable property name

I have a component in Ember 3.15 where I am trying to do something like

import { action, set } from '@ember/object';

@action
someMethod() {
  const value = ... // something random
  let propertyName = ... // some variable string
  set(this, propertyName, value);
}

It seems to be working fine in the browser but typescript is flagging the set line as an error (specifically the propertyName argument). So if it works, why doesn't typescript like it?

This also seems to be happening with get() where it doesn't like variable propertyNames like get(this, propertyName) .

Generally if your property is @tracked you dont need set and can just do this[propertyName] = value; .

However your problem is probably a general typescript limitation. Actual a general problem of static typing:

Typescript does only static analysis. So it does not execute your code. So it can not know is a dynamically generated property key actually exists.

So if you have something like this:

class Foo {
  data1: number = 1;
  data2: number = 2;
  foo() {
    const fixedProp = 'data1';
    console.log(this[fixedProp]);
    const dynamicProp = 'data' + (1 + 1);
    console.log(this[dynamicProp]);
  }
}

Then typescript will not be able to verify if this[dynamicProp] actually exists because for this it would need to execute 'data' + (1 + 1); so it would know what dynamicProp actually is. So it is impossible to know if this[dynamicProp] exists by static analysis.

You can just tell typescript to do what you want by (this as any)[dynamicProp] and it will just ignore it. But generally if you dynamically compute property keys you can not rely on static analysis.

There are two basic issues with the situation you have described—one of them related to TypeScript, one of them not.

The TypeScript issue is that TS is aware of the names of properties in general, and will check that you're setting things correctly—both when using normal JS property lookup and assignment, and when using Ember's get and set functions. Specifically, the types for Ember try to make sure you don't end up typo-ing things when doing get and set . You can see why they don't allow arbitrary strings in this example:

import Component from '@ember/component';
import { action, set } from '@ember/object';

export default class Whoops extends Component {
  greeting = 'Hello';

  @action updateGreeting(newGreeting) {
    set(this, 'greering', newGreeting);
    //         ----^--- TYPO!!!
  }
}

If the types for set (or get ) just allowed arbitrary strings, TS couldn't help you at all here; it would let it go, and you'd have to figure out the bug yourself—instead of the compiler helpfully telling you about it ahead of time.

In the case you're running into, TypeScript presumably just sees a string , and it says “I don't have any way to check if this string belongs to the property.”

There are a couple ways to improve things here. First of all, if you can, you should figure out if it's possible to constrain the type of propertyName to be a keyof for the type it's coming from. (Explaining keyof is beyond the scope of this answer, this section in the handbook and this blog post will get you up to speed.)

Second, though, and connected to the bigger issue: you noted in discussion to the other answer on this question that the problem is that you're trying to deeply set properties on a single piece of tracked root state. In general, you should not mutate autotracked state this way—it's a holdover from the old observers-driven patterns that Ember Classic used with its computed properties. Instead, prefer to drive all changes to that autotracked state through the owner of that state. Then you won't need set at all, and the system will update correctly automatically.

You can do that either by making the nested state itself be autotracked, either by defining a class for it or by using something like tracked-built-ins to wrap a plain JS object. Either way, instead of reaching in and deeply mutating that state from just anywhere, do it only on the object that owns that state. If you follow that pattern, and constrain the propertyName to be a keyof TheOwnerOfTheState where TheOwnerOfTheState is some class, everything will “just work”—both on the Ember side and the TypeScript side.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM