通用键入给定类型的商店

时间:2019-10-09 00:44:42

标签: typescript typescript-generics

我正在创建一个自定义的Redux存储,该存储应该可以扩展。我的目标是这样的:

class BaseStore<T> { 
    public state$: Observable<StoreState<T>>;
    ...
    // other helpers and the like
}

StoreState如下:

export type ErrorState = Record<string, any>;
export type LoadableState<T> = {
  data: T;
  loading: boolean;
  error: ErrorState;
};
export type StoreState<T> = Record<string, Partial<LoadableState<T>>>;

但是我不能正确输入。可以,但是如果您想在商店中有两个这样的商品:

{ // this object represents the store which consists of LoadableStates 
      something: { // I want to type the "slices" in the store
        data: { // and also to be able to type what is in data
          todos: [] 
        },
        loading: false,
        error: {}
      },
      somethingElse: {
        data: {
          items: []
        },
        loading: false,
        error: {}
      }
}

整个事情都崩溃了;意思是,我不能继续输入类似的内容:

interface SomethingState {
  email?: string;
  username?: string;
}

interface SomethingElseState {
  todos?: Array<Record<string,string>>;
  saveSuccess?: Record<string, string | boolean>;
}

interface MyStoreState {
  something?: SomethingState;
  somethingElse?: SomethingElseState;
}

class MyStore extends BaseStore<MyStoreState> {
  ...
  public getEmail(): Observable<string> {
    return this.state$.pipe(map(state => state.something.data.email))
  }
}

有什么想法吗?不能说我已经使用了泛型,所以这将是一次很棒的学习经历。

编辑:映射的类型似乎有缺点。该商店的update()函数无法正常工作:

// my implementation for updating the store

public set(nextState: StoreState<T>): void {
  this.state$.next(nextState);
}

public update(storeSlice: string, partialState: Partial<LoadableState<T>>): void {
    if (!this.stateValue[storeSlice]) {
      throw Error('Path does not exist in the store');
    }

    const currentState = this.stateValue[storeSlice];
    const nextState = merge(currentState, partialState); // lodash's merge

    this.set({ [storeSlice]: { ...nextState } }); // ERROR: Argument of type '{ [x: string]: any; }' is not assignable to parameter of type 'StoreState<T>'
  }

1 个答案:

答案 0 :(得分:2)

我不确定“整个事情都崩溃了”的含义是什么(通常是minimum reproducible example,它表明您的意思总比说单词更有用),但假设您想跟踪state的每个键及其相应属性的data类型,我建议使用mapped type来表示StoreState<T>,以对象类型{{1} },代表这些键/数据关系。

T

然后,假设type StoreState<T> = { [K in keyof T]: Partial<LoadableState<T[K]>> }; 具有用于设置其BaseStore<T>属性的构造函数:

state

您应该能够使用您的对象构造一个对象

class BaseStore<T> {
    constructor(public state: StoreState<T>) { }
}

,您将看到已构造对象的类型(通过inference from mapped types推断)是这样的:

const bs = new BaseStore({
    something: {
        data: {
            todos: [1, 2, 3] // ?‍♂️
        },
        loading: false,
        error: {}
    },
    somethingElse: {
        data: {
            items: ["a", "b", "c"] // ?‍♀️
        },
        loading: false,
        error: {}
    }
});

因此,我认为您想要的是/* const bs: BaseStore<{ something: { todos: number[]; }; somethingElse: { items: string[]; }; }>*/ 。{p>

希望有所帮助;祝好运!

Link to code