如何使用Redux,React和TypeScript维护连接组件中的动作创建者类型?

时间:2017-12-04 19:01:48

标签: reactjs typescript casting redux

我努力让自己的行为在连接组件中保持类型安全。

基本上当我导入一堆redux动作创建器时,使用react-redux将它们包装为调度程序并将它们作为道具传递给组件,我希望生成的操作能够保持导入的原始类型信息功能

操作具有类型和返回类型:

export const actionA = (p1: string, p2: number) =>
  ({ type: 'EXAMPLE_A', payload: { p1, p2 } })

export const actionB = (p1: number) =>
  ({ type: 'EXAMPLE_B', payload: p1 })

但是我的组件仍然有一些any类型来满足编译器,从而失去了类型安全性。

import * as React from 'react'
import { Dispatch, bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as exampleActions from '../actions/example'

interface MyComponentProps {
  someStore: SomeStoreState,
  actions: any // <-- Just use whatever types exampleActions have?
}

class MyComponent extends React.Component<MyComponentProps, {}> {

  constructor(props: MyComponentProps) {
    super(props)
  }

  private foo() {
    const { actionA } = this.props.actions.exampleActions
    actionA('foo', 'bar') // <-- Compile time error pls
  }


  public render() {
    return null
  }

}

const mapStateToProps = (state: RootState) => ({
  someStore: state.someStore
})

const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
  actions: {
    exampleActions: bindActionCreators<any>(exampleActions, dispatch)
  }
})

export default connect (mapStateToProps, mapDispatchToProps)(MyComponent)

在props接口sorta中再次声明函数参数类型有帮助,但我只想维护原始类型,以便在一个地方定义它们。

我并不真正关心调度程序本身的类型,因此以某种方式将exampleTypes(和任何其他操作')类型信息投射到道具上对我来说是一个很好的解决方案,就好像发送绑定根本不存在,创作者本身也作为道具传递。

此外,应用程序正在使用redux-promise-middleware,这意味着某些操作可能会返回承诺。我还希望保留这些信息,因此可以在组件中链接动作。但我认为使用铸造不应该是一个问题。

1 个答案:

答案 0 :(得分:2)

您需要明确键入您的动作创建者,然后将其类型与函数本身一起导入。创建一些通用动作接口可以帮助解决这个问题,因为通常我发现redux类型本身没有用。它有点冗长,但类型支持通常是值得的,特别是因为你可以在减速器中获得优秀的打字。

我通常会对动作/创作者使用类似的东西:

export interface TypedAction<TAction, TPayload> {
     type: TAction;
     payload: TPayload;
}

export type TypeA = "EXAMPLE_A";
export type TypeB = "EXAMPLE_B";

export interface PayloadA {
    p1: string;
    p2: number;
}

export interface PayloadB {
    p1: number;
}

export type ActionA = TypedAction<TypeA, PayloadA>;
export type ActionB = TypedAction<TypeB, PayloadB>;

export type Actions = ActionA | ActionB;

export type ActionCreatorA = (p1: string, p2: number) => ActionA;

export type ActionCreatorB = (p1: number) => ActionB;

export const actionCreatorA: ActionCreatorA = (p1, p2) => ({
    type: "EXAMPLE_A", 
    payload: {
        p1,
        p2
    }
});    

export const actionCreatorB: ActionCreatorB = (p1) => ({
    type: "EXAMPLE_B", 
    payload: {
        p1
    }
});

可以在组件中使用:

import * as React from 'react'
import { Dispatch, bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { 
    actionCreatorA, ActionCreatorA,
    actionCreatorB, ActionCreatorB
} from '../actions/example'

interface MyComponentProps {
    someStore: SomeStoreState;
    actionCreatorA: ActionCreatorA;
    actionCreatorB: ActionCreatorB;
}

class MyComponent extends React.Component<MyComponentProps, {}> {

    constructor(props: MyComponentProps) {
        super(props)
    }

    private foo() {
        const { actionA } = this.props;
        actionA('foo', 'bar') // <-- Compiles
    }


    public render() {
        return null
    }

}

const mapStateToProps = (state: RootState) => ({
    someStore: state.someStore
})

const mapDispatchToProps = {
    actionCreatorA
};

export default connect (mapStateToProps, mapDispatchToProps)(MyComponent)

减少者也可以通过使用以下方式获益:

import ( Actions } from "./actions/example";

// Actions here is the union type of all actions this reducer will
// handle, as exported from the actions file
export const someReducer = (state = defaultState, action: Actions) => {
    switch (action.type) {
        case "EXAMPLE_A":
            // action is typed as ActionA
            return {
                p1: action.p1,
                p2: action.p2
            };
        case "EXAMPLE_B":
            // action is typed as ActionB
            return {
                p1: action.p1,
                p2: action.p2  // <-- Does not compile, p2 does not exist on ActionB
            };
        default:
            return state;
    }
}