我正在使用redux和typescript作为我当前的webapp。
定义组件的道具的最佳做法是什么,它通过@connect
接收redux-actions,还有来自父级的道具?
例如:
// 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>;
}
}
在我看来,我有两个解决方案。
刚刚通过行动&amp;通过我的整个应用程序说明。但这意味着即使只是一些小的子组件必须改变,我的整个应用程序也会被重新渲染。或者是在所有商店更改中收听我的顶级组件的redux方式?然后我必须在shouldComponentUpdate
内写出很多逻辑,用于没有扁平物体的道具。
将IProps
MyChildComponent
中的参数设置为可选,如下所示:
-
// mychild.tsx
export namespace MyChildComponent {
export interface IProps {
propertyFromParent: string;
propertyFromRedux?: typeof MyAction; // This is the problem
}
}
还有其他方法吗?上述两种方式在我眼中看起来都太乱了。
答案 0 :(得分:17)
您需要拆分道具 - 您需要DispatchProps
,StateProps
和OwnProps
类型。您还必须使用TypeScript的泛型connect
DispatchProps
是您的行动创作者。StateProps
是你的状态道具(duh) - 这些来自mapStateToProps
- 该函数的返回类型应与此类型匹配。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>
来装饰并返回一个接受OwnProps
的类。
如果你看一下TS中的connect
实现
export declare function connect<TStateProps, TDispatchProps, TOwnProps>(...): ComponentDecorator<TStateProps & TDispatchProps, TOwnProps>
interface ComponentDecorator<TOriginalProps, TOwnProps> {
(component: ComponentClass<TOriginalProps> | StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>;
}
connect<...>
返回一个ComponentDecorator
,当传递组件时(在你的情况下,这是在转发装饰器时透明地完成),无论StateProps
和{{1}返回一个期望DispatchProps
的组件。
OwnProps
(非通用)返回connect
InferableComponentDecorator
试图根据提供给组件的道具来推断道具,在你的情况下是所有道具的组合(export interface InferableComponentDecorator {
<P, TComponentConstruct extends (ComponentClass<P> | StatelessComponent<P>)>(component: TComponentConstruct): TComponentConstruct;
}
从上面变为OwnProps
)。
答案 1 :(得分:0)
当然,您可以手动设置类型。但是实际上使用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状态和操作/调度功能获取类型。
一段时间后,我们将该示例简化为:
示例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)
答案 2 :(得分:0)
简单来说,
组件应该清楚 props
应该来自父级和 connect
(redux)。
现在,connect()
可以向组件发出 redux state
(您的应用程序状态)或 action
作为 prop
,组件的其余 props
应该来自父母。
按照建议,最好将组件 props 分成 3 部分(ComponentStateProps
, ComponentDispatchProps
&
ComponentOwnProps
),然后在 connect()
中使用它们.并且,加入这 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'} />
</>
);
};