【问题标题】:How to subscribe to a Redux action in a React component如何在 React 组件中订阅 Redux 操作
【发布时间】:2018-10-01 12:10:31
【问题描述】:

我试图弄清楚如何在 React 组件中订阅 Redux 操作。 我在 Google 上没有找到任何东西,所以我在这里打开一个问题,看看是否有人可以帮助我。

当用户尝试登录时,我发送一个loginRequestAction(),然后我使用redux-saga 处理它(查看下面我的saga.js 文件),最后如果一切正常,我发送LOGIN_REQUEST_SUCCESS 操作。

我想在这里做的是找到一种方法来订阅我的 React 组件中的 LOGIN_REQUEST_SUCCESS 操作,这样一旦收到操作,我就可以更新我的 React 组件本地状态并使用 history.push() 将用户重定向到仪表板页面.

这是我的组件代码:

/**
 *
 * LoginPage
 *
 */

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import { Container, Row, Col, Button, Alert } from 'reactstrap';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import { Link } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
import { ReactstrapInput } from 'reactstrap-formik';
import reducer from './reducer';
import saga from './saga';
import { loginRequestAction } from './actions';
import { makeSelectLoginPage } from './selectors';
import { makeSelectIsLogged } from '../Auth/selectors';

const LoginSchema = Yup.object().shape({
  userIdentifier: Yup.string().required('Required'),
  password: Yup.string().required('Required'),
});

/* eslint-disable react/prefer-stateless-function */
export class LoginPage extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      formMsg: {
        color: '',
        text: '',
      },
    };
  }

  componentDidMount() {
    const { history, isLogged } = this.props;

    if (isLogged) history.push('/dashboard/index');
  }

  render() {
    const { formMsg } = this.state;
    const { onLoginFormSubmit } = this.props;

    return (
      <div>
        <Helmet>
          <title>Sign in</title>
          <meta name="description" content="Description of LoginPage" />
        </Helmet>
        <Container className="auth-container">
          <div className="form-page">
            <Row>
              <Col className="text-center">
                <img
                  className="mb-4"
                  src="https://getbootstrap.com/docs/4.1/assets/brand/bootstrap-solid.svg"
                  alt=""
                  width="72"
                  height="72"
                />
              </Col>
            </Row>
            <Row>
              <Col className="text-center">
                {' '}
                <h1 className="h3 mb-3 font-weight-normal">Authentication</h1>
                <Alert
                  color={formMsg.color}
                  role="alert"
                  className={formMsg.text ? '' : 'd-none'}
                >
                  <strong>{formMsg.text}</strong>
                </Alert>
              </Col>
            </Row>

            <Formik
              initialValues={{
                userIdentifier: '',
                password: '',
              }}
              validationSchema={LoginSchema}
              onSubmit={onLoginFormSubmit}
            >
              {({ isSubmitting }) => (
                <Form>
                  <Field
                    component={ReactstrapInput}
                    name="userIdentifier"
                    type="userIdentifier"
                    placeholder="john@acme.com"
                    label="E-mail address"
                  />
                  <Field
                    component={ReactstrapInput}
                    name="password"
                    type="password"
                    placeholder="Password"
                    label="Password"
                  />
                  <div>
                    <Button
                      type="submit"
                      block
                      size="lg"
                      color="primary"
                      disabled={isSubmitting}
                    >
                      <FontAwesomeIcon
                        pulse
                        icon={faSpinner}
                        className={isSubmitting ? 'mr-2' : 'd-none'}
                      />
                      Log in to access
                    </Button>
                  </div>
                </Form>
              )}
            </Formik>

            <Link to="/auth/reset">
              <Button size="sm" color="secondary" block className="mt-2">
                Forgot password?
              </Button>
            </Link>
            <p className="mt-5 mb-3 text-center">
              <Link to="/auth/register">
                Don&#39;t have an account? Sign up
              </Link>
            </p>
          </div>
        </Container>
      </div>
    );
  }
}

LoginPage.propTypes = {
  isLogged: PropTypes.bool,
  history: PropTypes.object,
  onLoginFormSubmit: PropTypes.func,
};

const mapStateToProps = createStructuredSelector({
  loginpage: makeSelectLoginPage(),
  isLogged: makeSelectIsLogged(),
});

function mapDispatchToProps(dispatch) {
  return {
    onLoginFormSubmit: values => dispatch(loginRequestAction(values)),
  };
}

const withConnect = connect(
  mapStateToProps,
  mapDispatchToProps,
);

