正确的TypeScript类型以进行批量调度吗?

时间:2018-10-24 20:45:09

标签: reactjs typescript redux redux-thunk

我有一个异步的redux动作,所以我正在使用thunk中间件。

我对某个组件具有mapStateToPropsmapDispatchToPropsconnect函数,如下所示:

const mapStateToProps = (store: IApplicationState) => {
  return {
    loading: store.products.loading,
    products: store.products.products
  };
};
const mapDispatchToProps = (dispatch: any) => {
  return {
    getAllProducts: () => dispatch(getAllProducts())
  };
};
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ProductsPage);

这一切都可行,但是我想知道是否可以替换any中的dispatch参数上的mapDispatchToProps类型吗?

我尝试了ThunkDispatch<IApplicationState, void, Action>,但在connect函数上遇到以下TypeScript错误:

Argument of type 'typeof ProductsPage' is not assignable to parameter of type 'ComponentType<Matching<{ loading: boolean; products: IProduct[]; } & { getAllProducts: () => Promise<void>; }, IProps>>'.
  Type 'typeof ProductsPage' is not assignable to type 'ComponentClass<Matching<{ loading: boolean; products: IProduct[]; } & { getAllProducts: () => Promise<void>; }, IProps>, any>'.
    Types of property 'getDerivedStateFromProps' are incompatible.
      Type '(props: IProps, state: IState) => { products: IProduct[]; search: string; }' is not assignable to type 'GetDerivedStateFromProps<Matching<{ loading: boolean; products: IProduct[]; } & { getAllProducts: () => Promise<void>; }, IProps>, any>'.
        Types of parameters 'props' and 'nextProps' are incompatible.
          Type 'Readonly<Matching<{ loading: boolean; products: IProduct[]; } & { getAllProducts: () => Promise<void>; }, IProps>>' is not assignable to type 'IProps'.
            Types of property 'getAllProducts' are incompatible.
              Type '() => Promise<void>' is not assignable to type '() => (dispatch: Dispatch<AnyAction>) => Promise<void>'.
                Type 'Promise<void>' is not assignable to type '(dispatch: Dispatch<AnyAction>) => Promise<void>'.
                  Type 'Promise<void>' provides no match for the signature '(dispatch: Dispatch<AnyAction>): Promise<void>'.

enter image description here

是否可以在any中的调度参数上替换mapDispatchToProps类型?

5 个答案:

答案 0 :(得分:2)

此设置对我来说效果很好:

// store.ts
//...
export type TAppState = ReturnType<typeof rootReducer>;
export type TDispatch = ThunkDispatch<TAppState, void, AnyAction>;
export type TStore = Store<TAppState, AnyAction> & { dispatch: TDispatch };
export type TGetState = () => TAppState;
//...
const store: TStore = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(...middleware), ...enhancers)
);
export default store;

设置中的rootReducer如下const rootReducer = createRootReducer(history);

// createRootReducer.ts
import { combineReducers, Reducer, AnyAction } from 'redux';
import { History } from 'history';
import {
  connectRouter,
  RouterState,
  LocationChangeAction,
} from 'connected-react-router';
// ... here imports of reducers
type TAction = AnyAction & LocationChangeAction<any>;

export type TRootReducer = Reducer<
  {
    // ... here types of the reducer data slices
    router: RouterState;
  },
  TAction
>;
const createRootReducer = (history: History): TRootReducer =>
  combineReducers({
    // ... here actual inserting imported reducers
    router: connectRouter(history),
  });

export default createRootReducer;

然后在连接的组件中

import { connect } from 'react-redux';
import Add, { IProps } from './Add'; // Component
import { TDispatch, TAppState } from '../../store';

type TStateProps = Pick<
  IProps,
  'title' | 'data' | 'loading'
>;
const mapStateToProps = (
  state: TAppState,
): TStateProps => {
    // ... here you have typed state :)
    // and return the TStateProps object as required
    return {
      loading: state.someReducer.loading,
      //...
    }
}

type TDispatchProps = Pick<IProps,  'onSave'>;
const mapDispatchToProps = (
  dispatch: TDispatch,
): TDispatchProps => {
   // here you  have typed dispatch now
   // return the TDispatchProps object as required
   return {
    onSave: (): void => {
      dispatch(saveEntry()).then(() => {
        backButton();
      });
    },
   }
}

对于重击动作,我将执行以下操作

// this is a return type of the thunk
type TPromise = Promise<ISaveTaskResponse | Error>;

export const saveEntry = (): ThunkAction<
  TPromise, // thunk return type
  TAppState, // state type
  any, // extra argument, (not used)
  ISaveEntryAction // action type
> => (dispatch: TDispatch, getState: TGetState): TPromise => {
  // use getState
  // dispatch start saveEntry action
  // make async call
  return Axios.post('/some/endpoint', {}, {})
    .then((results: { data: ISaveTaskResponse; }): Promise<ISaveTaskResponse> => {
      // get results
      // you can dispatch finish saveEntry action
      return Promise.resolve(data);
    })
    .catch((error: Error): Promise<Error> => {
      // you can dispatch an error saveEntry action
      return Promise.reject(error);
    });
};

答案 1 :(得分:1)

Redux可以调度纯对象的动作。假设我们有这样的动作{type: 'ACTION2'}。我们可以创建动作创建者,并像这样将其包装在分派中

