Redux + TypeScript и mapDispatchToProps - PullRequest
       0

Redux + TypeScript и mapDispatchToProps

1 голос
/ 15 октября 2019

Это должно быть легко, но это ускользает от меня на данный момент, но я чувствую, что это должно быть простое решение. В настоящее время я использую redux с typescript и 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));
        });
    };
}

Идеальная ситуация, когда я могу вызвать это в файле .tsx, используя .then для навигации в другом месте, когда вход успешен.

Итак, это работает так, как вы ожидаете, когда я делаю что-то подобное в компоненте:

const { dispatch } = store;

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

Однако, когда я использую connect и mapDispatchToProps из react-redux, вот так:

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?

Ответы [ 3 ]

0 голосов
/ 15 октября 2019

Основной причиной проблемы является то, что redux-thunk является промежуточным программным обеспечением, которое выполняется redux, поэтому оно вызывает функцию (thunk) и возвращает значение. Однако TypeScript не «осознает», что происходит, поэтому нет способа его правильно набрать (без небольшой дополнительной работы).

Пакет redux-thunk (в настоящее время) на самом деле поставляется с определениями типов,Однако в его определениях типов произошел ряд значительных улучшений, но не было релизов. Похоже, что в версии 3.0 они будут удалены и перемещены в DefinitiveTyped (устанавливается через @types/redux-thunk).

Но до тех пор вы можете настраивать типы самостоятельно. Если вы сравните то, что выпущено сегодня против того, что находится в репо , то сравнительно больше определений типов.

Чтобы использовать их (до того, как они будут выпущены в новой версии)из 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 сегодня они ожидаютпервый аргумент bindActionCreators должен быть объектом (хотя в документах говорится, что это может быть либо функция, которую вы использовали, либо объект ), глядя на TActionCreators extends ActionCreatorsMapObject<any>.

Thisтеперь должен правильно набрать 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");
  });
};
0 голосов
/ 15 октября 2019

Это было на самом деле очень простое решение, которое я, должно быть, упустил из виду. Типы в компоненте для конкретной опоры были неправильными.

Вместо:

requestAuthenticationAsync: typeof requestAuthenticationAsync;

Я изменил его на:

requestAuthenticationAsync: ReturnType<requestAuthenticationAsync>;

Это было в состоянии захватить(email: string, password: string) => Promise<void> типа и компилятор перестал жаловаться и все заработало.

0 голосов
/ 15 октября 2019

Я хочу проверить, что я понимаю, и что я оставляю здесь, так как я не могу писать комментарии.

Если вы вызываете requestAuthenticationAsync в функции onGatewaySubmit вашего кода следующим образом;

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

Я чувствую, что вы вызываете отправленную функцию, как показано ниже

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

Не ожидается, как вы написали

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

Это может быть не по темено не могли бы вы объяснить поток?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...