Пример кода
type Base = {
name: string
}
type ExtA = {
address: string
}
type ExtB = {
streetName: string;
}
function handler<T extends Base>(input: T): T {return input}
/** how do I build an object up? */
let payload = {
name: "Aaron"
}
const addA = <T extends Base>(payload: T): T & ExtA => {
return {
...payload,
type: "a",
address: "123 Fake St"
}
}
const addB = <T extends Base>(payload: T): T & ExtB => {
return {
...payload,
type: "b",
streetName: "Main"
}
}
payload = addA(payload)
payload = addB(payload)
// type should be Base & ExtA & ExtB but it isn't
const wrongType = handler(payload)
// ^?
Я ожидаю, что payload
изменит тип, когда он пройдет через мои манипулирующие функции addA
и addB
, но это не так. Как мне заставить TS понять, что тип этой переменной должен меняться?
@StevenFrew «известный» тип переменной определяется при ее объявлении и на самом деле не меняется» - на самом деле это возможно, основываясь на анализе потока управления.
• Пожалуйста, отредактируйте , чтобы исправить конфликтующие type
, возможно, просто удалите это свойство. • Как только вы это сделаете, TS не сможет изменять типы переменных путем переназначения. Либо вам нужно назначить разные переменные, либо вам нужно использовать функции утверждения. Оба метода показаны по этой ссылке на игровую площадку. Это полностью решает вопрос? Если да, то я напишу ответ или найду подходящий дубликат. Если нет, то чего не хватает?
@jcalz - это близкий аналог моей реальной ситуации, настолько отличный момент, что &
их вместе будет недействительно. Я обновил свою игровую площадку и пример, удалив их, и рассмотрю дизайн отдельно. Я думаю, что мой основной вопрос об обновлении типов остается здесь.
@jcalz ссылка на твою игровую площадку отвечает на мой вопрос. Преобразуйте в ответ, и я выберу его. asserts
— это не то, что я использовал раньше. Спасибо за направление!
TypeScript не моделирует произвольное изменение состояния переменных. И хотя существует некоторая поддержка сужения переменных при присваивании , это происходит только в том случае, если тип переменной является типом объединения. Поэтому, если вы переназначите payload
типа X | Y
на значение типа X
, то payload
сузится до X
. Но если вы переназначите payload
типа X
на значение типа X & Y
, сужения не произойдет.
Если вы хотите эффективно использовать систему типов, вам следует разрешить каждой переменной иметь один тип на протяжении всего ее существования. Таким образом, вместо переназначения payload
вы можете просто иметь новые переменные для каждого присваивания:
const payload = {
name: "Aaron"
}
const payload1 = addA(payload)
const payload2 = addB(payload1)
const result = handler(payload2)
// ^? const result: { name: string; } & ExtA & ExtB
Если вы действительно хотите представить одну переменную, тип которой со временем становится уже, вы можете сделать это с помощью функций утверждения, но у них есть много предостережений. Они работают только за счет сужения переменной/свойства, переданного в качестве аргумента, и не могут возвращать какие-либо определенные значения, поэтому переназначение все равно не будет сужаться так, как вы предполагали. Это должно было бы выглядеть так:
function addA<T extends Base>(payload: T): asserts payload is T & ExtA {
Object.assign(payload, {
address: "123 Fake St"
});
}
function addB<T extends Base>(payload: T): asserts payload is T & ExtB {
Object.assign(payload, {
streetName: "Main"
});
}
Здесь addA()
и addB()
— функции утверждения, которые фактически изменяют свои входные данные (поскольку Object.assign() изменяет свой первый аргумент) вместо того, чтобы что-либо возвращать.
Теперь у вас может быть одна переменная payload
, и каждый вызов функций утверждения будет сужать ее:
const payload = {
name: "Aaron"
}
addA(payload)
addB(payload)
const result = handler(payload)
// ^? const result: { name: string; } & ExtA & ExtB
Это работает, но я бы предпочел избегать этого, если вам это действительно не нужно. Если вы когда-нибудь решите, что вам нужно что-то вроде removeA()
для расширения ввода, то функции утверждения не смогут работать, поскольку это не сужение. Тогда вам придется использовать разные переменные.
Во-первых,
ExtA & ExtB
имеет недопустимый тип и имеет два конфликтующих литеральных свойства (невозможно, чтобыtype
имел значение «a» И значение «b» одновременно), поэтому комбинированный тип оценивается какnever
. Во-вторых, «известный» тип переменной определяется при ее объявлении и на самом деле не изменяется. Если вы присвоите результатыaddA
иaddB
новым переменным, вы увидите изменение типа.