Как ограничить тип свойства интерфейса подмножеством объявленного перечисления? - PullRequest
1 голос
/ 05 июля 2019

У меня есть внешний модуль, объявляющий группу перечислений:

declare enum A {
B,
C,
D
}

Затем я создаю интерфейсы на основе этих перечислений:

interface ISomeProps {
someProp: A
}

Однако в некоторых случаях я хочу ограничить тип someProp только подмножеством A, например:

interface ISomeRestrictedProps {
restrictedProp: A.B | A.C
}

Можно ли обойтись без объявления нового enum с подмножеством падежей (как я сейчас это делаю)?

Я пытался использовать Extract или типы объединения, но я не могу ссылаться на регистры перечисления в объявленном перечислении, потому что я получаю ошибку TS:

enum has members with initializers that are not literals.

Ответы [ 2 ]

1 голос
/ 05 июля 2019

Ваш подход к использованию союза правильный.Проблема с объявлением enum.Typescript не позволит вам использовать объединение членов перечисления, если он не знает значений этих перечислений.Хотя не уверен, почему ?, отследил его до этого PR , в котором явно указано это правило:

Когда каждый член типа enum имеет либо автоматически назначенное значение, инициализатор, который указываетчисловой литерал или инициализатор, который задает один идентификатор с именем другого члена перечисления, этот тип перечисления считается типом перечисления объединения.Члены типа перечисления объединения могут использоваться как в качестве констант, так и в качестве типов, и тип перечисления эквивалентен объединению объявленных типов членов.

Так как перечисление объявлено, оно не будет иметьзначения присваиваются автоматически, и поскольку явных значений нет, они просто не будут рассматриваться как объединение членов перечисления.

Это будет работать, но я не уверен, является ли изменение определения перечисления опцией:

declare enum A {
    B = 0,
    C = 1,
    D = 2
}

interface ISomeRestrictedProps {
    restrictedProp: A.B | A.C
}
0 голосов
/ 05 июля 2019

Единственный способ обойти это - объявить перечисление по-другому.

Один из способов сделать это довольно многословен, но действует во многом как объявленное перечисление с неизвестными значениями.Истинные перечисления в TypeScript добавляют множество именованных типов и значений с небольшим объемом кода, поэтому для имитации этого вручную нам нужно много строк.Общий метод состоит в том, чтобы объявить значение и тип для каждого из A, A.B, A.C и A.D и сделать это таким образом, чтобы обрабатывать типы каждого значения перечисления как разныедруг от друга, не зная, что они на самом деле:

declare namespace A {
  interface _B { readonly __nominal: unique symbol; }
  export type B = _B & number;
  export const B: B;

  interface _C { readonly __nominal: unique symbol; }
  export type C = _C & number;
  export const C: C;

  interface _D { readonly __nominal: unique symbol; }
  export type D = _D & number;
  export const D: D;
}
type A = A.B | A.C | A.D;

В вышеприведенном, { readonly __nominal: unique symbol } служит техникой брендинга с использованием уникальных символов для создания TypeScriptобрабатывать каждый из A.B, A.C и A.D как номинально типизированных и, следовательно, отличающихся друг от друга типов, несмотря на то, что они имеют «одинаковую» структуру.Это немного обманывает компилятор, поскольку очевидно, что во время выполнения A.C не будет иметь свойства с именем __nominal, но, пока вы его игнорируете, оно должно работать нормально.

Давайте удостоверимся: еслиу вас есть переменная полного типа enum, вы можете назначить ей любой член:

let a: A = A.B; // okay
a = A.C; // okay
a = A.D; // okay

Но если у вас есть переменная только одного из типов членов, вы не можете назначить другие:

let b: A.B = A.B; // specifically only A.B
b = A.C; // error! A.C not assignable to A.B
b = A.D; // error! A.D not assignable to A.B

Теперь разрешены ваши типы:

interface ISomeRestrictedProps {
  restrictedProp: A.B | A.C;
}

И ведут себя более или менее так, как вы хотите:

const i: ISomeRestrictedProps = {
  restrictedProp: A.B // okay
};
i.restrictedProp = A.C; // okay
i.restrictedProp = A.D; // error! D not assignable to B | C

Ссылка на фирменный перечислимый код


Аналогично, вы можете просто сделать то, что @ TitianCernicova-Dragomir предложило и присвоить значения перечислениям.Они не обязательно должны быть фактическими значениями, если они отличаются друг от друга, и вы не ошибаетесь, считая их действительными значениями:

declare enum A {
  B = 0xbbbbb, // dummy value for B
  C = 0xccccc, // dummy value for C
  D = 0xddddd  // dummy value for D
}

Опять вы лжете компилятору о точных значениях, но пока вы просто игнорируете точные значения и не пишете код, который заботится о точных значениях, все должно быть хорошо.Последующий код должен работать так, как вы ожидаете (и я не собираюсь повторять приведенный выше код ... вы можете увидеть, как он работает по следующей ссылке)

Ссылка на dummy-val-enum code


В обоих из них вы немного лжете компилятору ... первый метод многословен, но не притворяется, что вы знаете числовые значения перечисления, тогда какпоследний метод более прост, но, возможно, подвергает риску неосторожного разработчика, который думает, что фиктивные значения являются действительными значениями.

В любом случае, надеюсь, это поможет;удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...