如何正确输入 Redux 连接调用?
How to properly type a Redux connect call?
我正在尝试将 Redux 状态存储与 TypeScript 结合使用。我正在尝试使用 Redux 的官方 Typings 并希望对 connect
方法(将 mapStatetoProps
和 mapDispatchToProps
与组件连接起来)进行整个调用类型安全。
我经常看到方法 mapStatetoProps
和 mapDispatchToProps
只是自定义类型和 return 组件道具的一部分,例如:
function mapStateToProps(state: IStateStore, ownProps: Partial<IComponentProps>)
: Partial<IProjectEditorProps> {}
function mapDispatchToProps (dispatch: Dispatch, ownProps: Partial<IComponentProps>)
: Partial<IProjectEditorProps> {}
这是类型化的并且可以工作,但不是很安全,因为可能会实例化一个缺少 props 的组件,因为 Partial 接口的使用允许不完整的定义。但是,这里需要 Partial 接口,因为您可能想在 mapStateToProps
中定义一些道具,在 mapDispatchToProps
中定义一些道具,而不是在一个函数中全部定义。所以这就是为什么我想避免这种风格。
我目前尝试使用的是直接将函数嵌入 connect
调用中,并使用 redux 提供的通用类型输入连接调用:
connect<IComponentProps, any, any, IStateStore>(
(state, ownProps) => ({
/* some props supplied by redux state */
}),
dispatch => ({
/* some more props supplied by dispatch calls */
})
)(Component);
但是,这也会抛出嵌入式mapStatetoProps
和mapDispatchToProps
调用未定义all Props each[=的错误38=] 因为两者只需要它们的一个子集,但一起定义了所有 Props。
如何正确键入连接调用,以便 mapStatetoProps
和 mapDispatchToProps
调用真正是类型安全的,并且键入检查这两种方法定义的组合值是否提供所有必需的道具,而没有之一一次定义所有道具所需的方法?这在某种程度上可以用我的方法实现吗?
选项 1:拆分 IComponentProps
最简单的方法可能就是为 "state derived props"、"own props" 和 "dispatch props" 定义单独的接口,然后使用 intersection type 加入它们一起为了IComponentProps
import * as React from 'react';
import { connect, Dispatch } from 'react-redux'
import { IStateStore } from '@src/reducers';
interface IComponentOwnProps {
foo: string;
}
interface IComponentStoreProps {
bar: string;
}
interface IComponentDispatchProps {
fooAction: () => void;
}
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps
class IComponent extends React.Component<IComponentProps, never> {
public render() {
return (
<div>
foo: {this.props.foo}
bar: {this.props.bar}
<button onClick={this.props.fooAction}>Do Foo</button>
</div>
);
}
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
(state, ownProps): IComponentStoreProps => {
return {
bar: state.bar + ownProps.foo
};
},
(dispatch: Dispatch<IStateStore>): IComponentDispatchProps => (
{
fooAction: () => dispatch({type:'FOO_ACTION'})
}
)
)(IComponent);
我们可以像这样设置连接函数的通用参数:
<TStateProps, TDispatchProps, TOwnProps, State>
选项 2:让您的函数定义 Props 接口
我在野外看到的另一个选择是利用 ReturnType
mapped type 让您的 mapX2Props
函数实际定义它们对 IComponentProps
.
的贡献
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps;
interface IComponentOwnProps {
foo: string;
}
type IComponentStoreProps = ReturnType<typeof mapStateToProps>;
type IComponentDispatchProps = ReturnType<typeof mapDispatchToProps>;
class IComponent extends React.Component<IComponentProps, never> {
//...
}
function mapStateToProps(state: IStateStore, ownProps: IComponentOwnProps) {
return {
bar: state.bar + ownProps.foo,
};
}
function mapDispatchToProps(dispatch: Dispatch<IStateStore>) {
return {
fooAction: () => dispatch({ type: 'FOO_ACTION' })
};
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
mapStateToProps,
mapDispatchToProps
)(IComponent);
这里最大的优势是它减少了一点样板,这样当你添加一个新的映射 prop 时你只有一个地方可以更新。
我一直避开 ReturnType
,简化是因为让您的实现定义您的编程接口感觉倒退 "contracts" (IMO)。 太容易以您不希望的方式改变您的IComponentProps
。
但是,由于这里的所有内容都是独立的,因此它可能是一个可以接受的用例。
一个解决方案是将您的组件属性拆分为状态、调度和自有属性:
import React from "react";
import { connect } from "react-redux";
import { deleteItem } from "./action";
import { getItemById } from "./selectors";
interface StateProps {
title: string;
}
interface DispatchProps {
onDelete: () => any;
}
interface OwnProps {
id: string;
}
export type SampleItemProps = StateProps & DispatchProps & OwnProps;
export const SampleItem: React.SFC<SampleItemProps> = props => (
<div>
<div>{props.title}</div>
<button onClick={props.onDelete}>Delete</button>
</div>
);
// You can either use an explicit mapStateToProps...
const mapStateToProps = (state: RootState, ownProps: OwnProps) : StateProps => ({
title: getItemById(state, ownProps.id)
});
// Ommitted mapDispatchToProps...
// ... and infer the types from connects arguments ...
export default connect(mapStateToProps, mapDispatchToProps)(SampleItem);
// ... or explicitly type connect and "inline" map*To*.
export default connect<StateProps, DispatchProps, OwnProps, RootState>(
(state, ownProps) => ({
title: getItemById(state, ownProps.id)
}),
(dispatch, ownProps) => ({
onDelete: () => dispatch(deleteItem(ownProps.id))
})
)(SampleItem);
真的很喜欢@NSjonas 的拆分方法,但我也想从他的第二种方法中借鉴一些东西,以便在实用性之间取得平衡,不要让实现完全定义你的接口,也不要在输入你的调度动作时过于冗长;
import * as React from 'react';
import { connect, Dispatch } from 'react-redux'
import { IStateStore } from '@src/reducers';
import { fooAction } from '@src/actions';
interface IComponentOwnProps {
foo: string;
}
interface IComponentStoreProps {
bar: string;
}
interface IComponentDispatchProps {
doFoo: (...args: Parameters<typeof fooAction>) => void;
}
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps
class IComponent extends React.Component<IComponentProps, never> {
public render() {
return (
<div>
foo: {this.props.foo}
bar: {this.props.bar}
<button onClick={this.props.doFoo}>Do Foo</button>
</div>
);
}
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
(state, ownProps): IComponentStoreProps => {
return {
bar: state.bar + ownProps.foo
};
},
{
doFoo: fooAction
}
)(IComponent);
我正在尝试将 Redux 状态存储与 TypeScript 结合使用。我正在尝试使用 Redux 的官方 Typings 并希望对 connect
方法(将 mapStatetoProps
和 mapDispatchToProps
与组件连接起来)进行整个调用类型安全。
我经常看到方法 mapStatetoProps
和 mapDispatchToProps
只是自定义类型和 return 组件道具的一部分,例如:
function mapStateToProps(state: IStateStore, ownProps: Partial<IComponentProps>)
: Partial<IProjectEditorProps> {}
function mapDispatchToProps (dispatch: Dispatch, ownProps: Partial<IComponentProps>)
: Partial<IProjectEditorProps> {}
这是类型化的并且可以工作,但不是很安全,因为可能会实例化一个缺少 props 的组件,因为 Partial 接口的使用允许不完整的定义。但是,这里需要 Partial 接口,因为您可能想在 mapStateToProps
中定义一些道具,在 mapDispatchToProps
中定义一些道具,而不是在一个函数中全部定义。所以这就是为什么我想避免这种风格。
我目前尝试使用的是直接将函数嵌入 connect
调用中,并使用 redux 提供的通用类型输入连接调用:
connect<IComponentProps, any, any, IStateStore>(
(state, ownProps) => ({
/* some props supplied by redux state */
}),
dispatch => ({
/* some more props supplied by dispatch calls */
})
)(Component);
但是,这也会抛出嵌入式mapStatetoProps
和mapDispatchToProps
调用未定义all Props each[=的错误38=] 因为两者只需要它们的一个子集,但一起定义了所有 Props。
如何正确键入连接调用,以便 mapStatetoProps
和 mapDispatchToProps
调用真正是类型安全的,并且键入检查这两种方法定义的组合值是否提供所有必需的道具,而没有之一一次定义所有道具所需的方法?这在某种程度上可以用我的方法实现吗?
选项 1:拆分 IComponentProps
最简单的方法可能就是为 "state derived props"、"own props" 和 "dispatch props" 定义单独的接口,然后使用 intersection type 加入它们一起为了IComponentProps
import * as React from 'react';
import { connect, Dispatch } from 'react-redux'
import { IStateStore } from '@src/reducers';
interface IComponentOwnProps {
foo: string;
}
interface IComponentStoreProps {
bar: string;
}
interface IComponentDispatchProps {
fooAction: () => void;
}
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps
class IComponent extends React.Component<IComponentProps, never> {
public render() {
return (
<div>
foo: {this.props.foo}
bar: {this.props.bar}
<button onClick={this.props.fooAction}>Do Foo</button>
</div>
);
}
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
(state, ownProps): IComponentStoreProps => {
return {
bar: state.bar + ownProps.foo
};
},
(dispatch: Dispatch<IStateStore>): IComponentDispatchProps => (
{
fooAction: () => dispatch({type:'FOO_ACTION'})
}
)
)(IComponent);
我们可以像这样设置连接函数的通用参数:
<TStateProps, TDispatchProps, TOwnProps, State>
选项 2:让您的函数定义 Props 接口
我在野外看到的另一个选择是利用 ReturnType
mapped type 让您的 mapX2Props
函数实际定义它们对 IComponentProps
.
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps;
interface IComponentOwnProps {
foo: string;
}
type IComponentStoreProps = ReturnType<typeof mapStateToProps>;
type IComponentDispatchProps = ReturnType<typeof mapDispatchToProps>;
class IComponent extends React.Component<IComponentProps, never> {
//...
}
function mapStateToProps(state: IStateStore, ownProps: IComponentOwnProps) {
return {
bar: state.bar + ownProps.foo,
};
}
function mapDispatchToProps(dispatch: Dispatch<IStateStore>) {
return {
fooAction: () => dispatch({ type: 'FOO_ACTION' })
};
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
mapStateToProps,
mapDispatchToProps
)(IComponent);
这里最大的优势是它减少了一点样板,这样当你添加一个新的映射 prop 时你只有一个地方可以更新。
我一直避开 ReturnType
,简化是因为让您的实现定义您的编程接口感觉倒退 "contracts" (IMO)。 太容易以您不希望的方式改变您的IComponentProps
。
但是,由于这里的所有内容都是独立的,因此它可能是一个可以接受的用例。
一个解决方案是将您的组件属性拆分为状态、调度和自有属性:
import React from "react";
import { connect } from "react-redux";
import { deleteItem } from "./action";
import { getItemById } from "./selectors";
interface StateProps {
title: string;
}
interface DispatchProps {
onDelete: () => any;
}
interface OwnProps {
id: string;
}
export type SampleItemProps = StateProps & DispatchProps & OwnProps;
export const SampleItem: React.SFC<SampleItemProps> = props => (
<div>
<div>{props.title}</div>
<button onClick={props.onDelete}>Delete</button>
</div>
);
// You can either use an explicit mapStateToProps...
const mapStateToProps = (state: RootState, ownProps: OwnProps) : StateProps => ({
title: getItemById(state, ownProps.id)
});
// Ommitted mapDispatchToProps...
// ... and infer the types from connects arguments ...
export default connect(mapStateToProps, mapDispatchToProps)(SampleItem);
// ... or explicitly type connect and "inline" map*To*.
export default connect<StateProps, DispatchProps, OwnProps, RootState>(
(state, ownProps) => ({
title: getItemById(state, ownProps.id)
}),
(dispatch, ownProps) => ({
onDelete: () => dispatch(deleteItem(ownProps.id))
})
)(SampleItem);
真的很喜欢@NSjonas 的拆分方法,但我也想从他的第二种方法中借鉴一些东西,以便在实用性之间取得平衡,不要让实现完全定义你的接口,也不要在输入你的调度动作时过于冗长;
import * as React from 'react';
import { connect, Dispatch } from 'react-redux'
import { IStateStore } from '@src/reducers';
import { fooAction } from '@src/actions';
interface IComponentOwnProps {
foo: string;
}
interface IComponentStoreProps {
bar: string;
}
interface IComponentDispatchProps {
doFoo: (...args: Parameters<typeof fooAction>) => void;
}
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps
class IComponent extends React.Component<IComponentProps, never> {
public render() {
return (
<div>
foo: {this.props.foo}
bar: {this.props.bar}
<button onClick={this.props.doFoo}>Do Foo</button>
</div>
);
}
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
(state, ownProps): IComponentStoreProps => {
return {
bar: state.bar + ownProps.foo
};
},
{
doFoo: fooAction
}
)(IComponent);