// This is our action
interface Action2 extends Action { type: "ACTION2"; }

// And this is action crator
const action2 = (): Action2 => ({ type: "ACTION2" });

借助thunk中间件,Redux可以调度功能。我们可以创建这样的异步动作

// This action will be dispatched from async action creator
interface Action1 extends Action { type: "ACTION1"; payload: string; }

// And async action creator
const thunkAction = (arg: string): ThunkAction<Promise<void>, {}, AppState, KnownActions> => 
    async dispatch => {
        const res = await asyncFunction(arg);
        dispatch({ type: "ACTION1", payload: res });
    },  

此处使用ThunkAction<R, S, E, A extends Action>类型。它接受以下类型参数:

  • R保留内部函数的返回类型。在上面的示例中,内部函数是异步函数,因此它返回Promise<void>
  • S停留在应用程序状态
  • E是未使用的扩展属性类型
  • A是操作的类型。上例中的KnownActions是所有可能的操作类型(type KnownActions = Action1 | Action2;)的并集

现在让我们看一下如何键入组件。

interface Component1DispatchProps {
  action2: () => Action2;
  thunkAction: (arg: string) => Promise<void>;
}

interface Component1StateProps {
  stateprop: string;
}

const mapDispatchToProps = (
  dispatch: ThunkDispatch<AppState, {}, KnownActions>
) => {
  return {
    action2: () => dispatch(action2()),
    thunkAction: (arg: string) => 
      dispatch(thunkAction(arg))
  };
};

thunkAction方法返回dispatch(thunkAction(arg)),其中thunkAction(arg)返回ThunkAction<Promise<void>, {}, AppState, KnownActions>。因此,dispatch将以类型ThunkAction的参数调用,并将解析为

<TReturnType>(
  thunkAction: ThunkAction<TReturnType, TState, TExtraThunkArg, TBasicAction>,
): TReturnType;

因此,在我们的示例中,dispatch(thunkAction(arg))将返回TReturnTypePromise<void>。在thunkAction中为Component1DispatchProps设置了这种签名。

完整的示例是here

答案 2 :(得分:1)

根据我的经验,问题不在于分配参数的类型,而实际上是组件上的 props类型

注意:我的解决方案没有使用mapDispatchToProps的功能符号,因为直到需要时为止,它都是not recommended(并且我还不需要)。但是我想这个解决方案可以适应它(具有正确的沙箱)。

要正确定义道具(无需创建复制粘贴界面),有必要将mapDispatchToProps分成两部分,一个用于减速器动作,另一个用于重击动作

为显示此解决方案,我从另一个答案中派生了一个示例(感谢@Fyodor)。

此处的完整代码:https://codesandbox.io/s/thunkdispatch-forked-uss1b?file=/src/Component.tsx

道具组合是以下各项的组合:

interface ComponentOwnProps {
  ownprop: string;
}

const mapStateToProps = (state: AppState) => ({
  stateprop: state.stateprop
});

const mapDispatchToProps = {
  action2
};

const mapDispatchThunkToProps = {
  thunkAction
};

道具的完整类型定义为:

type ComponentProps = ComponentOwnProps &
  ReturnType<typeof mapStateToProps> &
  typeof mapDispatchToProps &
  ThunkProps<typeof mapDispatchThunkToProps>;

这里唯一的技巧是ThunkProps,它将字典中的Thunk操作类型从“双重功能” (args) => (dispatch, getState) => void转换为已连接(且更简单)的(args) => void函数。

// types from redux examples (usually in `store.ts`)
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  any, // or some ThunkExtraArgument interface
  Action<string>
>;

// the tricky part: convert each `(args) => AppThunk => void` to `(args) => void`
export type ThunkProps<T extends { [K in keyof T]: (...a: any[]) => AppThunk<void> }> = {
  [K in keyof T]: (...args: Parameters<T[K]>) => void;
};

...最后您可以连接组件

// don't forget to combine the two mapDispatch-es
export default connect(
  mapStateToProps,
  {...mapDispatchToProps, ...mapDispatchThunkToProps}
)(Component);

完整代码(从@Fyodor的答案中派生和更新):https://codesandbox.io/s/thunkdispatch-forked-uss1b?file=/src/Component.tsx ...为了方便起见,所有重要的代码和类型都在Component.tsx中。

注意:尽管此解决方案有一些局限性,但对我而言,它的目的是不编写不必要的接口,并具有类型明确的道具,包括函数参数。

答案 3 :(得分:0)

您可以共享getAllProducts函数的代码吗 看起来getAllProducts返回了一个Promise,这是Typescript引发错误的原因。

getAllProducts的返回类型可以是一个动作:

{
  type: string
  [key: string]: any
}

或函数

(dispatch, getState) => {
  // do some async action
}

答案 4 :(得分:0)

您还可以在全局类型定义中为redux Dispatch函数提供另一个替代:

(将其放入global.d.ts或其他一些全局类型定义中)

import { ThunkAction } from 'redux-thunk';
import { Action } from 'redux';

declare module 'redux' {
    export interface Dispatch<A extends Action = AnyAction> {
        <T extends ThunkAction<any, any, any, any>>(action: T): T extends ThunkAction<infer K, any, any, any> ? K : never;
    }
}

然后在您的mapDispatchToProps中使用Dispatch类型而不是ThunkDispatch。