#javascript #reactjs #typescript #redux #react-redux
#javascript #reactjs #typescript #сокращение #реагировать-redux
Вопрос:
Я создаю replace reducer на основе документов, но я использую typescript, а в документах redux нет ни одного примера того, как правильно вводить replace reducer, я пытаюсь, но моя попытка переопределяет useStore
игнорирование дженериков, теряющих типизацию getState()
.
Компонент:
interface State {
sim: number;
nao: string;
}
function Experimente() {
const store: InjectableStore = useStore<State>();
const dispatch = useDispatch();
store.getState(); // any
}
конфигурация redux:
export const initStore = () => {
const store: StoreCreator amp; WithAsync = createStore(
createReducer(),
composeEnhancers(
applyMiddleware(thunk),
),
);
store.asyncReducers = {};
store.injectPageReducer = (pageReducer) => {
store.asyncReducers['page'] = pageReducer;
store.replaceReducer(createReducer(store.asyncReducers));
}
return store;
}
function createReducer(asyncReducers?) {
return combineReducers({
...staticReducers,
...asyncReducers
})
}
определение типа:
export interface WithAsync {
injectPageReducer?: (asyncReducer: Reducer) => void;
asyncReducers?: IGenericObject<any>;
replaceReducer?: typeof combineReducers;
}
export type InjectableStore = ReturnType<typeof useStore> amp; {
injectPageReducer?: WithAsync['injectPageReducer']
}
Как я могу ввести replaceReducer
стратегию, сохраняя useStore
тип?
Ответ №1:
Проблема в том, что ваш тип InjectableStore
не получает никакой информации о вашем магазине. Тип ReturnType<typeof useStore>
не будет применять ваш конкретный тип состояния, потому useStore
что является общим, но вы не предоставили ему общие аргументы, поэтому вместо этого он использует свои собственные значения по умолчанию. Подпись useState
:
export function useStore<S = RootStateOrAny, A extends Action = AnyAction>(): Store<S, A>;
Так ReturnType<typeof useStore>
становится Store<RootStateOrAny, AnyAction>
.
Store
, useStore
, Reducer
, и т.д. — это все обобщения, которые зависят от двух значений: S
формы состояния и A
типа разрешенных действий. A
может быть необязательным и по умолчанию разрешать любой стандарт Action
. Но если S
это не предусмотрено, то эти типы ничего не знают о вашем состоянии.
В принципе, каждый раз, когда вы используете тип, который был импортирован из «redux» или «react-redux», вам нужно будет предоставить ему S
аргумент. Вы можете создавать свои пользовательские типы, например InjectableStore
, быть дженериками, которые принимают форму хранилища и передают ее вниз, или вы можете заставить их всегда устанавливать хранилище для вашего интерфейса Store
. Второй подход проще, поэтому давайте сделаем это.
Вы не можете использовать ReturnType<typeof useStore<S, A>>
, потому что это недопустимый синтаксис, но это нормально, потому что мы видели в подписи функции выше, что возвращаемый тип Store<S, A>
, который в вашем случае Store<State>
Необходимость применения типов становится глубже, потому что свойство {injectPageReducer: (asyncReducer: Reducer) => void;}
должно знать, что редуктор не просто Reducer<any>
(что подразумевается), но специфичен для вашего состояния (или части вашего состояния, в данном случае).
Часть , где это становится сложным , заключается в том , что кажется , что когда вы вызываете injectPageReducer
функцию , которая , в свою очередь , вызывается store.replaceReducer
, вы меняете форму своего состояния с объекта с ключами sim
и nao
на объект с ключами sim
, nao
, и page
. Это плохо, и мы не хотим этого делать. Подпись replaceReducer
функции фактически говорит нам, что заменяющий редуктор должен работать с той же формой S
состояния, что и исходная:
export interface Store<S = any, A extends Action = AnyAction> {
/** ... */
replaceReducer(nextReducer: Reducer<S, A>): void
/** ... */
}
Быстрое решение — page
включить необязательное свойство State
, но это приводит к ошибкам в дальнейшем. Лучше сделать это обязательным и установить значение по умолчанию.
На самом деле мне не нравится эта настройка asyncReducers
, поскольку она установлена как свойство Store
, а не как временная переменная, переданная replaceReducer
, но я вижу, что она взята непосредственно из примера redux docs, поэтому я с этим справлюсь.
Я избавился от WithAsync
него и превратил его в один InjectableStore
интерфейс, который обладает всеми свойствами Store
плюс (возможно) ваших двух дополнений. Поскольку дополнения являются необязательными, вам нужно будет проверить, что injectPageReducer
существует, прежде чем вызывать его.
export type InjectableStore = Store<State, MyAction> amp; {
injectPageReducer?: (pageReducer: Reducer<PageState, MyAction>) => void;
asyncReducers?: Partial<ReducersMapObject<State, MyAction>>;
};