Typescirpt mapped напечатал в проблеме заводской типизации редукторного редуктора - PullRequest
0 голосов
/ 12 января 2019

Я пытаюсь создать безопасную для типов функцию для создания редукторов на основе карты «обработчики действий». Идея состоит в том, чтобы иметь API, который будет выглядеть так:

export const Actions = {
    setToken: (token: string) => createAction(SET_TOKEN_TYPE, token),
    invalidateToken: () => createAction(INVALIDATE_TOKEN_TYPE),
    startLogin: () => createAction(START_LOGIN_TYPE)
};

export const reducer = createReducer<State, typeof Actions>(
    {
        [SET_TOKEN_TYPE]: ({ loginError, ...state }, action) => ({
            ...state,
            token: action.payload,
            loading: false
        }),
        [INVALIDATE_TOKEN_TYPE]: ({ token, ...state }) => state,
        [START_LOGIN_TYPE]: ({ loginError, ...state }) => ({
            ...state,
            loading: true
        })
    },
    {
        loading: false
    }
);

createReducer функция должна (без Typescript для ясности) выглядеть так:

function createReducer(handlers, initialState) {
    return (state = initialState, action) => {
        if (action.type in handlers) {
            return handlers[action.type](state, action);
        }
        return state;
    };
}

Я создал такую ​​типизированную функцию для обеспечения безопасности типов:

interface Action<T extends string> {
    type: T;
}
type ActionCreator<T extends string> = (...args: any) => Action<T>;
type ActionsCreators = {
    [creator: string]: ActionCreator<any>;
};
type ActionsUnion<Actions extends ActionsCreators> = ReturnType<
    Actions[keyof Actions]
>;
type ActionHandlers<ActionCreators extends ActionsCreators, State> = {
    [K in ReturnType<ActionCreators[keyof ActionCreators]>["type"]]: (
        state: State,
        action: ReturnType<ActionCreators[K]> 
    ) => State
};

    function createReducer<State, Actions extends ActionsCreators>(
    handlers: ActionHandlers<Actions, State>,
    initialState: State
) {
    return (
        state: State = initialState,
        action: ActionsUnion<Actions>
    ): State => {
        if (action.type in handlers) {
            // unfortunately action.type is here any :(
            return handlers[action.type](state, action); // here I have the error
        }
        return state;
    };
}

В handlers[action.type] у меня ошибка (с noImplicitAny: true)

Элемент неявно имеет тип 'any', потому что тип 'ActionHandlers' не имеет сигнатуры индекса.

Есть идеи, как набрать action.type внутри редуктора?

Вы можете найти весь пример в Суть

1 Ответ

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

Причина, по которой вы получаете ошибку: action.type, неявно напечатана как any, так как не было применено применимого типа.

В некоторый момент в цепочке вы используете any в качестве параметра типа для чего-то, что должно быть укоренено в string:

type ActionsCreators = {
    [creator: string]: ActionCreator<any>;
};

Если вы добавите здесь параметр типа, вы можете заменить any; тем не менее, вам нужно будет пройти весь путь вниз по линии.

См. Версию ниже, где было сделано это обновление. Мне пришлось переименовать некоторые из промежуточных типов в общие имена (T или P), так как мне было трудно сохранять типизацию прямым.

С дополнительным параметром типа <P> теперь у нас есть тип для следующего вместо неявного any:

const f = handlers[action.type];

здесь f становится ActionHandlers<P, T, State>[P]

export interface Action<T extends string> {
    type: T;
}

export interface ActionWithPayload<T extends string, P> extends Action<T> {
    payload: P;
}

export type ActionCreator<T extends string> = (...args: any) => Action<T>;

export type ActionsCreators<T extends string> = {
    [creator: string]: ActionCreator<T>;
};

export type ActionsUnion<P extends string, T extends ActionsCreators<P>> = ReturnType<T[keyof T]>;

export type ActionHandlers<P extends string, T extends ActionsCreators<P>, State> = {
    [K in ReturnType<T[keyof T]>["type"]]: (
        state: State,
        action: ReturnType<T[K]>
    ) => State
};

export function createReducer<P extends string, State, T extends ActionsCreators<P>>(
    handlers: ActionHandlers<P, T, State>,
    initialState: State
) {
    return (state: State = initialState, action: ActionsUnion<P, T>): State => {

        if (action.type in handlers) {
            const f = handlers[action.type];
            return f(state, action);
        }

        return state;
    };
}
...