我有以下类型:

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>
}

有办法吗?

最佳答案

对于路径中的每个属性,需要不同的类型参数。这允许编译器对指定的特定字段进行推理:

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]时,它将得到一个unionCellsReducer | BarReducer。因为只有联合的普通成员是可访问的keyof (CellsReducer | BarReducer)never(没有可访问的键)。
额外的参数捕获实际字段,因此如果KStore字符串字符串类型"cells" keyof AppState["cells"]将是App状态中特定字段的键。KField的工作原理与此类似,允许我们正确键入data
为了避免两次指定statefield值,可以编写一个helper函数:
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
})

09-25 22:04