我努力让自己的行为在连接组件中保持类型安全。
基本上当我导入一堆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
,这意味着某些操作可能会返回承诺。我还希望保留这些信息,因此可以在组件中链接动作。但我认为使用铸造不应该是一个问题。
答案 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;
}
}