В приведенном ниже примере кода я бы хотел, чтобы моя IDE принимала mySap.rootValues[0].test2
и mySap.rootValues[1].test4
как действительные. Сейчас это не так, потому что rootValues[0]
и rootValues[1]
относятся к типу TestRootOutput | TestRootOutput2
.
type RootFunction<T, U> = (input: T) => U;
type Root<T, U> = {
value: U;
};
const root = <T, U>(rootFn: RootFunction<T, U>): ((initialValue: T) => Root<T, U>) => {
return initialInput => {
let _value: U = rootFn(initialInput);
return {
get value() {
return _value;
},
};
};
};
interface TestRootInput {
test: string;
}
interface TestRootOutput {
test2: string;
}
interface TestRootInput2 {
test3: string;
}
interface TestRootOutput2 {
test4: string;
}
const testRoot = root<TestRootInput, TestRootOutput>(input => {
return {test2: input.test};
});
const testRoot2 = root<TestRootInput2, TestRootOutput2>(input => {
return {test4: input.test3};
});
type BranchSap = {roots: Root<any, any>[]};
const sap = <T extends BranchSap>(sap: T) => {
const roots = sap.roots;
return {
rootValues: roots.map((root) => root.value as any as T['roots'][number]['value']),
};
};
const mySap = sap({roots: [testRoot({test: 'hi'}), testRoot2({test3: 'hi2'})]});
// should be valid
console.info(mySap.rootValues[0].test2)
// should be valid
console.info(mySap.rootValues[1].test4)
@jcalz еще раз спасибо, я новичок в том, чтобы задавать здесь вопросы. Я отредактировал исходное сообщение. Я надеюсь, что таким образом это будет объяснено лучше, и вы сможете легко скопировать его в свою собственную IDE. Я отредактировал исходный код.
Извините, произошла еще одна опечатка. зафиксированный.
Соответствует ли такой подход вашим потребностям? Если да, то я напишу ответ с объяснением (но, пожалуйста, отредактируйте, чтобы изменить нетрадиционное K
на традиционное U
; см. комментарий выше). Если нет, то что мне не хватает?
Спасибо, я отредактировал исходное сообщение. Когда я копирую ваш подход на игровую площадку TS, предложения .test2
и .test4
работают. Однако, когда я пытаюсь запустить там код (также пробовал в своей IDE), я получаю, что sap не определен или sap не является функцией. Я полагаю, это из-за declare
? Предполагается, что он будет преобразован в реальную функцию Javascript.
Вы не спрашиваете о типографиях? Вы, конечно, можете реализовать эту функцию так, как захотите, и использовать as any as
, как вы это делаете сейчас. Часть JS здесь не совсем уместна, если только вы не спрашиваете: «Как я могу убедить компилятор, что реализация соответствует сигнатуре вызова», но вы уже используете утверждения типа, поэтому я в замешательстве.
см. предыдущий комментарий. Я имею в виду, не стесняйтесь делать это, но я не уверен, почему это должно быть частью вопроса или ответа как такового.
Я спрашиваю, как я могу создать функцию, которая возвращает значения переданных корней в виде массива строго типизированным способом, чтобы моя IDE выдавала правильные предложения при доступе к rootValues по индексу X. В моем исходном коде возврат ценности были неоднозначными. Извините, мой уровень навыков таков, что я, кажется, не могу сформулировать это лучше. Я очень благодарен за вашу помощь здесь.
@jcalz, спасибо, последняя ссылка — это именно то, что мне нужно! Теперь мне нужно как-то понять, как и почему это работает хаха
Как я уже сказал, я бы написал ответ, объясняющий. Я не буду заморачиваться с реализацией, поскольку она у вас уже есть и это не ваша проблема.
Ваша функция sap()
имеет возвращаемый тип {rootValues: Array<T['roots'][number]['value']>}
. Но T['roots'][number]
— это объединение всех типов элементов массива roots
, при этом совершенно не отслеживается, какие значения находятся в массиве, а Массив представляет собой неупорядоченный однородный тип массива и не кодирует, какие типы могут найти по какому индексу. Итак, вы выбросили информацию о длине и положении элементов в T['roots']
.
Вместо этого вам нужно, чтобы rootValues
был типом кортежа , который зависит от T['roots']
. Сначала мы хотим убедиться, что T['roots']
, скорее всего, будет выведен как кортеж. Для этого мы можем создать T
параметр константного типа:
const sap = <const T extends BranchSap>(sap: T): { ⋯ } => ⋯;
Затем мы хотим взять T['roots']
и сопоставить его с новым типом кортежа . То есть мы создаем сопоставленный тип, чтобы превратить каждый элемент с индексом I
в T['roots']
и превратить его в T['roots'][I]['value']
:
type RootVal<T extends Root<any, any>[]> =
{ [I in keyof T]: T[I]['value'] };
const sap = <const T extends BranchSap>(
sap: T
): { rootValues: RootVal<T['roots']> } => ⋯;
Давайте проверим это с точки зрения вызывающего абонента:
const mySap = sap({
roots: [
testRoot({ test: 'hi' }),
testRoot2({ test3: 'hi2' })
]
});
Если вы проверите с помощью IntelliSense, вы увидите, что T
выводится как
{ readonly roots: [
Root<TestRootInput, TestRootOutput>,
Root<TestRootInput2, TestRootOutput2>
];
}
из-за параметра типа const
. И поэтому вывод имеет тип
{ rootValues: [TestRootOutput, TestRootOutput2]; }
потому что это то, что вы получаете, когда сопоставляете входной кортеж с помощью RootVal
. И теперь mySap
ведет себя именно так, как вы хотите:
mySap.rootValues[0].test2.toUpperCase(); // okay
mySap.rootValues[1].test4.toUpperCase(); // okay
Обратите внимание, что это имеет мало общего с реализацией sap()
. В большинстве случаев вам нужно будет просто использовать утверждение типа , чтобы убедить компилятор, что реализация удовлетворяет сигнатуре вызова:
const sap = <const T extends BranchSap>(
sap: T
): { rootValues: RootVal<T['roots']> } => {
const roots = sap.roots;
return {
rootValues: roots.map((root) => root.value),
} as any; // <-- assert here or wherever you want
};
Это связано с тем, что типизация TypeScript для метода массива map() не создает тип кортежа. На самом деле невозможно заставить TypeScript делать это автоматически, поскольку системе типов TypeScript не хватает необходимой выразительности, чтобы даже представить ее, см. Сопоставление значения, типизированного кортежем, с другим значением, типизированным кортежем, без приведения.
На практике большинство общих функций не могут быть проверены компилятором как безопасные, поскольку у него нет возможности анализировать все, кроме нескольких конкретных операций, таких как индексирование. Поэтому вам обычно понадобится утверждение типа или что-то подобное.
Детская площадка, ссылка на код
Ты удивительный. Огромное спасибо за вашу огромную помощь и подробное объяснение!
@jcalz спасибо за ваш комментарий. Я отредактировал пример кода под исходным сообщением.