Свойство, которое индексирует другое свойство в Typescript - PullRequest
0 голосов
/ 15 января 2019

У меня есть следующие типы:

interface CellsReducer {
    source: number;
    destination: number;
    plan: string;
    duration: number;
    test: []
}

interface BarReducer {
    baz: string;
}

interface AppState {
    cells: CellsReducer;
    bar: BarReducer;
}

Я хочу написать интерфейс со следующими объектами:

interface Props {
    store: keyof AppState;
    field: // AppState[store]
    data: // AppState[store][field]
}

Использование дженериков никуда меня не привело. fields заканчивается типом never в следующем примере:

type Stores<T> = keyof T;
type Fields<T> = keyof T[Stores<T>];
type Props<TState> = {
    state: Stores<TState>;
    field: Fields<TState>
}

Есть ли способ сделать это?

Ответы [ 2 ]

0 голосов
/ 15 января 2019

Вы имеете в виду:

interface Props<T, K extends keyof T, V extends keyof T[K]> {
    state: keyof T;
    field: T[K];
    data: T[K][V]
}

Использование:

const props: Props<AppState, 'cells', 'plan'> = { /* ... */ } ;
const props: Props<AppState, 'bar', 'baz'> = { /* ... */ } ;
0 голосов
/ 15 января 2019

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

type Props<TState, KStore extends keyof TState, KField extends keyof TState[KStore]> = {
    state: KStore;
    field: KField
    data: TState[KStore][KField]
}

let p: Props<AppState, "cells", "duration"> = {
  state: "cells",
  field: "duration",
  data: 1
}

Причина, по которой вы никогда не получаете, заключается в том, что когда компилятор пытается развернуть AppState[keyof AppState], он получит объединение CellsReducer | BarReducer. Поскольку доступны только общие члены объединения keyof (CellsReducer | BarReducer) - это never (ключи недоступны).

Дополнительные параметры фиксируют фактическое поле, поэтому, если KStore является строковым литералом, тип "cells" keyof AppState["cells"] будет ключами этого конкретного поля в состоянии приложения. KField работает аналогично, позволяя нам правильно набирать data.

Чтобы не указывать значения state и field дважды, вы можете написать вспомогательную функцию:

function propertyFactory<TState>() {
  return function <KStore extends keyof TState, KField extends keyof TState[KStore]>(o: Props<TState, KStore, KField>) {
    return o;
  }
}
let p = propertyFactory<AppState>()({
  state: "cells",
  field: "duration",
  data: 1
})
...