Как в этом случае получить возвращаемое значение безопасной для типа функции?

В приведенном ниже примере кода я бы хотел, чтобы моя 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 спасибо за ваш комментарий. Я отредактировал пример кода под исходным сообщением.

msbb 05.04.2024 19:46

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

msbb 05.04.2024 19:56

Извините, произошла еще одна опечатка. зафиксированный.

msbb 05.04.2024 19:58

Соответствует ли такой подход вашим потребностям? Если да, то я напишу ответ с объяснением (но, пожалуйста, отредактируйте, чтобы изменить нетрадиционное K на традиционное U; см. комментарий выше). Если нет, то что мне не хватает?

jcalz 05.04.2024 20:01

Спасибо, я отредактировал исходное сообщение. Когда я копирую ваш подход на игровую площадку TS, предложения .test2 и .test4 работают. Однако, когда я пытаюсь запустить там код (также пробовал в своей IDE), я получаю, что sap не определен или sap не является функцией. Я полагаю, это из-за declare? Предполагается, что он будет преобразован в реальную функцию Javascript.

msbb 05.04.2024 20:09

Вы не спрашиваете о типографиях? Вы, конечно, можете реализовать эту функцию так, как захотите, и использовать as any as, как вы это делаете сейчас. Часть JS здесь не совсем уместна, если только вы не спрашиваете: «Как я могу убедить компилятор, что реализация соответствует сигнатуре вызова», но вы уже используете утверждения типа, поэтому я в замешательстве.

jcalz 05.04.2024 20:12

см. предыдущий комментарий. Я имею в виду, не стесняйтесь делать это, но я не уверен, почему это должно быть частью вопроса или ответа как такового.

jcalz 05.04.2024 20:14

Я спрашиваю, как я могу создать функцию, которая возвращает значения переданных корней в виде массива строго типизированным способом, чтобы моя IDE выдавала правильные предложения при доступе к rootValues ​​по индексу X. В моем исходном коде возврат ценности были неоднозначными. Извините, мой уровень навыков таков, что я, кажется, не могу сформулировать это лучше. Я очень благодарен за вашу помощь здесь.

msbb 05.04.2024 20:17

@jcalz, спасибо, последняя ссылка — это именно то, что мне нужно! Теперь мне нужно как-то понять, как и почему это работает хаха

msbb 05.04.2024 20:19

Как я уже сказал, я бы написал ответ, объясняющий. Я не буду заморачиваться с реализацией, поскольку она у вас уже есть и это не ваша проблема.

jcalz 05.04.2024 20:35
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
1
10
78
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Ваша функция 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 не хватает необходимой выразительности, чтобы даже представить ее, см. Сопоставление значения, типизированного кортежем, с другим значением, типизированным кортежем, без приведения.

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

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

Ты удивительный. Огромное спасибо за вашу огромную помощь и подробное объяснение!

msbb 06.04.2024 00:12

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