如何在 React 组件中订阅 Redux 操作
How to subscribe to a Redux action in a React component
我想弄清楚如何在 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'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);
}
P.S。我来自这个 GitHub 问题:https://github.com/react-boilerplate/react-boilerplate/issues/2360(请查看它,因为有可能的解决方案,但恕我直言,我认为这不是正确的方法)。
通常你永远不会 "subscribe" 到组件中的 Redux 操作,因为它破坏了 React 的一些声明性好处。大多数时候,你只是 connect()
通过 props 进入 Redux 状态,然后简单地基于这些进行渲染。订阅动作,然后调用路由函数作为响应是命令式编程,而不是声明式,因此您需要在组件中避免这种情况。
因此,在使用 sagas 时,您可以针对此类场景进行多种选择。与其从你的 saga 中发出 LOGIN_REQUEST_SUCCESS
,不如从 saga 本身中推送一条新路线:
yield call(push, '/foo')
或者在你的 Redux state 中维护一个 authenticated: boolean
属性,当用户登录时将它翻转到 saga 中的 true
,然后用它有条件地渲染一个 react -router <Redirect />
在你的组件中(或者可能有条件地渲染你的应用程序的整个认证树,由你决定)。
老实说,我觉得github中的答案很有道理。您使用 reducer 订阅 redux 中的调度操作。和 reducer 负责设置 redux 状态并 return 通过使用 connect 函数将其发送到您的组件。
添加一个名为“loginSuccess”或其他名称的标志 属性 并传递给您的组件不会导致性能问题,因为从我的角度来看,这是 redux 与 react 一起工作的方式
我想弄清楚如何在 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'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);
}
P.S。我来自这个 GitHub 问题:https://github.com/react-boilerplate/react-boilerplate/issues/2360(请查看它,因为有可能的解决方案,但恕我直言,我认为这不是正确的方法)。
通常你永远不会 "subscribe" 到组件中的 Redux 操作,因为它破坏了 React 的一些声明性好处。大多数时候,你只是 connect()
通过 props 进入 Redux 状态,然后简单地基于这些进行渲染。订阅动作,然后调用路由函数作为响应是命令式编程,而不是声明式,因此您需要在组件中避免这种情况。
因此,在使用 sagas 时,您可以针对此类场景进行多种选择。与其从你的 saga 中发出 LOGIN_REQUEST_SUCCESS
,不如从 saga 本身中推送一条新路线:
yield call(push, '/foo')
或者在你的 Redux state 中维护一个 authenticated: boolean
属性,当用户登录时将它翻转到 saga 中的 true
,然后用它有条件地渲染一个 react -router <Redirect />
在你的组件中(或者可能有条件地渲染你的应用程序的整个认证树,由你决定)。
老实说,我觉得github中的答案很有道理。您使用 reducer 订阅 redux 中的调度操作。和 reducer 负责设置 redux 状态并 return 通过使用 connect 函数将其发送到您的组件。
添加一个名为“loginSuccess”或其他名称的标志 属性 并传递给您的组件不会导致性能问题,因为从我的角度来看,这是 redux 与 react 一起工作的方式