#typescript #typescript-typings #typescript-generics
#typescript #typescript-типизации #typescript-дженерики
Вопрос:
Дана функция, которая принимает массив объектов и возвращает объект, все ключи которого соответствуют свойству каждого из объектов в аргументе… похоже, должен быть способ получить более явный возвращаемый тип.
Функция-
function objectFromArray(arr: { key: string }[]) {
let obj: {[k: string]: true} = {};
for (let {key} of arr) {
obj[key] = true;
}
return obj;
}
Если я выполню следующее
let myArray = [{key: 'a'}, {key: 'b'}, {key: 'c'}];
let myObj = objectFromArray(myArray);
Тогда тип myObj
let myObj: {
[k: string]: true;
}
Но я чувствую, что имеется достаточно информации, чтобы typescript мог сделать вывод, что тип myObj
на самом деле
let myObj: {
a: true;
b: true;
c: true;
}
Мой вопрос заключается в том, что нужно сделать в objectFromArray
функции, чтобы получить явный возвращаемый тип на основе аргумента. Для ясности я не беспокоюсь о значениях возвращаемого типа, просто беспокоюсь о получении явных ключей.
Ответ №1:
Если вы хотите, чтобы это работало, вам нужно objectFromArray()
быть универсальной функцией в типе K
строковых литеральных значений в key
свойстве arr
элементов:
function objectFromArray<K extends string>(arr: readonly { key: K }[]) {
let obj = {} as { [P in K]: true }; // have to assert this because it's not true yet
for (let { key } of arr) {
obj[key] = true;
}
return obj;
}
readonly
In readonly { key: K }[]
просто означает, что мы специально не требуем, чтобы передаваемый массив был изменяемым. Это дает больше возможностей для ввода, которые нам понадобятся немного позже.
Возвращаемый тип — { [P in K]: true }
это сопоставленный тип, предоставляющий объект со свойством со значением a true
для каждого строкового литерала, вводимого в K
. (Вы также можете использовать Record
тип утилиты для предоставления эквивалента Record<K, true>
). Поскольку {}
значение этого типа недопустимо, вам потребуется утверждение типа, чтобы сообщить компилятору, что он должен обрабатывать obj
как тип { [P in K]: true }
.
Затем, когда вы создаете myArray
, поведение компилятора по умолчанию заключается в расширении его типа до Array<{key: string}>
, полностью забывая о строковых литеральных типах "a"
, "b"
, и "c"
. Чтобы предотвратить это, вы можете использовать const
утверждение:
let myArray = [{ key: 'a' }, { key: 'b' }, { key: 'c' }] as const;
/* let myArray: readonly [{
readonly key: "a";
}, {
readonly key: "b";
}, {
readonly key: "c";
}] */
Обратите внимание, как const
утверждение создает myArray
readonly
массив, поэтому полезно ослабить тип ввода функции. Теперь, когда компилятор знает достаточно myArray
, давайте вызовем objectFromArray()
:
let myObj = objectFromArray(myArray);
/* let myObj: {
a: true;
b: true;
c: true;
} */
Успех! myObj
известно, что имеет a
, b
, и c
свойства типа true
, по желанию.