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

时间:2019-11-19 16:58:31

标签: reactjs typescript redux

我在组件中实现了这个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);

3 个答案:

答案 0 :(得分:1)

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

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

React-Redux类型具有某种内在的魔力,可以通过弄清thunk本身返回的内容来尝试“解决thunk”(基本上除去=> thunkFunction部分)。因此,您声明的updateValuesFromBackend与要传递的connect之间是不匹配的。

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

  

有一种精巧的技术可以根据connectmapState来推断mapDispatch将传递给组件的组合道具的类型。

     

ConnectedProps<T>中有一个新的@types/react-redux@7.1.2类型。您可以像这样使用它:

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中包含的重击动作创建者的类型,而{{1}}则不是。

     

更多详细信息:

     

答案 1 :(得分:0)

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

您对connect如何剥离函数签名的派发部分的解释确实是解决我的特定问题的方法:

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

答案 2 :(得分:0)

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

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