不兼容的道具:void不可分配给ThunkAction

问题描述 投票:0回答:3

我有这个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>时,我当然可以运行该应用程序。

谁知道我该如何解决?

actions / index.ts

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:

App.tsx


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;

AppContainer.tsx


const mapStateToProps = ({ meta }: AppState) => ({
  settingsReceived: meta.settingsReceived,
  error: meta.error
});

const mapDispatchToProps = ({
  changeParameter, reportError, updateValuesFromBackend
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
reactjs typescript redux
3个回答
1
投票

是,问题特别是在updateValuesFromBackend: typeof updateValuesFromBackend部分。

其实际类型大致为() => thunkFunction => void。但是,由于dispatch和thunk的工作方式,您自己的组件获取的绑定函数实际上看起来像() => void

React-Redux类型内部有一些魔术,它通过弄清楚thunk自身返回的内容(基本上是删除=> thunkFunction部分)来尝试“解决thunk”。因此,您声明的updateValuesFromBackend与要传递的connect之间不匹配。

我最近自己遇到了这个问题,并找到了一个非常简洁的解决方案,我们很快将在React-Redux文档中正式推荐该解决方案。我在this SO answer中描述了该方法,但是为了完整起见,我将其粘贴在这里:

[有一种精巧的技术可以基于connectmapState来推断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则不是。

更多详细信息:


0
投票

非常感谢@markerikson的出色解释。在我的实现中,我想保持表示组件完全独立,并且不了解其容器及其与Redux存储的连接。因此,您在容器中定义连接类型并将其导入到我的组件中的具体(且非常好的)建议不适合我的体系结构。

您对connect如何剥离功能签名的分派部分的说明实际上是我特定问题的解决方案:

export type AppProps = {
  settingsReceived: boolean,
  error: Error | null,
  changeParameter: typeof changeParameter,
  updateValuesFromBackend: (updatedValues: any) => void,
  reportError: typeof reportError
}

0
投票

个人,我认为将所有连接逻辑从组件中移出Saga或自定义中间件会更好。这样,当您从API获得响应时,您可以决定是否应该分派操作。

尽管它是基于REST的,而不是使用WebSockets。我建议看看redux-api-middleware的工作方式,以此为例。

© www.soinside.com 2019 - 2024. All rights reserved.