Typescript: получение правильного типа логического вывода при использовании условно сопоставленных ключей - PullRequest
0 голосов
/ 06 ноября 2018

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

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

Я создал пример для демонстрации ( представление на машинописной площадке ):

interface TraversableType{
  name: string;
}

interface TypeOne extends TraversableType{
  typeNotTraversable: string;
  typeTwo: TypeTwo;
  typeThree: TypeThree;
}

interface TypeTwo extends TraversableType{
  typeTwoNotTraversable: string;
  typeOne: TypeOne;
  typeThree: TypeThree;
}

interface TypeThree extends TraversableType{
  typeThreeProp: string;
}


type TraversablePropNames<T> = { [K in keyof T]: T[K] extends TraversableType ? K : never }[keyof T];


//given start object, return 
function indexAny<T extends TraversableType, K extends keyof T>(startObj: T, key: K): T[K] {
  return startObj[key]; 
}

//same thing, but with only "traversable" keys allow
function indexTraverseOnly<T extends TraversableType, K extends TraversablePropNames<T>>(startObj: T, key: K): T[K] {
  return startObj[key]; 
}

let t2: TypeTwo;

type keyType = keyof TypeTwo;                  // "typeTwoNotTraversable" | "typeOne" | "typeThree" | "name"
type keyType2 = TraversablePropNames<TypeTwo>; // "typeOne" | "typeThree"

let r1 = indexAny(t2, 'typeOne');              // TypeOne
let r2 = indexTraverseOnly(t2, 'typeOne');     // TypeOne | TypeThree

Обратите внимание, что при использовании K extends keyof T функция indexAny может вывести правильный тип возврата.

Однако, когда я пытаюсь использовать условный сопоставленный тип TraversablePropNames для определения ключа, он не знает, является ли он TypeOne или TypeTwo.

Есть ли какой-нибудь способ написать функцию, чтобы она ТОЛЬКО позволяла использовать клавиши TraversableType И правильно выводила тип?

UPDATE:

Интересно ... кажется, что он работает на 1 свойство, если я обертываю метод в общий класс и передаю экземпляр (а не в качестве первого параметра). Тем не менее, кажется, что он работает только для одного обхода ... затем снова происходит сбой:

class xyz<T>{
  private traversable: T;
  constructor(traversable: T) {
    this.traversable = traversable;
  }

   indexTraverseOnly<K extends TraversablePropNames<T>>(key: K): T[K] {
    return this.traversable[key]; 
  }

  indexTraverseTwice<K extends TraversablePropNames<T>, K2 extends TraversablePropNames<T[K]>>(key: K, key2: K2): T[K][K2] {
    return this.traversable[key][key2]; 
  }
}

let t2: TypeTwo;
let r3Obj = new xyz(t2);
let r3 = r3Obj.indexTraverseOnly('typeOne'); // TypeOne (WORKS!)

let r4 = r3Obj.indexTraverseTwice('typeOne', 'typeThree'); // TypeTwo | TypeThree

1 Ответ

0 голосов
/ 06 ноября 2018

Поскольку T отображается в двух позициях для вызова функции (как автономно, так и в K), в основном есть две позиции, которые могут определять тип T. Теперь, как правило, машинопись может обрабатывать такие случаи для простых ситуаций, но использование сопоставленного типа заставит его отказаться от вывода типа K.

Существует несколько возможных решений, одно из которых вы обнаружили, то есть сначала исправить T. Вы сделали это с классом, вы также можете сделать это с функцией, которая возвращает функцию:

function indexTraverseOnly2<T extends TraversableType>(startObj: T) {
  return function <K extends TraversablePropNames<T>>(key: K): T[K] {
    return startObj[key];
  }
}

let r3 = indexTraverseOnly2(t2)('typeThree');     // TypeThree

Другим решением было бы указать ограничение, что K должен вводить в T значение TraversableType другим способом, можно сказать, что T должен расширять Record<K, TraversableType>, означая, что ключ K должен иметь тип TraversableType независимо от любых других свойств.

function indexTraverseOnly<T extends Record<K, TraversableType>, K extends keyof any>(startObj: T, key: K): T[K] {
  return startObj[key]; 
}

Редактировать

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

function indexTraverseOnly<T extends Record<K, TraversableType & Record<K2,TraversableType& Record<K3,TraversableType>>>, K extends keyof any, K2 extends keyof any, K3 extends keyof any>(startObj: T, key: K, key2:K2, key3:K3): T[K][K2][K3]
function indexTraverseOnly<T extends Record<K, TraversableType & Record<K2,TraversableType>>, K extends keyof any, K2 extends keyof any>(startObj: T, key: K, key2:K2): T[K][K2] 
function indexTraverseOnly<T extends Record<K, TraversableType>, K extends keyof any>(startObj: T, key: K): T[K]
function indexTraverseOnly(startObj: any, ...key: string[]): any {
  return null; 
}

let t2: TypeTwo;

let r1 = indexTraverseOnly(t2, 'typeOne');     // TypeOne
let r2 = indexTraverseOnly(t2, 'typeOne', 'typeTwo'); // TypeTwo
let r3 = indexTraverseOnly(t2, 'typeOne', 'typeTwo', 'typeThree'); // TypeThree
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...