Typescript - передать один элемент UnionType в функцию, но не оба - PullRequest
0 голосов
/ 24 января 2020

У меня есть два интерфейса, и я хочу создать функцию, в которую включен интерфейс A или интерфейс B, но не оба одновременно. Использование простого UnionType не работает, поскольку оно также позволяет включать оба интерфейса

Пример (https://stackblitz.com/edit/typescript-qgytsy)

interface A{
  a: string;
}

interface B{
  b: string;
}

function aOrB(modifier: A | B) {}


aOrB({ a: '' }); // works!
aOrB({ b: '' }); // works!
aOrB({ a: '',  b: '' }); // also works but should not work!

Есть ли способ достичь это так, что первые два вызова aOrB(...) работают, а последний нет?

Br, Benedikt

Ответы [ 2 ]

2 голосов
/ 24 января 2020

Вы можете определить тип, который исключает определенные свойства:

type IFoo = {
  bar: string; can?: never
} | {
    bar?: never; can: number
  };


let val0: IFoo = { bar: "hello" } // OK only bar
let val1: IFoo = { can: 22 } // OK only can
let val2: IFoo = { bar: "hello",  can: 22 } // Error foo and can
let val3: IFoo = {  } // Error neither foo or can

Затем используйте этот тип для параметра модификатора.

1 голос
/ 24 января 2020

Система типов TS является структурной, это означает, что {a: 'a', b: 'b'} является правильным членом A | B, потому что она присваивается A, поскольку имеет все свойства A и присваивается B по той же причине. Нет проблем использовать такой объект, где требуется A или B, так как он имеет все, что нужно обоим с точки зрения структуры. Рассмотрим следующий фрагмент:

function fOnA(modifier: A) { } // we require only A
const a = { a: '',  b: '' } // we create object with additional props
fOnA(a) // no error

Мы можем выполнить проверку типа { a: '', b: '' }.

type ExtendsA = { a: '', b: '' } extends A ? true : false // evaluates true
type ExtendsB = { a: '',  b: '' } extends B ? true : false // evaluates true

Как видите, { a: '', b: '' } является допустимым объектом, который будет использоваться для A и для B.

Понятно, что использование такой конструкции с дополнительными свойствами не вредит, поскольку у нас всегда есть то, что нам нужно.

Случай, когда мы хотим различить, если у нас есть A или B - это случай, когда мы должны создать дискриминант, чтобы иметь возможность проверить, какой объект попал внутрь функции. Рассмотрим:

interface A{
  kind: 'A', // discriminant 
  a: string;
}

interface B{
  kind: 'B', // discriminant 
  b: string;
}

function aOrB(modifier: A | B) {}

aOrB({ kind: 'A', a: '' }); // works!
aOrB({ kind: 'B', b: '' }); // works!
aOrB({ kind: 'B', a: '' }); // error as it should be
aOrB({ kind: 'B', b: '', a: '' }); // error as it should be

Я добавил kind, чтобы различать guish обе версии. Такой подход имеет много преимуществ, так как теперь мы можем легко проверить, есть ли у нас A или B, просто проверив kind.

Другое решение состоит в том, чтобы заблокировать свойства, которые нам не нужны, never ключевое слово. Обратите внимание:

interface A{
  a: string;
  b?: never;
}

interface B{
  b: string;
  a?: never;
}

function aOrB(modifier: A | B) {}

aOrB({ a: '' }); // works!
aOrB({ b: '' }); // works!
aOrB({ a: '', b: ''}); // error as it should be

Добавляя prop?: never;, я не позволяю объекту проходить требования A и B.

...