Я работаю над созданием расширения Prisma. По определенным причинам я начал с JavaScript, используя подсказки JSDoc для набора текста. Расширение на самом деле работает отлично; однако подсказки типов ужасны, что заставило меня поверить, что я делаю что-то не так. Фактически, когда я переключился на транспилятор машинописных текстов, в моих файлах объявлений появились всевозможные ошибки, которые были невидимы при использовании esbuild
. Моя самая большая текущая проблема в их устранении заключается в том, что мне нужно использовать значение, предоставленное пользователем, когда исходное расширение создается в качестве ключа в различных других объявлениях типов в моих файлах объявлений.
Например:
//file: index.d.ts
import { MyMethod1Args } from './method_1.d.ts';
// these are mocks; really imported from Prisma
type SomeType = 'id' | 'foo';
interface SomeOtherType {
foo: string;
bar: number;
}
export interface RequiredArgs {
modelName: string;
idFieldName?: SomeType; // want to use user value of this as key elsewhere
}
// what I want to do
export type IdField = Pick<SomeOtherType, `${idFieldName}`>;
export type IdFieldKey = keyof IdField; // maybe typeof keyof IdField?
export declare function myExtendsion<I extends RequiredArgs>(args: I): {
Method1(args: MyMethod1Args): string
}
В этой (ретроспективно ошибочной) попытке использовать литерал шаблона я пытаюсь создать определение типа, которое зависит от пользовательского ввода. В коде их несколько десятков.
Причина такого решения, как я уже упоминал, заключается в том, что JavaScript уже работает и уже «набран» с помощью подсказок JSDoc, и я хотел бы перенести его, но медленно — без необходимости переписывать каждый файл JS заранее. Прямо сейчас эти подсказки JSDoc импортируются из других файлов объявлений, поэтому мне нужно иметь возможность использовать эти IdField
и IdFieldKey
в других местах. Например:
//File: method_1.d.ts
import { IdFieldKey } from '$type/index.d.ts';
// trying for MyMethod1Args = { foo: { in: string[] } },
// where "foo" was the value supplied to `idFieldName`.
export type MyMethod1Args = {
[K in idFieldKey]: { in: string[] }
};
Примечание. Это может иметь или не иметь отношение к ответу, но я включаю это здесь на тот случай, если это повлияет на ситуацию, и чтобы помочь объяснить, как это на самом деле используется. Что я сделал в JavaScript, так это то, что перед экспортом отдельных методов из основной функции я использую HoF, чтобы обернуть их все и передать аргументы верхнего уровня как configArgs
, чтобы каждый метод имел доступ к параметрам (например, idFieldName
Но я не включаю это в сигнатуру функции, так что это не сбивает с толку Intellisense или что-то в этом роде.
// File: Method_1.js
/**
* @param {import('$types/method_1.d.ts').MyMethod1Args} args
* @returns string
*/
export default function (args, configArgs) {
console.info(args[configArgs.idFieldName].in.join());
}
• Если этот вопрос не зависит от Prisma, пожалуйста, отредактируйте код, чтобы он представлял собой минимально воспроизводимый пример , который не использует сторонние типы для демонстрации. Мы должны иметь возможность копировать и вставлять его в наши собственные IDE и работать над ним, не добавляя ничего лишнего. Также используйте общепринятые имена; Имена пользовательских типов должны быть в UpperCamelCase. • Какого типа вы ожидаете idFieldName
? В этом примере только K
? Если да, то почему бы не использовать K
? Если нет, то что это? Кроме того, {[K]:⋯}
должно быть {[P in K]:⋯}
или Record<K, ⋯>
. Но без минимально воспроизводимого примера я просто предполагаю. Поэтому, пожалуйста, отредактируйте, чтобы это обеспечить.
@jcalz, Готово! Я выложил это на codeandbox. Допустимые значения idFieldName
зависят от того, что было предусмотрено для modelName
. Надеюсь, минимальный пример прояснит ситуацию.
Минимальный воспроизводимый пример должен быть в вопросе в виде открытого текста, а не только в виде внешней ссылки. Ссылка на проект IDE — это здорово, но она также должна быть здесь в виде текста, иначе она не засчитывается. Когда я смотрю на эту ссылку, я вижу ошибки, не связанные с тем, что вы спрашиваете, например export type X;
без определения и т. д. Можете ли вы убедиться, что присутствуют только те проблемы, о которых вы спрашиваете?
(см. предыдущий комментарий). Я также не понимаю, как этот пример отвечает на мои вопросы. Когда я смотрю на K extends InitArgs<M> ? K extends undefined ? 'id' : `${idFieldName}` : never;
, я растеряюсь. Если K extends InitArgs<M>
, то K
определенно не является undefined
(потому что InitArgs
— это интерфейс), поэтому 'id'
никогда не достигается. Зачем тогда ты это пишешь? Это не может быть минимальный воспроизводимый пример , потому что он не минимален, и это еще до того, как я доберусь до `${idFieldName}`
, чего до сих пор не понимаю. Не могли бы вы предоставить несколько примеров предполагаемых пар ввода-вывода для этого типа? Это зависит от modelName
, но как?
(см. предыдущие 2 комментария). Я думаю, было бы лучше, если бы вы просто написали очень простой пример, который показывает входные данные и предполагаемые выходные данные, а затем кто-то может написать для вас функцию типа, которая делает это, вместо того, чтобы пытаться отлаживать или понимать ваши неудачные попытки, которые могут быть скорее отвлечением, чем помощью к пониманию. Не могли бы вы отредактировать, чтобы сделать это?
ОК @jcalz; Я попробовал еще раз с еще более минимальным примером и постарался быть как можно более ясным. Надеюсь, это прояснит.
Суть минимально воспроизводимого примера заключается в том, что нам нужно иметь возможность скопировать и вставить его в наши собственные IDE и начать над ним работать. Моя IDE не знает ни о PrismaModelProps
, ни о множестве других вещей, поэтому мне приходится либо попытаться создать это самостоятельно, либо игнорировать это. Если я проигнорирую это и просто посмотрю на «если пользователь позвонит MyExtensions
» по номиналу, я получу что-то вроде эта ссылка на игровую площадку . Это достаточно близко к тому, что вы хотите послужить ответом? Если да, то я напишу ответ. Если нет, пожалуйста отредактируйте , чтобы создать настоящий минимально воспроизводимый пример, демонстрирующий то, чего мне не хватает.
Это выглядит примерно так, если предположить, что два разных args
({ modelName: string, idFieldName?: K }
и { [P in K]: boolean }
находятся в файле объявлений .d.ts
! (и если вы можете объяснить, какPropertyKey
работает на вашей игровой площадке, было бы здорово!)
Я опубликовал ответ, объясняющий; возможно, мы захотим отредактировать вопрос, чтобы очистить его. Он не должен выглядеть как серия правок (заинтересованные стороны могут просмотреть историю изменений, чтобы увидеть предыдущие материалы), и он не должен содержать фрагментов, о которых мы на самом деле не спрашиваем, если только они не являются истинным минимально воспроизводимым примером в сам вопрос (поэтому ссылка на дополнительный код является лишней). Я с удовольствием сделаю это сам, если вы не возражаете. Дайте мне знать.
Хм, теперь этот вопрос выглядит совсем по-другому. Что означает «Я пытаюсь создать определение типа, которое зависит от ввода пользователя»? На первый взгляд это невозможно. Какой «пользователь» вводит данные до времени компиляции? Это человек, пишущий код TypeScript?
(см. предыдущий комментарий) Чем больше я перечитываю этот вопрос, тем больше я сбиваюсь с толку по поводу того, что вы пытаетесь сделать. В чем разница между IdField
и IdFieldKey
? Мне действительно нужно увидеть пример использования этой вещи.
Чтобы обратиться к примеру MyExtension
в вопросе, не беспокоясь об отношениях между modelName
и idFieldName
, я бы написал такое объявление:
declare function MyExtension<K extends PropertyKey = "id">(
arg: { modelName: string, idFieldName?: K }
): {
MyExtensionsMethod(arg: { [P in K]: boolean }): Promise<void>;
}
Здесь функция общая в K
, тип idFieldName
. Этот тип ограничен до PropertyKey
(это всего лишь псевдоним string | number | symbol
, то есть типов, которые могут быть ключами для свойств) и который по умолчанию устанавливает на id
(значение по умолчанию произойдет только в том случае, если K
невозможно вывести, т.е. , когда для idFieldName
не передается аргумент.
Затем он возвращает объект с методом, принимающим аргумент типа {[P in K]: boolean}
, который аналогичен Record<K, boolean>
при использовании типа утилиты Record . Это простой отображаемый тип, который имеет свойство с ключом типа K
и значением типа boolean
(и это, вероятно, то, что вы имели в виду под {[K]: boolean}
).
Давайте проверим это:
const UserMethods1 = MyExtension({ modelName: 'foo', idFieldName: 'bar' });
const newValue1 = await UserMethods1.MyExtensionsMethod({ bar: true });
const UserMethod2 = MyExtension({ modelName: 'foo' });
const newValue2 = await UserMethod2.MyExtensionsMethod({ id: true });
Выглядит неплохо. Для UserMethods1.MyExtensionMethod
ожидается {bar: boolean}
, тогда как для UserMethod2.MyExtensionsMethod
ожидается {id: boolean}
.
Если вам нужно связать modelName
с idFieldName
, вы, вероятно, можете сделать это вдвойне общим, чтобы у вас было
declare function MyExtension<M extends ModelNames, K extends KeysFor<M> | "id" = "id">(
arg: { modelName: M, idFieldName?: K }
): {
MyExtensionsMethod(arg: { [P in K]: boolean }): Promise<void>;
}
где ModelNames
и KeysFor
определены правильно, но я буду считать, что это выходит за рамки, поскольку эту часть должно быть достаточно легко подключить.
Детская площадка, ссылка на код
1-е: потрясающая помощь, спасибо за работу! 2-й: ваш ответ работает так, как рекламируется, но не совсем так, потому что мне нужны экспортированные фактические типы. Переписал вопрос еще 1 (и, надеюсь, последний) раз.
🙃 Ну что ж, наверное, надо посмотреть, что по сути является новым вопросом, и написать другой ответ? Новая версия вашего вопроса теперь, похоже, требует... циклической зависимости модуля? И какой «пользователь» решает, каким должен быть `${idFieldName}`
? Я озадачен тем, как этот вопрос, кажется, вырвался из-под меня. Я думал, что понял, что вы делаете, и потратил время на написание ответа, который сейчас выглядит... бесполезным? Ну что ж.
Собственно, отметив это как правильное. Единственным дополнительным шагом, который мне понадобился, чтобы дойти до конца, был тип утилиты Parameters
.
@jonrsharpe, ты удалил тег
typescript-generics
. Это правда, я нигде здесь не использовал слово «дженерики», но разве оно не присуще тому, что я пытаюсь сделать?