const withReducer = injectReducer({ key: 'loginPage', reducer });
const withSaga = injectSaga({ key: 'loginPage', saga });

export default compose(
  withReducer,
  withSaga,
  withConnect,
)(LoginPage);

这是我的 saga.js 文件:

import { put, call, takeLatest } from 'redux-saga/effects';
import {
  LOGIN_REQUEST,
  LOGIN_REQUEST_SUCCESS,
  LOGIN_REQUEST_FAILED,
} from './constants';
import { AuthApi } from '../../api/auth.api';

export function* loginRequest(action) {
  const { userIdentifier, password } = action.values;

  try {
    const tokens = yield call(AuthApi.login, userIdentifier, password);
    yield put({ type: LOGIN_REQUEST_SUCCESS, tokens });
  } catch (err) {
    let errMsg;

    switch (err.status) {
      case 403:
        errMsg = 'Invalid credentials';
        break;
      case 423:
        errMsg = 'Account desactivated';
        break;
      default:
        errMsg = `An server error ocurred. We have been notified about this error, our devs will fix it shortly.`;
        break;
    }

    yield put({ type: LOGIN_REQUEST_FAILED, errMsg });
  }
}

export default function* defaultSaga() {
  yield takeLatest(LOGIN_REQUEST, loginRequest);
}

附:我来自这个 GitHub 问题:https://github.com/react-boilerplate/react-boilerplate/issues/2360(请查看它,因为有可能的解决方案,但恕我直言,我认为这不是正确的方法)。

【问题讨论】:

  • 如果有人来自 Google 试图解决类似的问题并阅读此内容,我所做的就是遵循 Jed Richards 的建议。我还在做更多的事情:当一个组件从 DOM 中删除时,我 dispatch componentWillUnmount 中的 reset 操作以清除 Redux 状态,因此它释放不会在其他部分使用的状态的应用程序,是无用的。

标签: javascript reactjs redux redux-saga react-boilerplate


【解决方案1】:

通常,您永远不会“订阅”组件中的 Redux 操作,因为它破坏了 React 的一些声明性优势。大多数情况下,您只需 connect() 通过 props 进入 Redux 状态,然后简单地根据这些进行渲染。订阅动作,然后调用路由函数作为响应是命令式编程,而不是声明式编程,因此您需要在组件中避免这种情况。

因此,在使用 sagas 时,您有多种选择来应对这种情况。与其从 saga 发出 LOGIN_REQUEST_SUCCESS ,不如从 saga 内部推送一条新路由:

yield call(push, '/foo')

或者在你的 Redux 状态中维护一个 authenticated: boolean 属性,当用户登录时将其翻转为 saga 中的 true,然后使用它有条件地在你的组件中渲染一个 react-router &lt;Redirect /&gt;(或也许有条件地呈现您的应用程序的整个经过身份验证的树,这取决于您)。

【讨论】:

  • 这是错误的。它不会破坏 React 的声明式好处。在组件中进行 fetch 是订阅大小为 1 的流。当该流发出时,会执行 setState 来更新组件。使用 connect 订阅无限大小的状态流,connect 本身调用setState 来更新组件。同样,人们可以订阅 redux 的动作流并做一些副作用,它根本不会破坏 react 的任何好处。仅仅因为 react-redux 没有提供 API 并不会出错。
  • 我从来没有说过在 React 组件中订阅 anything 是错误的 - 我说过订阅 actions 是错误的。从 redux 操作创建者或 thunk 返回 fetch promise 并在组件中使用它而不是通过商店进行正确的往返是一个常见的初学者错误,所以我想引导 OP 远离该术语。显然,一般地订阅事物并在组件中产生副作用是可以的——我的回答与 redux+saga 模式密切相关。
  • 这就是我所指出的。从组件内部订阅 redux 的操作是完全可以的。我谈到了其他副作用和其他可订阅渠道,仅仅是因为它们与订阅操作的差异为零。任何人都必须提出一个非常好的论点和支持该论点的证据。
【解决方案2】:

说实话,我觉得github上的答案很有道理。您可以使用 reducer 在 redux 中订阅 dispatch action。以及负责设置 redux 状态并使用 connect 函数将其返回给您的组件的 reducer。

从我的角度来看,添加一个名为“loginSuccess”或其他任何标志属性并传递给您的组件不会导致性能问题,因为这是 redux 与 react 的工作方式

【讨论】:

    猜你喜欢
    • 2018-11-03
    • 2017-08-03
    • 2021-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-29
    相关资源
    最近更新 更多