Как проверить, является ли ввод фирменным типом в TypeScript?

У меня такое ощущение, что я в корне неправильно понимаю фирменные типы. Вот мой код:

type BrandedString = string & { __brand: true };

function brandString(value: string): BrandedString {
  return value as BrandedString;
}

function isBrandedString(value: string): value is BrandedString {
  return (value as any)["__brand"];
}

let x = brandString("hello");
console.info("brandString", x, isBrandedString(x)); // returns false

Если я наведу курсор на «x», это покажет (в intellisense), что тип теперь является BrandedString, но если я попытаюсь вызвать попытку защиты типа «IsBrandedString», я получу false.

Я хочу реализовать isBrandedString, чтобы можно было проверить, имеет ли ввод фирменный тип или нет, но я не могу его получить. Вещи, которые я пробовал:

...
return (value as any)["__brand"] === true
return (value as any)["__brand"]
return (value as any).__brand !== undefined;
return value[__brand] !== undefined; // compilation error

Как я могу проверить, является ли данный ввод BrandedString?

Вы действительно неправильно понимаете; фирменные типы — это чисто конструкции времени компиляции, имитирующие номинальную типизацию. Это мешает вам писать const x: BrandedString = "xxx". Он не имеет никакого эффекта во время выполнения. Знаете ли вы, как TS стирает систему типов при компиляции в JS? Созданный JS для brandString — это function brandString(value) {return value}, который явно ничего не делает. Для этой цели вам не нужны фирменные типы. JS не позволяет «маркировать» примитивы. Если вам интересно, вам понадобится объект, содержащий строку. Это полностью решает вопрос? Если да, то я напишу ответ; если нет, то что мне не хватает?

jcalz 29.03.2024 20:18

По этой причине я начал с части «фундаментального недопонимания»; это мне очень помогает. Итак, а) имеет смысл, это чисто время компиляции, и на самом деле мы просто создаем замену невозможности самостоятельно создать примитивный тип. б) Я пытался следить за чем-то вроде этого (youtube.com/watch?v=rpw59rajUSI) и неправильно понял, что делал охранник в этом видео. Тогда ответьте на вопрос: имеет ли значение то, что мы добавляем в часть бренда «& {}»? Мне кажется, что это просто специальные «метаданные», которые мы используем при кодировании. Большое спасибо за ваш ответ!

Darshan 30.03.2024 21:19
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
2
319
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Система типов TypeScript во многом является структурной , а не номинальной . Это означает, что два типа, имеющие одинаковую структуру, считаются идентичными типами. Не имеет значения, имеют ли эти два типа разные имена или были объявлены в разных местах. TypeScript позволяет создавать псевдонимы типов, но это просто другое имя для существующего типа и не служит для различения типов или создания нового типа. Это просто другое название одного и того же:

type MyString = string;
const x: string = "abc";
const y: MyString = x; // okay, no difference between string and MyString

Но иногда люди хотят, чтобы в TypeScript были номинальные типы. Вы можете имитировать номинальную типизацию, добавив к типу некоторую случайную отличительную структуру, даже если эта структура на самом деле ничему не соответствует во время выполнения.

Для примитивов , таких как string, вы даже не можете добавить такую ​​структуру во время выполнения, потому что они на самом деле не содержат свойств (кажется, у них есть методы и свойства через автоупаковку, но это просто дает вам материал на прототип класса-обертки, например String ). Но это не мешает нам писать такой тип так, как если бы он существовал, и это дает нам фирменные примитивы:

type MyString = string & { __myString: true };
const x: string = "abc";
const y: MyString = x; // error!
//    ~ <-- Type 'string' is not assignable to type 'MyString'.

Теперь тип MyString структурно отличается от string тем, что предположительно обладает свойством __myString типа true. И поэтому вы больше не сможете случайно назначить stringMyString. В __myString и true тоже нет ничего особенного. Это просто что-то случайное, что вы добавляете туда, чтобы отличить это от других вещей. Если вы хотите создать другие типы фирменных строк, вам следует выбрать другое свойство бренда.

Все это нормально, за исключением того, что мы не можем получить значение типа MyString во время выполнения. Примитивы не могут хранить свойства так, как объекты:

y.__myString = true; // you can write this, but it doesn't work
// 💥 TypeError! can't assign to property "__myString" on "abc": not an object 

Вы можете солгать компилятору, что у вас есть один из этих фирменных примитивов, используя утверждение типа :

const z: MyString = "def" as MyString; // okay

и это позволяет вам перемещать значения, как если бы они были фирменными, но при этом эффект времени выполнения нулевой. Вся система типов TypeScript стирается при компиляции, поэтому as MyString исчезает. В TypeScript есть утверждения типов, а не приведение типов (или, по крайней мере, тот тип приведения, который он имеет, не следует путать с приведением в других языках, таких как Java или C, которые на самом деле что-то делают во время выполнения).

Поэтому единственное использование фирменного примитива — помочь разработчикам отслеживать различные варианты использования одного и того же базового примитива во время компиляции. Не существует теста времени выполнения, который можно было бы выполнить, чтобы определить, является ли значение фирменным. Ни один примитив времени выполнения на самом деле не имеет торговой марки.


Итак, ваша brandString функция

function brandString(value: string): BrandedString {
  return value as BrandedString;
}

просто возвращает свои входные данные и не имеет никакого эффекта во время выполнения. Вы не можете написать охранную функцию типа isBrandedString() , которая работает:

function isBrandedString(value: string): value is BrandedString {
  // impossible
}

Весь смысл чего-то вроде BrandedString в том, чтобы не дать вам случайно перепутать разные «типы» строк в вашем собственном коде. Это больше похоже на мысленный тег, который вы добавляете к типу, чтобы помочь вам отслеживать события. Тег существует только в вашем представлении о значении, а не о фактическом значении.


Итак, теперь вам нужно решить, почему вы это делаете. Если вам действительно нужно пометить значение во время выполнения, чтобы можно было отличить string от BrandedString во время выполнения, то вам в значительной степени придется отказаться от примитивов. Вы можете использовать тип объекта, например

type BrandedString = { value: string; __brand: true };

function brandString(value: string): BrandedString {
    return { value, __brand: true };
}

function isBrandedString(value: unknown): value is BrandedString {
    return !!value && typeof value === "object"
        && "__brand" in value && value.__brand === true;
}

let x = brandString("hello");
console.info("brandString", x, isBrandedString(x)); // returns true

Но, конечно, теперь вы просто держите string в контейнере и клеймите контейнер, а не веревку. Возможно, это не то, что вы хотели сделать, но у этого есть то преимущество, что оно действительно работает.

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

Какой невероятный и очень ясный ответ! Это полностью проясняет мои недоразумения и подтверждает то, что я подозревал, и на 100% решило мою проблему. Я изменил свои ожидания от BrandedString, чтобы он был просто напоминанием во время работы, и если у меня есть какая-либо причина проверять этот тип во время выполнения, мне просто придется перейти к «контейнерной» перспективе.

Darshan 01.04.2024 16:46

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