Как проверить, является ли generic K частью generic M | Typescript Generic | Redux

#typescript #generics #redux #typescript-generics

#typescript #дженерики #redux #typescript-дженерики

Вопрос:

Привет

Я пытаюсь уменьшить Bolilderplate в своем приложении react. Я использую состояние Redux и решил, какую структуру Redux использовать (см. Контекст). Теперь я хочу сделать его меньше. Для этого я создал универсальный ActionCreator . Моя проблема состоит в том, чтобы найти желаемый общий синтаксис.

Создатель универсального действия

Это мой общий подход к генератору действий.

 export class genericAction<M, K> extends Action {
    public reducer = (state: M) => ({ ...state, ...this.payload });

    constructor(public readonly type: string | undefined, public payload: K) {
        super();
    }
}
  

На данный момент возможно только объединение состояний, но я стремлюсь к необязательному параметру, который является функцией. Затем эта функция позволит мне выполнять пользовательские слияния состояний (например, state.count полезная нагрузка). Но это другая тема.

Проблема

Моя проблема в том, что я мог бы использовать { fubar: number} вместо { loading: boolean } . Очевидно, что fubar не является частью моей ProductStateModel.

 const someAction = new genericAction<ProductStateModel, { loading: boolean }>(ProductListActionTypes.REQUEST_START, { loading: true });
  

Вопрос

Я хочу использовать только атрибуты ProductStateModel в качестве типа для общего K. Псевдокод:

 genericAction<M, K is type of M> extends Action
  

Общая идея состоит в том, чтобы создать действие со следующими типами и параметрами:

  • StateModel
  • ProductListActionType
  • Полезная нагрузка

Возможно ли это? Или есть общее / альтернативное решение? Я согласен с изменениями в StateModel, если это необходимо. Я новичок в redux и хочу сделать это правильно. Я попытался получить Pick<T, K> в общем методе, но к настоящему времени я не уверен, возможно ли это вообще так, как мне нужно, или, может быть, я должен просто пойти спать: D

Спасибо за любые подсказки и помощь


Контекст

Это моя отправная точка

 // State
export interface State {}

export interface ProductStateModel extends State {
    products: Array<ProductDTO>;
    loading: boolean;
    error: string;
}


// Product Actions
export abstract class Action {
    public readonly type: string | undefined;
    protected constructor() {
        return Object.assign({}, this);
    }
    abstract reducer(state: State): State;
}

// First of 3 Actions. They are all very similar. The generic function should replace all of them.
export class ProductListRequest extends Action {
    public readonly type = ProductListActionTypes.REQUEST_START;
    public reducer = (state: ProductStateModel) => ({ ...state, loading: this.payload.loading });

    constructor(public payload: Pick<ProductStateModel, 'loading'>) {
        super();
    }
}

// Reducer
export const productListReducer = (state: ProductStateModel = defaultProductState, action: Action) => {
    return isNil(action.reducer) ? action.reducer(state) : state;
};
  

Ответ №1:

 export class genericAction<M> extends Action {
    public reducer = (state: M) => ({ ...state, ...this.payload });

    constructor(public readonly type: string | undefined, public payload: Partial<M>) {
        super();
    }
}
  

Я удалил второй тип и добавил частичный к общему типу полезной нагрузки. В моей голове я хотел ограничить разрешенные атрибуты, как я делал в оригинальном подходе, Pick<ProductStateModel, 'loading'> но потом я понял, что это не нужно. Новый вызов универсального класса — это единственное место, где я определяю, какие атрибуты необходимы для этого действия, и это нормально.