typescript + redux:在父组件中排除 redux props
typescript + redux: exclude redux props in parent component
我正在为我当前的网络应用程序使用 redux 和 typescript。
定义通过 @connect
接收 redux-actions 的组件的 props 以及来自父级的 props 的最佳实践是什么?
示例:
// mychild.tsx
export namespace MyChildComponent {
export interface IProps {
propertyFromParent: string;
propertyFromRedux: string; // !!!! -> This is the problem
actionsPropertyFromRedux: typeof MyReduxActions; // !!!! -> And this
}
}
@connect(mapStateToProps, mapDispatchToProps)
export class MyChildComponent extends React.Component <MyChildComponent.IProps, any> {
... react stuff
}
function mapStateToProps(state: RootState) {
return {
propertyFromRedux: state.propertyFromRedux
};
}
function mapDispatchToProps(dispatch) {
return {
actionsPropertyFromRedux: bindActionCreators(MyReduxActions as any, dispatch)
};
}
// myparent.tsx
export class MyParentComponent extends React.Component <MyParentComponent.IProps, any> {
... react stuff
render(){
// typescript complains, because I am not passing `propertyFromRedux`!
return <div><MyChildComponent propertyFromParent="yay" /></div>;
}
}
据我所知,我得到了 2 个解决方案。
只需将操作和状态传递到我的整个应用程序即可。但这意味着即使只有一些小的子组件必须更改,我的整个应用程序也会重新呈现。还是在我的顶级组件中监听所有商店更改的 redux 方式?然后我将不得不在 shouldComponentUpdate
中为不是平面对象的道具编写很多逻辑。
设置 MyChildComponent
可选的 IProps
中的参数,如下所示:
-
// mychild.tsx
export namespace MyChildComponent {
export interface IProps {
propertyFromParent: string;
propertyFromRedux?: typeof MyAction; // This is the problem
}
}
还有别的办法吗?以上两种方式在我看来都太乱了。
您需要拆分道具 - 您需要 DispatchProps
、StateProps
和 OwnProps
类型。您还必须将 TypeScript 的泛型与 connect
一起使用
DispatchProps
是您的动作创作者。
StateProps
是你的状态道具(duh) - 这些来自 mapStateToProps
- 该函数的 return 类型应该匹配这个类型。
OwnProps
是您的组件接受(并且可能是期望的)的道具。可选道具应该在界面中标记为可选。
我这样做的方式(没有装饰器,但我确信它适用于此)是
interface ComponentDispatchProps {
doSomeAction: typeof someAction;
}
interface ComponentStateProps {
somethingFromState: any;
}
interface ComponentOwnProps {
somethingWhichIsRequiredInProps: any;
somethingWhichIsNotRequiredInProps?: any;
}
// not necessary to combine them into another type, but it cleans up the next line
type ComponentProps = ComponentStateProps & ComponentDispatchProps & ComponentOwnProps;
class Component extends React.Component<ComponentProps, {}> {...}
function mapStateToProps(state, props) {
return { somethingFromState };
}
export default connect<ComponentStateProps, ComponentDispatchProps, ComponentOwnProps>(
mapStateToProps,
mapDispatchToProps
)(Component);
我认为你必须使用 @connect<StateProps, DispatchProps, OwnProps>
来装饰,return 一个 class 接受 OwnProps
.
如果您查看 connect
在 TS
中的实现
export declare function connect<TStateProps, TDispatchProps, TOwnProps>(...): ComponentDecorator<TStateProps & TDispatchProps, TOwnProps>
interface ComponentDecorator<TOriginalProps, TOwnProps> {
(component: ComponentClass<TOriginalProps> | StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>;
}
connect<...>
returns a ComponentDecorator
,当传递组件时(在您的情况下,这是在转译装饰器时透明地完成的),而不管 StateProps
,并且 DispatchProps
return 是一个需要 OwnProps
的组件。
connect
(非通用)returns InferableComponentDecorator
export interface InferableComponentDecorator {
<P, TComponentConstruct extends (ComponentClass<P> | StatelessComponent<P>)>(component: TComponentConstruct): TComponentConstruct;
}
它试图根据提供给组件的道具来推断道具,在您的情况下是所有道具的组合(OwnProps
从上面变为 ComponentProps
)。
当然你可以手动设置类型。但是使用生成的东西很舒服,你实际上是从 connect
得到的。它有助于避免恼人的重复。
示例 1:
type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>
class MyComponent extends React.PureComponent<Props> {
...
}
const mapStateToProps = (state: ReduxState) => ({
me: state.me,
})
const mapDispatchToProps = (dispatch: ReduxDispatch) => ({
doSomething: () => dispatch(Dispatcher.doSomething()),
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
现在我们直接从 redux 状态和 action/dispatch 函数获取类型。
一段时间后,我们将此示例简化为:
示例 2:
//// other file
import { InferableComponentEnhancerWithProps } from 'react-redux'
type ExtractConnectType<T> = T extends InferableComponentEnhancerWithProps<infer K, any> ? K : T
//// <= Save it somewhere and import
type Props = ExtractConnectType<typeof connectStore>
class MyComponent extends React.PureComponent<Props> {
...
}
const connectStore = connect(
(state: ReduxState) => ({
me: state.me,
}),
(dispatch) => ({
doSomething: () => dispatch(Dispatcher.doSomething()),
})
)
export default connectStore(MyComponent)
简单来说,
一个组件应该清楚什么 props
应该来自父级和 connect
(redux).
现在,connect()
可以将 redux state
(您的应用程序状态)或 action
作为 prop
发送给组件,其余的 props
组件应该来自父组件。
按照建议,最好将组件道具分成3部分(ComponentStateProps
,ComponentDispatchProps
&
ComponentOwnProps
)然后在[=13中使用它们=].并且,加入这 3 个道具形成 ComponentProps
.
我认为下面的代码会更好地理解。
// Your redux State
type SampleAppState = {
someState: any;
};
// State props received from redux
type ChildStateProps = {
propFromState: any;
};
// dispatch action received from redux (connect)
type ChildDispatchProps = {
actionAsProp: () => void;
};
// Props received from parent
type ChildOwnProps = {
propFromParent: any;
};
// All Props
type ChildProps = ChildStateProps & ChildDispatchProps & ChildOwnProps;
const ChildComponent = (props: ChildProps) => {
return <>{/*....*/}</>;
};
let ConnectedChildComponent = connect<
ChildStateProps,
ChildDispatchProps,
ChildOwnProps,
SampleAppState
>(
(appState: SampleAppState, ownProps: ChildOwnProps) => {
// Shape that matches ChildStateProps
return {
propFromState: appState.someState,
};
},
(dispatch, ownProps: ChildOwnProps) => {
return bindActionCreators(
// Shape that matches ChildDispatchProps
{
actionAsProp: () => {},
},
dispatch,
);
},
)(ChildComponent);
const ParentComponent = () => {
return (
<>
<ConnectedChildComponent propFromParent={'Prop Value'} />
</>
);
};
我正在为我当前的网络应用程序使用 redux 和 typescript。
定义通过 @connect
接收 redux-actions 的组件的 props 以及来自父级的 props 的最佳实践是什么?
示例:
// mychild.tsx
export namespace MyChildComponent {
export interface IProps {
propertyFromParent: string;
propertyFromRedux: string; // !!!! -> This is the problem
actionsPropertyFromRedux: typeof MyReduxActions; // !!!! -> And this
}
}
@connect(mapStateToProps, mapDispatchToProps)
export class MyChildComponent extends React.Component <MyChildComponent.IProps, any> {
... react stuff
}
function mapStateToProps(state: RootState) {
return {
propertyFromRedux: state.propertyFromRedux
};
}
function mapDispatchToProps(dispatch) {
return {
actionsPropertyFromRedux: bindActionCreators(MyReduxActions as any, dispatch)
};
}
// myparent.tsx
export class MyParentComponent extends React.Component <MyParentComponent.IProps, any> {
... react stuff
render(){
// typescript complains, because I am not passing `propertyFromRedux`!
return <div><MyChildComponent propertyFromParent="yay" /></div>;
}
}
据我所知,我得到了 2 个解决方案。
只需将操作和状态传递到我的整个应用程序即可。但这意味着即使只有一些小的子组件必须更改,我的整个应用程序也会重新呈现。还是在我的顶级组件中监听所有商店更改的 redux 方式?然后我将不得不在
shouldComponentUpdate
中为不是平面对象的道具编写很多逻辑。设置
MyChildComponent
可选的IProps
中的参数,如下所示:
-
// mychild.tsx
export namespace MyChildComponent {
export interface IProps {
propertyFromParent: string;
propertyFromRedux?: typeof MyAction; // This is the problem
}
}
还有别的办法吗?以上两种方式在我看来都太乱了。
您需要拆分道具 - 您需要 DispatchProps
、StateProps
和 OwnProps
类型。您还必须将 TypeScript 的泛型与 connect
DispatchProps
是您的动作创作者。StateProps
是你的状态道具(duh) - 这些来自mapStateToProps
- 该函数的 return 类型应该匹配这个类型。OwnProps
是您的组件接受(并且可能是期望的)的道具。可选道具应该在界面中标记为可选。
我这样做的方式(没有装饰器,但我确信它适用于此)是
interface ComponentDispatchProps {
doSomeAction: typeof someAction;
}
interface ComponentStateProps {
somethingFromState: any;
}
interface ComponentOwnProps {
somethingWhichIsRequiredInProps: any;
somethingWhichIsNotRequiredInProps?: any;
}
// not necessary to combine them into another type, but it cleans up the next line
type ComponentProps = ComponentStateProps & ComponentDispatchProps & ComponentOwnProps;
class Component extends React.Component<ComponentProps, {}> {...}
function mapStateToProps(state, props) {
return { somethingFromState };
}
export default connect<ComponentStateProps, ComponentDispatchProps, ComponentOwnProps>(
mapStateToProps,
mapDispatchToProps
)(Component);
我认为你必须使用 @connect<StateProps, DispatchProps, OwnProps>
来装饰,return 一个 class 接受 OwnProps
.
如果您查看 connect
在 TS
export declare function connect<TStateProps, TDispatchProps, TOwnProps>(...): ComponentDecorator<TStateProps & TDispatchProps, TOwnProps>
interface ComponentDecorator<TOriginalProps, TOwnProps> {
(component: ComponentClass<TOriginalProps> | StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>;
}
connect<...>
returns a ComponentDecorator
,当传递组件时(在您的情况下,这是在转译装饰器时透明地完成的),而不管 StateProps
,并且 DispatchProps
return 是一个需要 OwnProps
的组件。
connect
(非通用)returns InferableComponentDecorator
export interface InferableComponentDecorator {
<P, TComponentConstruct extends (ComponentClass<P> | StatelessComponent<P>)>(component: TComponentConstruct): TComponentConstruct;
}
它试图根据提供给组件的道具来推断道具,在您的情况下是所有道具的组合(OwnProps
从上面变为 ComponentProps
)。
当然你可以手动设置类型。但是使用生成的东西很舒服,你实际上是从 connect
得到的。它有助于避免恼人的重复。
示例 1:
type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>
class MyComponent extends React.PureComponent<Props> {
...
}
const mapStateToProps = (state: ReduxState) => ({
me: state.me,
})
const mapDispatchToProps = (dispatch: ReduxDispatch) => ({
doSomething: () => dispatch(Dispatcher.doSomething()),
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
现在我们直接从 redux 状态和 action/dispatch 函数获取类型。
一段时间后,我们将此示例简化为:
示例 2:
//// other file
import { InferableComponentEnhancerWithProps } from 'react-redux'
type ExtractConnectType<T> = T extends InferableComponentEnhancerWithProps<infer K, any> ? K : T
//// <= Save it somewhere and import
type Props = ExtractConnectType<typeof connectStore>
class MyComponent extends React.PureComponent<Props> {
...
}
const connectStore = connect(
(state: ReduxState) => ({
me: state.me,
}),
(dispatch) => ({
doSomething: () => dispatch(Dispatcher.doSomething()),
})
)
export default connectStore(MyComponent)
简单来说,
一个组件应该清楚什么 props
应该来自父级和 connect
(redux).
现在,connect()
可以将 redux state
(您的应用程序状态)或 action
作为 prop
发送给组件,其余的 props
组件应该来自父组件。
按照建议,最好将组件道具分成3部分(ComponentStateProps
,ComponentDispatchProps
&
ComponentOwnProps
)然后在[=13中使用它们=].并且,加入这 3 个道具形成 ComponentProps
.
我认为下面的代码会更好地理解。
// Your redux State
type SampleAppState = {
someState: any;
};
// State props received from redux
type ChildStateProps = {
propFromState: any;
};
// dispatch action received from redux (connect)
type ChildDispatchProps = {
actionAsProp: () => void;
};
// Props received from parent
type ChildOwnProps = {
propFromParent: any;
};
// All Props
type ChildProps = ChildStateProps & ChildDispatchProps & ChildOwnProps;
const ChildComponent = (props: ChildProps) => {
return <>{/*....*/}</>;
};
let ConnectedChildComponent = connect<
ChildStateProps,
ChildDispatchProps,
ChildOwnProps,
SampleAppState
>(
(appState: SampleAppState, ownProps: ChildOwnProps) => {
// Shape that matches ChildStateProps
return {
propFromState: appState.someState,
};
},
(dispatch, ownProps: ChildOwnProps) => {
return bindActionCreators(
// Shape that matches ChildDispatchProps
{
actionAsProp: () => {},
},
dispatch,
);
},
)(ChildComponent);
const ParentComponent = () => {
return (
<>
<ConnectedChildComponent propFromParent={'Prop Value'} />
</>
);
};