通用输入给定类型的商店
Generic typing a store of a given type
我正在创建一个商店应该能够扩展的自定义 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>'
}
我不确定 "the whole thing falls apart" 到底是什么意思(通常 minimum reproducible example which shows what you mean is more helpful than words anyway), but presuming that you'd like to keep track of each key of state
and the data
type of its corresponding property, I'd suggest using a mapped type 代表 StoreState<T>
对象类型 T
表示这些 key/data 关系。
type StoreState<T> = { [K in keyof T]: Partial<LoadableState<T[K]>> };
然后,假设 BaseStore<T>
有一个构造函数设置它的 state
属性:
class BaseStore<T> {
constructor(public state: StoreState<T>) { }
}
您应该能够使用您的对象构建一个:
const bs = new BaseStore({
something: {
data: {
todos: [1, 2, 3] // ♂️
},
loading: false,
error: {}
},
somethingElse: {
data: {
items: ["a", "b", "c"] // ♀️
},
loading: false,
error: {}
}
});
并且您会看到构造对象的类型已被推断(通过 inference from mapped types),如下所示:
/*
const bs: BaseStore<{
something: {
todos: number[];
};
somethingElse: {
items: string[];
};
}>*/
所以这里推断的 T
是 {something: {todos: number[]}; somethingElse: {items: string[]};}
,我想,你想要的。
希望对您有所帮助;祝你好运!
我正在创建一个商店应该能够扩展的自定义 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>'
}
我不确定 "the whole thing falls apart" 到底是什么意思(通常 minimum reproducible example which shows what you mean is more helpful than words anyway), but presuming that you'd like to keep track of each key of state
and the data
type of its corresponding property, I'd suggest using a mapped type 代表 StoreState<T>
对象类型 T
表示这些 key/data 关系。
type StoreState<T> = { [K in keyof T]: Partial<LoadableState<T[K]>> };
然后,假设 BaseStore<T>
有一个构造函数设置它的 state
属性:
class BaseStore<T> {
constructor(public state: StoreState<T>) { }
}
您应该能够使用您的对象构建一个:
const bs = new BaseStore({
something: {
data: {
todos: [1, 2, 3] // ♂️
},
loading: false,
error: {}
},
somethingElse: {
data: {
items: ["a", "b", "c"] // ♀️
},
loading: false,
error: {}
}
});
并且您会看到构造对象的类型已被推断(通过 inference from mapped types),如下所示:
/*
const bs: BaseStore<{
something: {
todos: number[];
};
somethingElse: {
items: string[];
};
}>*/
所以这里推断的 T
是 {something: {todos: number[]}; somethingElse: {items: string[]};}
,我想,你想要的。
希望对您有所帮助;祝你好运!