Присвоение типа переменной на основе значения другой переменной

Я хочу сделать что-то вроде этого:

document("post").insert({ ... } /* TYPE SHOULD BE AUTOMATICALLY DETERMINED BY TYPESCRIPT */ ); 

Функция document() возвращает объект, содержащий такие функции, как insert. Объект также содержит свойство document, которому присваивается значение аргумента, переданного функции document(). Объект имеет тип DocumentObject, и такие функции, как insert, могут обращаться к свойству document с помощью this.document. Все работает нормально, но я хочу insert принять аргумент определенного типа, определяемого значением this.document.

Как я могу иметь параметры, типы которых различаются в зависимости от значения другой переменной. Это заявление insert:

export default async function (this: DocumentObject, record: ???): Promise<...> {...}

Переменная this.document (доступ к которой можно получить из тела функции) может содержать следующие значения: post, user, comment. У меня также есть соответствующие типы для каждого возможного значения: Post, User, Comment. Вот какthis.document определяется:

document: "post" | "comment" | "user" = ...;

Мой вопрос: как я могу использовать Typescript для сопоставления каждого значения с соответствующим типом, чтобы я мог назначить этот тип параметру record? Возможно ли это вообще в Typescript?

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

Пожалуйста, отредактируйте код здесь, чтобы он стал минимально воспроизводимым примером, где мы можем скопировать и вставить его в наши собственные IDE и немедленно приступить к работе над ним. В настоящее время нам нужно написать свои собственные типы Post, User, Comment и т. д. И каким бы ни был тип вывода Promise<>.

jcalz 22.06.2024 03:01

(см. предыдущий комментарий) Соответствует ли этот подход вашим потребностям? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?

jcalz 22.06.2024 03:06

@jcalz О да, это отлично работает, именно то, что я хотел!

Omar Ahmed 22.06.2024 18:36

Я напишу ответ, когда у меня будет возможность.

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

Ответы 2

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

type PostDoc = {author: string}
type UserDoc = {name:string}
type CommentDoc = {text:string}

type DocumentObject = {
  document:"post"|"user"|"comment"
}

type DocumentTypes = {
  post: PostDoc,
  user: UserDoc,
  comment:CommentDoc
} 

async function insert<D extends DocumentObject, R extends DocumentTypes[D["document"]]>(doc:D, record:R) {
    ...
}

Тогда тип второго параметра зависит от значения поля document.

insert({document:"post"}, {author:"a"})
insert({document:"user"}, {name:"b"})
insert({document:"comment"}, {text:"c"})
insert({document:"comment"}, {name:"d"}) // TypeError
insert({document:"user"}, {author:"e"}) // TypeError

Похоже, вы неправильно поняли мой код: параметр this в функции не является параметром, который следует заменять аргументом; вместо этого это то, как набирается ключевое слово this. Кстати, это называется «Fake This». typescriptlang.org/docs/handbook/release-notes/…. В любом случае, будет ли это работать, если this не является параметром для передачи? Мол, это сработает: document("user").insert({ name: "a" })?

Omar Ahmed 22.06.2024 02:10
Ответ принят как подходящий

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

interface DocumentMap {
    post: Post;
    user: User;
    comment: Comment;
}

Как только мы это сделаем, мы сможем сделать DocumentObjectобобщенным в типе K свойства document следующим образом:

interface DocumentObject<K extends keyof DocumentMap> {
    document: K;
    insert(record: DocumentMap[K]): Promise<any>;
}

Здесь Kограничено до keyof DocumentMap, что является всего лишь типом объединения "post" | "user" | "comment" по желанию. Затем метод insert() принимает аргумент record индексированного типа доступа DocumentMap[K].

Наконец, ваша функция document является универсальной по типу K своего аргумента и возвращает значение типа DocumentObject<K>:

declare function document<K extends keyof DocumentMap>(k: K): DocumentObject<K>;

Давайте проверим это:

document("post").insert(post); // okay
document("user").insert(user); // okay
document("comment").insert(comment); // okay
document("user").insert(post); // error

Выглядит неплохо. Когда вы вызываете document("user"), вы получаете DocumentObject<"user"> типа {document: "user", insert(record: User): Promise<any>}, поэтому метод insert() принимает User и отклоняет Post.

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

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

Как установить новое значение для «Сигнала», который вызывается из «Эффекта»?
Как я могу вывести типы аргументов нескольких функций в TypeScript
Сопоставьте значения из NgForm с моделью машинописного текста, определенной в Angular
Могу ли я использовать файлы ts точно так же, как я использую файлы js?
Проблема вывода типа с массивом универсальных объектов конфигурации с разными внутренними типами, которые зависят от другого внутреннего свойства
Почему добавление круглых скобок удаляет «только для чтения [x: число]: число;» из этого типа?
Введите 'строка | логическое значение не может быть назначено для ввода «никогда» в машинописном тексте
Как добавить динамические метаданные на страницу [slug] в маршрутизаторе приложений next.js
Программно переключить свойство, нулевое или необязательное, на обязательное в Zod
Кортеж, который принимает только одно вхождение определенного типа