redux 减速器类型“从不"

问题描述:

我想使用 'useSelector' 来选择 rootStore 的正确状态,但无法正确获取状态.原因是 RootState 的 topic reducer 给了我类型 never

I want to use 'useSelector' to select proper state of rootStore, but can't get state properly. The reason is topic reducer of RootState gives me type never

export type RootState = ReturnType<typeof rootReducer>

如何正确获取主题类型?

整个代码看起来像

///topic.ts 
import { Action, ActionCreator } from 'redux';
import { CardData } from '@models/card';
import { TopicName } from 'src/models/topic';
import { ActionEnum } from './index';

//action
export enum TopicEnum {
  FETCH_TOPIC = "FETCH_TOPIC"
}
type TopicType = ActionEnum | TopicEnum;  

export interface TopicBasicState{
  topic: TopicName
  isLoading :boolean
  data: CardData[]
  isError: boolean 
}
export const initialState: TopicBasicState = {
  topic: 'all',
  isLoading: true,
  data: [] as CardData[], 
  isError: false
}

//action type
export interface FetchAction extends Action {
  type: typeof TopicEnum.FETCH_TOPIC
  url: string;
}
interface LoadingAction extends Action{
  type: typeof ActionEnum.FETCH_LOADING
}
interface SuccessAction extends Action{
  type: typeof ActionEnum.FETCH_SUCCESS
  payload: TopicBasicState
}
interface ErrorAction extends Action{
  type: typeof ActionEnum.FETCH_ERROR
}
//action creator
const fetch: ActionCreator<FetchAction> = (
  url
) => ({
  type: TopicEnum.FETCH_TOPIC,
  url, 
});
const load: ActionCreator<LoadingAction> = () => ({
  type: ActionEnum.FETCH_LOADING,
}); 
const success: ActionCreator<SuccessAction> = (_, payload) => ({
  type: ActionEnum.FETCH_SUCCESS,
  payload
})
const error: ActionCreator<ErrorAction> = () => ({
  type: ActionEnum.FETCH_ERROR
})

type TopicAction =
  | ReturnType<typeof fetch>
  | ReturnType<typeof load>
  | ReturnType<typeof success>
  | ReturnType<typeof error>

export const topicCreator = {
  fetch,
  load,
  success,
  error  
}
const topicReducer: (
  state: TopicBasicState,
  action: TopicAction
) => TopicBasicState = (
  state = initialState, action
) => {
  switch(action.type){
    case TopicEnum.FETCH_TOPIC: 
      return {
        ...state,
        isError: false
      }
    case ActionEnum.FETCH_SUCCESS: 
      return {
        ...state, 
        topic: action.payload?.topic,
        isLoading: false, 
        data: action.payload?.data
      }
    case ActionEnum.FETCH_LOADING: 
      return {
        ...state,
        isLoading: true,
      }
    case ActionEnum.FETCH_ERROR:
      return {
        ...state,
        isLoading: false,
        isError: true  
      }
    default:
      return state;
  } 
}

export type TopicState = ReturnType<typeof topicReducer>; 
export default topicReducer;

** store/index.ts **

** store/index.ts **

import React, { FC } from 'react';
import { Provider } from 'react-redux'; 
import { 
  combineReducers,
  createStore,
  applyMiddleware,
  compose
} from 'redux';
import createSagaMiddleware from 'redux-saga';
import topic from './topic';
import { rootSaga } from '@sagas/index'; 

export enum ActionEnum {
  FETCH_LOADING = "FETCH_LOADING",
  FETCH_SUCCESS = "FETCH_SUCCESS", 
  FETCH_ERROR =  "FETCH_ERROR"
}
export type DefaultAction = 
  | ActionEnum.FETCH_SUCCESS
  | ActionEnum.FETCH_LOADING 
  | ActionEnum.FETCH_ERROR

// root reducer
export const rootReducer = combineReducers({
  topic
});
// root store type 
export type RootState = ReturnType<typeof rootReducer>


...
    const { data, isLoading, id } = useSelector((state: RootState) => state.topic);
    const dispatch= useDispatch();
    const { fetch}  = topicCreator; 
    useEffect(() => {
      console.log('path: ', pathname);
      const [ include, exclude ] = pathExtractor(pathname); 
      dispatch(fetch()); 
    },[])
    const handleClick : (data: CardData ) => void = (data) => {
      setModal({
        type: 'OPEN', 
        payload: {
          data,
          visible: true
        }
      });
    }
    return(
      <>
        <section>
          <Carousel>
            <Slide url={Netflix} />
            <Slide url={Adobe} />
            <Slide url={NetflixPhone} />    
          </Carousel>
        </section>
        <section>
          {isModal.visible && <Modal data={isModal.data} />}
        </section>
        <section>
          <RoomContainer>
            {
              isLoading ?
                <ProgressBar/>
                :
                data.map((value) => { // <-- this variable data cause error because typescript cannot infer data as array but type never 
                  return <Card
                    data={value} 
                    key={value.id} 
                    handleClick={() => handleClick(value)}
                   /> 
                })
            }
          </RoomContainer>
        </section>
      </>
    )
}); 



解决方案

您可以使用 redux 库中的 Reducer 类型来输入您的减速器.将 topicReducer 的类型更改为以下内容:

Solution

You can use the Reducer type from the redux library to type your reducers. Change the typing of topicReducer to the following:

import { Reducer } from 'redux';

const topicReducer: Reducer<TopicBasicState,TopicAction> = (
    state = initialState, action
) => {

这应该为您提供以下 RootState 类型:

That should give you the following typing for RootState:

type RootState = {
    readonly [$CombinedState]?: undefined;
} & {
    topic: TopicBasicState;
}

来源: