【问题标题】:Redux + TypeScript and mapDispatchToPropsRedux + TypeScript 和 mapDispatchToProps
【发布时间】:2019-10-15 01:15:43
【问题描述】:

这应该很容易,但目前我无法解决,但我觉得这应该是一个简单的解决方法。我目前使用 reduxtypescript 并使用 redux-thunk 异步操作创建者。

设置很简单。这是我用于登录的代码:

export function requestAuthenticationAsync(email: string, password: string) {
    return (dispatch: ThunkDispatch<IState, undefined, IAction>): Promise<void> => {
        dispatch(requestAuthentication());

        return postAuthentication(email, password).then((response) => {
            dispatch(receiveAuthentication());

            return response.json();
        }).then((data) => {
            dispatch(receiveUser(data));
        });
    };
}

理想的情况是我可以在登录成功时使用.then.tsx 文件中调用它,以便在其他地方导航。

所以,当我在组件中执行类似操作时,它的工作原理与您期望的一样:

const { dispatch } = store;

dispatch(requestAuthenticationAsync('email', 'password')).then(() => {
    // navigate somewhere
});

但是,当我像这样使用react-redux 中的connectmapDispatchToProps 时:

import './Gateway.scss';
import * as React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { requestAuthenticationAsync } from './actions';
import { withRouter } from 'react-router-dom';

const mapDispatchToProps = (dispatch) => {
    return {
        requestAuthenticationAsync: bindActionCreators(requestAuthenicationAsync, dispatch)
    };
};

const mapStateToProps = (state) => {
    return {
        authenticated: state.authentication.authenticated
    };
};

class Gateway extends React.Component<{
    authenticated: boolean;
    requestAuthenticationAsync: typeof requestAuthenticationAsync;
}, {
    email: string;
    password: string;
}> {

    constructor(props) {
        super(props);

        this.state = {
            email: '',
            password: ''
        };
    }

    onGatewaySubmit = (event) => {
        event.preventDefault();

        const { requestAuthenticationAsync } = this.props;
        const { email, password } = this.state;

        requestAuthenticationAsync(email, password).then(() => {
            console.log('done');
        });
    };

    onEmailValueChange = (event) => {

        this.setState({
            email: event.target.value
        });
    };

    onPasswordValueChange = (event) => {
        this.setState({
            password: event.target.value
        });
    };

    render() {
        return (
            <div id='gateway'>
                <form onSubmit={ this.onGatewaySubmit }>
                    <input
                        className='email'
                        onChange={ this.onEmailValueChange }
                        placeholder='email'
                        type='text' />
                    <input
                        className='password'
                        onChange={ this.onPasswordValueChange }
                        placeholder='password'
                        type='password' />
                    <input type='submit' value='Submit' />
                </form>
            </div>
        );
    }
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Gateway));

我收到以下错误:

TS2339: Property 'then' does not exist on type '(dispatch: ThunkDispatch<IState, undefined, IAction>) => Promise<void>'.

什么给了?在这种情况下如何让 TypeScript 满意,以便我可以使用带有 .then 的 Promise?

【问题讨论】:

    标签: javascript reactjs typescript redux react-redux


    【解决方案1】:

    问题的根本原因是redux-thunk 是由redux 执行的中间件,因此它正在调用函数(thunk)并返回值。但是,TypeScript 并没有“意识到”正在发生的事情,因此无法正确输入(无需额外工作)。

    redux-thunk 包(此时)实际上带有类型定义。然而,它的类型定义有许多重大改进,但没有发布。 It sounds like 在 3.0 版本中它们将被删除并移至 DefinedTyped(可通过 @types/redux-thunk 安装)。

    但在那之前,您可以自己设置类型。如果你 compare what is released todaywhat is in the repo 相比,类型定义相对更多。

    要使用这些(在新版本的redux-thunk 或DefiniteTyped 发布之前),您可以使用以下内容创建类型文件(例如:types.d.ts):

    import { ActionCreatorsMapObject } from "redux";
    import { ThunkAction } from "redux-thunk";
    
    /**
     * Redux behaviour changed by middleware, so overloads here
     */
    declare module "redux" {
      /**
       * Overload for bindActionCreators redux function, returns expects responses
       * from thunk actions
       */
      function bindActionCreators<
        TActionCreators extends ActionCreatorsMapObject<any>
      >(
        actionCreators: TActionCreators,
        dispatch: Dispatch
      ): {
        [TActionCreatorName in keyof TActionCreators]: ReturnType<
          TActionCreators[TActionCreatorName]
        > extends ThunkAction<any, any, any, any>
          ? (
              ...args: Parameters<TActionCreators[TActionCreatorName]>
            ) => ReturnType<ReturnType<TActionCreators[TActionCreatorName]>>
          : TActionCreators[TActionCreatorName]
      };
    }
    

    这是直接从今天的回购中提取的。如果您需要更多,您可以复制整个文件,但这应该可以解决您的问题。

    然后,更新您对bindActionCreators 的调用以传递对象并推断这些类型(这对于mapStateToProps 并不是绝对必要的,但我发现避免“双重”输入更容易一些):

    type DispatchProps = ReturnType<typeof mapDispatchToProps>;
    const mapDispatchToProps = dispatch => {
      return bindActionCreators({ requestAuthenticationAsync }, dispatch);
    };
    
    type StateProps = ReturnType<typeof mapStateToProps>;
    const mapStateToProps = state => ({
      authenticated: state
    });
    
    type Props = DispatchProps & StateProps;
    
    class Gateway extends React.Component<Props> {
      // ...
    }
    

    这些类型可以更新,但是对于今天redux-thunk repo 中的类型,他们希望bindActionCreators 的第一个参数是一个对象(尽管the docs say it can be either a function as you were using or an object)通过查看TActionCreators extends ActionCreatorsMapObject&lt;any&gt;

    现在应该可以正确键入 this.props.requestAuthenticationAsync 以在您的组件中使用。

    onGatewaySubmit = event => {
      event.preventDefault();
    
      const { requestAuthenticationAsync } = this.props;
      const { email, password } = this.state;
    
      // Type:
      //   (email: string, password: string) => Promise<void>
      requestAuthenticationAsync(email, password).then(() => {
        console.log("done");
      });
    };
    

    【讨论】:

      【解决方案2】:

      由于不会写cmets,所以想检查一下我的理解并离开这里。

      如果你像这样在代码的函数onGatewaySubmit 中调用requestAuthenticationAsync

      requestAuthenticationAsync(email, password).then(() => {
          console.log('done');
      });
      

      我觉得你在调用如下的调度函数

      dispatch(requestAuthenticationAsync)(email, password).then(() => {
        // navigate somewhere
      });
      

      没想到你写的那样

      dispatch(requestAuthenticationAsync('email', 'password')).then(() => {
        // navigate somewhere
      });
      

      这可能是题外话,但你能解释一下流程吗?

      【讨论】:

        【解决方案3】:

        这实际上是一个我一定忽略的非常简单的解决方案。特定道具的组件类型错误。

        代替:

        requestAuthenticationAsync: typeof requestAuthenticationAsync;
        

        我改成:

        requestAuthenticationAsync: ReturnType<requestAuthenticationAsync>;
        

        这能够捕获(email: string, password: string) =&gt; Promise&lt;void&gt; 类型并且编译器停止抱怨并且一切正常。

        【讨论】: