Я просмотрел вопросы о stackoverflow, но не нашел нужного ответа в моем случае.
Есть

interface FruitBox {
  name: string
  desc: {
   'orange': number;
   'banana': number;
  }
}

interface IceBox {
  name: string
}

interface Vegabox {
  name: string
  desc: {
   'tomato': number;
   'potato': number;
  }
}

type UnionBox = FruitBox | Vegabox | IceBox;

Что я пытаюсь реализовать

type Ship = Record<string, (description: UnionBox['desc'] | UnionBox['name']) => void>;

Я пытаюсь назначить свойство description типу UnionBox['desc'], и если параметр desc не существует, назначить его UnionBox['name']

Получение следующей ошибки: Property 'desc' does not exist on type 'UnionBox'

Буду рад любой помощи или подсказке

пс. Я понимаю, что должен быть какой-то странный алгоритм, просто не уверен, в каком направлении искать

1
Sergii Onish 17 Сен 2021 в 08:38

2 ответа

Лучший ответ

См. Этот ответ. Дело очень похоже.

Когда TypeScript разрешает тип объединения, он позволяет вам использовать наиболее распространенный введите. Я имею в виду тот тип, который является общим для всех элементов объединения. Потому что этот вариант самый безопасный.

Проверим ваш союз:

interface FruitBox {
  name: string
  desc: {
   'orange': number;
   'banana': number;
  }
}

interface IceBox {
  name: string
}

interface Vegabox {
  name: string
  desc: {
   'tomato': number;
   'potato': number;
  }
}

type UnionBox = FruitBox | Vegabox | IceBox;

type AllowedKeys = keyof UnionBox // name

Как вы могли заметить (AllowedKeys), вам разрешено использовать только свойство name из UnionBox. Почему ? Потому что name существует в каждом союзе.

Чтобы справиться с этим, вы можете использовать помощник StrictUnion.

// credits goes to https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> = 
    T extends any 
    ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;

type StrictUnion<T> = StrictUnionHelper<T, T>

Полный код:

interface FruitBox {
    name: string
    desc: {
        'orange': number;
        'banana': number;
    }
}

interface IceBox {
    name: string
}

interface Vegabox {
    name: string
    desc: {
        'tomato': number;
        'potato': number;
    }
}

type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
    T extends any
    ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;

type StrictUnion<T> = StrictUnionHelper<T, T>

type UnionBox = StrictUnion<FruitBox | Vegabox | IceBox>;

type AllowedKeys = keyof UnionBox // name


type Ship = Record<string, (description: UnionBox['desc'] | UnionBox['name']) => void>; // ok


const record: Ship = {
    foo: (elem) => { }
}

Детская площадка

1
captain-yossarian 17 Сен 2021 в 07:12

UnionBox из вашего примера может быть одного из типов - FruitBox, Vegabox или IceBox. IceBox - это то, что делает Typescript несчастливым - он не содержит свойства desc. Представьте, что у вас есть объект типа IceBox, который затем передается в функцию, которая принимает UnionBox в качестве параметра, а затем использует свойство desc - она ​​обращается к несуществующему.

Я пытаюсь назначить свойство описания типу UnionBox ['desc'], и если параметр desc не существует, назначить его UnionBox ['name']

Я не уверен, как вы ожидаете, что это будет работать, поскольку в вашем примере описание просто набирается либо как строка, либо как строка - | оператор просто создаст объединение. Но я думаю, вы могли бы просто принять unionBox: UnionBox в качестве параметра и добавить внутри логику, которая должна была бы unionBox.desc ?? unionBox.name получать name, если desc не существует.

0
Vladyslav Zavalykhatko 17 Сен 2021 в 05:58