Тип переменной Typescript не меняется даже при строго типизированных манипуляциях

Пример TS Playground

Пример кода

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 понять, что тип этой переменной должен меняться?

Во-первых, ExtA & ExtB имеет недопустимый тип и имеет два конфликтующих литеральных свойства (невозможно, чтобы type имел значение «a» И значение «b» одновременно), поэтому комбинированный тип оценивается как never. Во-вторых, «известный» тип переменной определяется при ее объявлении и на самом деле не изменяется. Если вы присвоите результаты addA и addB новым переменным, вы увидите изменение типа.

Steven Frew 30.08.2024 17:50

@StevenFrew «известный» тип переменной определяется при ее объявлении и на самом деле не меняется» - на самом деле это возможно, основываясь на анализе потока управления.

Bergi 30.08.2024 17:58

• Пожалуйста, отредактируйте , чтобы исправить конфликтующие type, возможно, просто удалите это свойство. • Как только вы это сделаете, TS не сможет изменять типы переменных путем переназначения. Либо вам нужно назначить разные переменные, либо вам нужно использовать функции утверждения. Оба метода показаны по этой ссылке на игровую площадку. Это полностью решает вопрос? Если да, то я напишу ответ или найду подходящий дубликат. Если нет, то чего не хватает?

jcalz 30.08.2024 18:13

@jcalz - это близкий аналог моей реальной ситуации, настолько отличный момент, что & их вместе будет недействительно. Я обновил свою игровую площадку и пример, удалив их, и рассмотрю дизайн отдельно. Я думаю, что мой основной вопрос об обновлении типов остается здесь.

ABMagil 30.08.2024 18:13

@jcalz ссылка на твою игровую площадку отвечает на мой вопрос. Преобразуйте в ответ, и я выберу его. asserts — это не то, что я использовал раньше. Спасибо за направление!

ABMagil 30.08.2024 18:15
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
4
5
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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() для расширения ввода, то функции утверждения не смогут работать, поскольку это не сужение. Тогда вам придется использовать разные переменные.

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

Другие вопросы по теме

Похожие вопросы

Почему функции Contravariance с их параметрами в машинописном тексте?
Получите доступ к этому в функции, которая является свойством объекта
Создание сопоставлений на основе перечислений, включая обеспечение ошибок компилятора
Почему сопоставление массива не работает с той же записью сопоставления рекламы?
React Router обнаружил следующую ошибку во время рендеринга. Ошибка: отрисовано меньше перехватчиков, чем ожидалось
Почему машинописный текст не позволяет частично указывать аргументы типа, чтобы создать новую универсальную функцию из другой универсальной функции?
Компонент React со свойством «as», которое указывает на другой компонент и также наследует его свойства
Введите параметры из параметров функции
Как ввести динамический компонент в общий компонент Vue?
Можно ли заставить все дополнительные свойства интерфейса машинописного текста принимать значение null?