我有这个redux thunk操作,该操作在我的组件中实现,该组件通过容器组件连接到redux。我收到一个错误,指出App
本身的道具与传递给connect
的道具不兼容。似乎期望返回一个ThunkAction
的函数,但正在接收一个返回void
的函数。
C:/Users/jtuzman/dev/NOUS/front_end/src/containers/AppContainer.tsx
Type error: Argument of type 'typeof App' is not assignable to parameter of type 'ComponentType<Matching<{ settingsReceived: boolean; error: Error | null; } & { changeParameter: (parameter: ParameterName, value: any) => ChangeParameterAction; reportError: (msg: string, type?: ErrorType) => ErrorAction; updateValuesFromBackend: (updatedValues: any) => void; }, AppProps>>'.
Type 'typeof App' is not assignable to type 'ComponentClass<Matching<{ settingsReceived: boolean; error: Error | null; } & { changeParameter: (parameter: ParameterName, value: any) => ChangeParameterAction; reportError: (msg: string, type?: ErrorType) => ErrorAction; updateValuesFromBackend: (updatedValues: any) => void; }, AppProps>, any>'.
Types of parameters 'props' and 'props' are incompatible.
Type 'Matching<{ settingsReceived: boolean; error: Error | null; } & { changeParameter: (parameter: ParameterName, value: any) => ChangeParameterAction; reportError: (msg: string, type?: ErrorType) => ErrorAction; updateValuesFromBackend: (updatedValues: any) => void; }, AppProps>' is not assignable to type 'Readonly<AppProps>'.
The types returned by 'updateValuesFromBackend(...)' are incompatible between these types.
Type 'void' is not assignable to type 'ThunkAction<void, { meta: { ...; }; study: { ...; }; data: { ...; } | { ...; }; }, {}, MyAction>'. TS2345
18 | mapStateToProps,
19 | mapDispatchToProps,
> 20 | )(App);
| ^
21 |
我不明白这一点,首先是因为我的函数确实返回了ThunkAction
(我认为?),但是主要是因为类型是typeof updateValuesFromBackend
,所以无论如何都应该不会出现不匹配,对吗?
BTW ThunkAction
,我的动作“应该”返回但“不是”,实际上是返回void的函数的别名! (即updateValuesFromBackend
返回ThunkAction
,这实际上是返回void
的函数)
当我切换tp class App extends Component<any>
时,我当然可以运行该应用程序。
谁知道我该如何解决?
export const updateValuesFromBackend = (updatedValues: any): MyThunkAction => {
return (
dispatch: ThunkDispatch<AppState, {}, MyAction>,
getState: () => AppState
) => {
const changes: any = {};
Object.entries(updatedValues).forEach(([key, value]) => {
const parameter = backEndSettingsKeys[key];
if (!parameter) return;
changes[parameter] = value;
});
// if we haven't received any settings yet, accept these settings
if (true || !getState().meta.settingsReceived) {
dispatch(setParameters(changes));
} else {
// Compare the received settings with the current settings.
// If they match, we consider this a "success" response
// after a settings update request.
// If they don't, we consider this an error.
}
};
};
在我的App
组件中实现,该组件连接到AppContainer
中的redux:
export type AppProps = {
settingsReceived: boolean,
error: Error | null,
changeParameter: typeof changeParameter,
updateValuesFromBackend: typeof updateValuesFromBackend,
reportError: typeof reportError
}
// class App extends Component<any> {
class App extends Component<AppProps> {
settingsSubscription: W3CWebSocket;
componentDidMount(): void {
this.settingsSubscription = this.subscribeToSettings(urls.SETTINGS_WS);
}
subscribeToSettings(url: string): W3CWebSocket {
let settingsSubscription = new W3CWebSocket(url);
settingsSubscription.onopen = () => console.log('WebSocket Client Connected (settings)');
settingsSubscription.onclose = () => console.log('WebSocket Client Disconnected (settings)');
settingsSubscription.onmessage = (message: MessageEvent) => this.handleSettingsMessage(message);
return settingsSubscription;
}
handleSettingsMessage(message: MessageEvent) {
const updatedValues = JSON.parse(message.data);
const { settingsReceived, reportError, updateValuesFromBackend } = this.props;
if (settingsReceived) return reportError('Invalid settings received.');
console.log('updating settings');
updateValuesFromBackend(updatedValues);
}
render() {
return this.props.error
? <ErrorWrapper/>
: <InnerAppContainer/>
}
}
export default App;
const mapStateToProps = ({ meta }: AppState) => ({
settingsReceived: meta.settingsReceived,
error: meta.error
});
const mapDispatchToProps = ({
changeParameter, reportError, updateValuesFromBackend
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
是,问题特别是在updateValuesFromBackend: typeof updateValuesFromBackend
部分。
其实际类型大致为() => thunkFunction => void
。但是,由于dispatch
和thunk的工作方式,您自己的组件获取的绑定函数实际上看起来像() => void
。
React-Redux类型内部有一些魔术,它通过弄清楚thunk自身返回的内容(基本上是删除=> thunkFunction
部分)来尝试“解决thunk”。因此,您声明的updateValuesFromBackend
与要传递的connect
之间不匹配。
我最近自己遇到了这个问题,并找到了一个非常简洁的解决方案,我们很快将在React-Redux文档中正式推荐该解决方案。我在this SO answer中描述了该方法,但是为了完整起见,我将其粘贴在这里:
[有一种精巧的技术可以基于
connect
和mapState
来推断mapDispatch
将传递给组件的组合道具的类型。
ConnectedProps<T>
中有新的@types/[email protected]
类型。您可以像这样使用它:
function mapStateToProps(state: MyState) {
return {
counter: state.counter
};
}
const mapDispatch = {increment};
// Do the first half of the `connect()` call separately,
// before declaring the component
const connector = connect(mapState, mapDispatch);
// Extract "the type of the props passed down by connect"
type PropsFromRedux = ConnectedProps<typeof connector>
// should be: {counter: number, increment: () => {type: "INCREMENT"}}, etc
// define combined props
type MyComponentProps = PropsFromRedux & PropsFromParent;
// Declare the component with the right props type
class MyComponent extends React.Component<MyComponentProps> {}
// Finish the connect call
export default connector(MyComponent)
[请注意,如果它是对象,则可以正确推断
mapDispatch
中包含的重击动作创建者的类型,而typeof mapDispatch
则不是。更多详细信息:
非常感谢@markerikson的出色解释。在我的实现中,我想保持表示组件完全独立,并且不了解其容器及其与Redux存储的连接。因此,您在容器中定义连接类型并将其导入到我的组件中的具体(且非常好的)建议不适合我的体系结构。
您对connect
如何剥离功能签名的分派部分的说明实际上是我特定问题的解决方案:
export type AppProps = {
settingsReceived: boolean,
error: Error | null,
changeParameter: typeof changeParameter,
updateValuesFromBackend: (updatedValues: any) => void,
reportError: typeof reportError
}
个人,我认为将所有连接逻辑从组件中移出Saga或自定义中间件会更好。这样,当您从API获得响应时,您可以决定是否应该分派操作。
尽管它是基于REST的,而不是使用WebSockets。我建议看看redux-api-middleware
的工作方式,以此为例。