Вывод типа для объединяемых типов в TypeScript - PullRequest
0 голосов
/ 22 октября 2018

Скажем, у меня есть три базовых класса или типа, как показано ниже:

type A = { one: number };
type B = { one: number, two: string };
type C = { one: number, two: string, three: boolean };

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

type X = A | B | C;

Определение элементов массива и создание массива:

const a: A = { one: 1 };
const b: B = { one: 1, two: '2' };
const c: C = { one: 1, two: '2', three: true };

let list: X[] = [a, b, c];

Теперь, если я попытаюсь получить доступ к two Свойство второго или третьего элемента в этом массиве, я получаю следующую ошибку:

const z: X = list[2];
z.two;                // yields this error
error TS2339: Property 'two' does not exist on type 'X'. Property 'two' does not exist on type 'A'.

Я пытался изменить тип A - и другие фактически, с const a: A на const a: X, но я все еще получаю ту же ошибку.

Если я вместо этого приведу z: X к z: B;Я все еще получаю очень похожую ошибку снова:

const z: B = list[2];
z.two;                // yields this error
Type 'X' is not assignable to type 'B'. Type 'A' is not assignable to type 'B'. Property 'two' is missing in type 'A'.

Мне кажется, что механизм определения типа в TypeScript каким-то образом переопределяет мою явную типизацию и, в основном, вместо создания list: X[] он создал версию с пониженным приводомlist: A[].

Я также пытался использовать определения class и interface, чтобы увидеть, если это что-то меняет - хотя я был уверен, что это не из-за Структурный TypeScript Тип системы , и, как и ожидалось, ничего не изменилось.

Есть идеи, что я здесь не так делаю, или есть предложения по улучшению подхода?

Ответ

Оказалось, что изменение const z: B = list[2] на const z = <B>list[2] выполняет правильный кастинг, о котором я собирался.

Ответы [ 2 ]

0 голосов
/ 22 октября 2018

Это не отменяет вашу явную типизацию ... подчиняется вашей явной типизации, обрабатывая list как массив элементов типа X.X может быть A, B или C.Если я передам вам значение типа X, можно прочитать свойство one, поскольку оно определенно существует.Но небезопасно пытаться прочитать свойство two, потому что X может быть A, а A, как известно, не имеет two.Таким образом, вы получаете полезную ошибку:

z.two; // error!
// Property 'two' does not exist on type 'X'. Property 'two' does not exist on type 'A'.

Итак, каковы ваши варианты?Одним из них является просто сообщить компилятору, что вы знаете лучше, чем он, используя утверждение типа , как в другом ответе:

(z as B).two; // okay now

Это подавляет ошибку компилятора, но это действительноне очень хорошее решение, потому что оно частично отключает проверку типов, когда в этом нет необходимости.Следующее также не будет ошибкой, но это вызовет проблемы во время выполнения:

(list[0] as B).two.length;  // no compile error
// at runtime: TypeError: list[0].two is undefined

Обычно утверждения типа должны быть последним средством решения ошибок компилятора, которые должны использоваться только там, где вы не можете найтиразумный способ убедить компилятор в том, что то, что вы делаете, безопасно, и вы уверены, что это безопасно и останется безопасным даже перед лицом возможных изменений кода (например, вы измените list на [b,c,a] в будущем).


Лучшее решение - использовать type guard , чтобы убедить компилятор в том, что то, что вы делаете, безопасно.Это оказывает влияние на время выполнения, поскольку вы выполняете больше кода, но код, который вы выполняете, более перспективен на будущее, если вы измените list где-нибудь в будущем.Вот способ сделать это:

const z = list[2]; // z is inferred as A | B | C
if ('two' in z) {
  // z is now narrowed to B | C
  z.two; // okay
}

Таким образом, вы защищаете чтение z.two, используя in, чтобы проверить наличие свойства two перед его использованием.Теперь это безопасно.Если вы думаете, «почему я должен проходить через эту проблему, когда я знаю, что z будет иметь тип B (на самом деле C, ха-ха, list[2] является третьим элементом)», затем читайте:


Если вы уверены, что list всегда будет трехэлементным массивом типов A, B и C, в этом порядке, тоВы можете сообщить об этом компилятору и получить ожидаемое поведение во время компиляции без какой-либо защиты во время выполнения.Вы ищете типов кортежей :

const list: [A, B, C] = [a, b, c];

const z = list[2];
z.two; // okay
z.three; // okay

Вы сказали компилятору, что list - это трехэлементный кортеж типа [A, B, C].Теперь ошибок нет (и видно, что z - это C).Это безопасно с нулевым воздействием во время выполнения.Он также не позволяет вам связываться с list:

list[1] = a; // error!  A is not assignable to B

, поскольку вы сказали, что list[1] всегда является B, тогда вы должны назначить ему что-то совместимое с B:

list[1] = c; // okay, C is assignable to B

Итак, у вас есть три варианта: утверждения, защита типов и кортежи, и я рекомендую для этой ситуации кортежи.Надеюсь, это поможет.Удачи!

0 голосов
/ 22 октября 2018

Вы пытались использовать утверждение типа, например.

const z = <B>list[2]; // omitting the .two because that would never be assignable to B

?

...