Хотя существуют некоторые методы для манипулирования типами с помощью сигнатур индекса (см., Например, этот ответ ), указанная проверка c, которую вы хотите выполнить, здесь невозможна. Если значение аннотируется как тип string
, то компилятор не сузит его до строкового литерала типа , даже если вы инициализируете его строковым литералом:
const str: string = "hello"; // irretrievably widened to string
let onlyHello: "hello" = "hello";
onlyHello = str; //error! string is not assignable to "hello"
В приведенном выше примере переменная string
str
инициализируется значением "hello"
, но вы не можете присвоить ее переменной типа "hello"
; компилятор навсегда забыл, что значение str
является строковым литералом "hello"
.
Это «забывчивое» расширение верно для любой аннотации типа, не являющегося объединением. Если тип является объединением, компилятор фактически сужает тип переменной при присваивании, по крайней мере, до тех пор, пока переменная не будет переназначена:
const strOrNum: string | number = "hello"; // narrowed from string | number to string
let onlyString: string = "hello";
onlyString = strOrNum; // okay, strOrNum is known to be string
К сожалению, ваш тип Obj
не является типом объединения , А поскольку он имеет подпись индекса string
, компилятор будет знать только, что переменная, аннотированная как Obj
, будет иметь ключи string
и не будет помнить литеральное значение этих ключей, даже если он инициализируется литералом объекта со строковыми литеральными ключами:
const obj: Obj = { a: 1, b: 2 }; // irretrievably widened to Obj
let onlyAB: { a: 1, b: 1 } = { a: 1, b: 1 };
onlyAB = obj; // error! Obj is missing a and b
Таким образом, переменные a
и b
, которые были аннотированы как тип Obj
, известны компилятору только как тип Obj
. Он забыл какие-то отдельные свойства внутри них. С точки зрения системы типов a
и b
идентичны.
И, таким образом, независимо от того, в какие безумные игры типа я пытаюсь играть с подписью для mergeUnique()
, ничего, что я могу сделать, не сделает это так, что mergeUnique(a, b)
успешно, а mergeUnique(a, a)
неудачно; типы a
и b
являются идентичными типами без объединения; компилятор не может их различить.
Если вы хотите, чтобы компилятор запомнил отдельные ключи в a
и b
, вам не следует комментировать их, но позвольте компилятору вывести их. Если вы хотите убедиться, что a
и b
можно присвоить Obj
без фактического расширения их до Obj
, вы можете сделать вспомогательную функцию generi c, чтобы сделать это:
const asObj = <T extends Obj>(t: T) => t;
Функция asObj()
просто возвращает то же значение, которое она получает в качестве аргумента, и не меняет свой предполагаемый тип. Но поскольку T
ограничено с до Obj
, оно будет успешным только в том случае, если объект можно назначить на Obj
:
const a = asObj({ a: undefined }); // {a: undefined}
const b = asObj({ b: 3 }); // {b: number}
const c = asObj({ c: "oopsie" }); // error!
Теперь у вас есть a
и b
узких типов с известными строковыми литеральными ключами свойств (и c
с ошибкой компилятора, потому что "oopsie"
не является `числом | неопределенным). И, таким образом, остальная часть вашего кода ведет себя так, как нужно:
// these all succeed
const res01 = mergeUnique({ a: undefined }, { b: 3 })
const res02 = mergeUnique({ a: undefined }, b)
const res03 = mergeUnique(a, { b: 3 })
const res04 = mergeUnique(a, b)
const res05 = mergeUnique({ b: 3 }, { a: undefined })
const res06 = mergeUnique(b, { a: undefined })
const res07 = mergeUnique({ b: 3 }, a)
const res08 = mergeUnique(b, a)
// these all fail
const res09 = mergeUnique({ a: undefined }, { a: undefined })
const res10 = mergeUnique({ a: undefined }, a)
const res11 = mergeUnique(a, { a: undefined })
const res12 = mergeUnique(a, a)
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код