#typescript
Вопрос:
Возможно ли в Typescript вывести несколько свойств одного и того же типа или, возможно, вывести типы на основе определенного свойства..
Допустим, у нас есть ->
interface Test<T> {
value1: T;
value2: T;
}
const test1:Test<string> = {
value1: 'string1',
value2: 'string1'
}
В приведенном выше примере, если бы я превратил значение 1 или значение 2 в число, это было бы ошибкой, отлично. Но было бы неплохо удалить универсальный <string>
, и чтобы он работал, он основан, возможно, на первых типах свойств value1
.
Сначала я подумал, что мог бы использовать значение по умолчанию для T как любое, и позже он определит тип, но он просто сохраняется как любой, как видно ниже, у нас есть строка и число.
interface Test<T = any> {
value1: T;
value2: T;
}
const test1:Test = {
value1: 'string1',
value2: 2
}
Комментарии:
1. К сожалению, это очень сложно из-за того, что машинопись выводит типы союзов, поэтому она сделает вывод
string | number
, чтобы это сработало.
Ответ №1:
Как вы видели, параметр универсального типа по умолчанию не оказывает желаемого эффекта. Если вы ссылаетесь на тип как Test
без указания параметра типа как в Test<string>
, компилятор будет использовать значение по умолчанию и интерпретировать его как Test<any>
. Это не будет для вас выводом string
. Существует запрос функции, microsoft/TypeScript#10671, запрашивающий способ, чтобы компилятор выводил неуказанные параметры типа… что-то вроде Test<infer>
или Test<*>
. Но до сих пор TypeScript не обладает этой функцией.
Вместо этого вы можете создать универсальную служебную функцию, которая возвращает свои входные данные:
const asTest = <T,>(test: Test<T>) => test;
Когда вы вызываете универсальную функцию, TypeScript попытается определить соответствующий тип для тестового параметра. Вы можете вызвать эту функцию вместо аннотирования переменной:
const testString = asTest({
value1: "string1",
value2: "string2"
})
// const testString: Test<string>
const testStringNumber = asTest({
value1: "string1",
value2: 2 // error! 'number' is not assignable to 'string'
})
// const testString: Test<string>
Это ведет себя так, как хотелось бы.
Однако обратите внимание, что не всегда очевидно, будет ли компилятор отклонять значение или синтезировать тип объединения для своего параметра типа. В testStringNumber
этом было бы разумно T
сделать вывод как string | number
, но эвристика, которую использует компилятор, заключается в том, что люди обычно не хотят выводить объединения примитивных типов.
С другой стороны, следующий код приводит к выводу объединения:
const testUnion = asTest({
value1: { a: "" },
value2: { b: "" }
})
/* Test<{
a: string;
b?: undefined;
} | {
b: string;
a?: undefined;
}> */
Этот тип Test<{a: string; b?: undefined} | {b: string; a?: undefined}>
точно описывает тип передаваемого значения asTest()
. Эвристика компилятора предполагает, что объединения типов объектов являются более приемлемыми и ожидаемыми.
Но, возможно, вы предпочли бы что-то, что жалуется на такие союзы и использует только (скажем) value1
для вывода T
, а затем просто проверяет value2
это. То есть вы хотели бы T
, чтобы в value2
качестве параметра типа без вывода использовался параметр, как указано в microsoft/TypeScript#14829. Официальной прямой поддержки для этого нет, но есть несколько разумных способов смоделировать это. Например:
const asTest = <T, U extends T>(test: { value1: T, value2: U }): Test<T> => test;
В этой версии asTest
используются два параметра универсального типа: T
для value1
и U
для value2
. U
ограничен возможностью назначения T
, поэтому компилятор будет принимать входные данные только там, где value2
есть тип, которому можно назначить value1
. И компилятор сделает вывод, только T
посмотрев value1
, а не на value2
. Это работает одинаково для testString
и testStringNumber
:
const testString = asTest({ value1: "string1", value2: "string2" });
// const testString: Test<string>
const testStringNumber = asTest({
value1: "string1",
value2: 2 // error! 'number' is not assignable to 'string'
})
// const testString: Test<string>
Но теперь testUnion
выдает ошибку:
const testUnion = asTest({
value1: { a: "" },
value2: { b: "" } // error! '{ b: string; }' is not assignable to '{ a: string; }'
})
потому {b: ""}
что не может быть присвоен типу {a: string}
, из которого сделан вывод value1
.
Комментарии:
1. Спасибо, я думал, что схожу с ума, каждая моя попытка казалась мне провалом в кроличью нору. Я попробовал
U extends T
непосредственно на типе, но у этого была проблема с курицей и яйцом 🙂