React saga 生命周期

React saga lifecycle

以下是我的登录传奇片段:

export function* loginFlow() {
  while (true) {
    const request = yield take(LOGIN_REQUEST);
    const { username, password } = request.data;

    const authResp = yield call(authorize, { username, password });
    if (authResp) {
      yield put({ type: SET_AUTH, newAuthState: true }); // User is logged in (authorized)
      yield put({ type: CHANGE_FORM, newFormState: { username: '', password: '' } }); // Clear form
      forwardTo('/home'); // Go to dashboard page
    }
  }
}

这个 saga 在我的 LoginContainer 中。现在每次我进入登录屏幕并加载登录容器时,都会产生一个新的 saga "process",所以每次我重新访问登录屏幕时,我都会收到越来越多的请求进入我的登录 API我单击 "login" 按钮。

我能否在组件销毁时以某种方式销毁 saga?

编辑:这是取消传奇的尝试:

export function* loginFlow() {
  const request = yield take(LOGIN_REQUEST);
  const { username, password } = request.data;

  const authResp = yield call(authorize, { username, password });
  if (authResp) {
    yield put({ type: SET_AUTH, newAuthState: true }); // User is logged in (authorized)
    yield put({ type: CHANGE_FORM, newFormState: { username: '', password: '' } }); // Clear form
    forwardTo('/home'); // Go to dashboard page
  }
}

export function* watchLogin() {
  // or takeEvery (according to your business logic)
  yield* takeEvery(LOGIN_REQUEST, loginFlow);
}

export function* root() {
  const watchers = [
    yield fork(watchLogin),
  ];

  // Cancel all watchers on location change
  yield take(LOCATION_CHANGE);

  watchers.forEach(function(watcher) {
    console.log("cancelling watcher")
    cancel(watcher)
  });
}

// All sagas to be loaded
export default [
  root,
];

我必须在初始加载时点击登录按钮两次,以便发出 API 请求,然后我遇到与以前相同的行为 - saga 没有被取消请求不断增加。

这是我的组件:

export class Login extends React.Component {
  constructor(props) {
    super(props);
    this.login = this.login.bind(this);
    this.onChange = this.onChange.bind(this);
  }

  onChange(newFormState) {
    this.props.dispatch(changeForm(newFormState));
  }

  login(username, password) {
    console.log("dispatching login request")
    this.props.dispatch(loginRequest({ username, password }));
  }

  render() {
    const { formState, currentlySending, error } = this.props;

    return (
      <Wrapper>
        <LoginForm onChange={this.onChange} data={formState} error={error} currentlySending={currentlySending} btnText={messages.btnText} usernameText={messages.usernameText} passwordText={messages.passwordText} onSubmit={this.login} />
      </Wrapper>
    );
  }
}

这是我加载 sagas 的方式 (routes.js):

export default function createRoutes(store) {
  // create reusable async injectors using getAsyncInjectors factory
  const { injectReducer, injectSagas } = getAsyncInjectors(store);

  return [
    {
      path: '/login',
      name: 'login',
      getComponent(nextState, cb) {
        const importModules = Promise.all([
          System.import('containers/Login/reducer'),
          System.import('containers/Login/sagas'),
          System.import('containers/Login'),
        ]);

        const renderRoute = loadModule(cb);

        importModules.then(([reducer, sagas, component]) => {
          injectReducer('login', reducer.default);
          injectSagas(sagas.default);
          renderRoute(component);
        });

    importModules.catch(errorLoading);
  },
...

我认为这是导致问题的 forwardTo 函数:

function forwardTo(location) {
  browserHistory.push(location);
}

如果我在 saga 的 while 循环中调用此函数之前中断,则 saga 会自动销毁并且一切正常。

嗯,是的,你可以在 component 销毁时销毁你的 saga-watchers,这有两种方法:

  1. 添加到组件挂载和卸载的操作,然后在您的 React 组件的方法中,componentWillMount,调度安装操作并在 componentWillUnmount 调度卸载操作并处理您的相应的传奇。

  2. 你会在 page/container 上摧毁你的 saga-watchers 而不是组件破坏,你只需要听 LOCATION_CHANGE 动作(也许来自 react-router-redux 如果你使用它)而不是 COMPONENT_UNMOUNT 行动(如上面第一种方法所述


这里是在你的 saga 中应用 第二种方法 的示例,还有对你的 loginFlow saga 生成器的一些修改:

import {call, cancel, fork, put, take} from 'redux-saga/effects';
import {takeEvery, takeLatest} from 'redux-saga';
import {LOCATION_CHANGE} from 'react-router-redux';
import {
  LOGIN_REQUEST,
  SET_AUTH,
  CHANGE_FORM,
} from './constants';

export function* loginFlow() {
  const request = yield take(LOGIN_REQUEST);
  const { username, password } = request.data;

  const authResp = yield call(authorize, { username, password });
  if (authResp) {

    // User is logged in (authorized)
    yield put({ type: SET_AUTH, newAuthState: true });

    // Clear form
    yield put({ type: CHANGE_FORM, newFormState: {
      username: '',
      password: ''
    } });

    forwardTo('/home'); // Go to dashboard page
  }
}

export function* watchLogin() {
  // or takeEvery (according to your business logic)
  yield* takeLatest(LOGIN_REQUEST, loginFlow);
}

export function* root() {
  const watchers = [
    yield fork(watchLogin),
  ];

  // Cancel all watchers on location change
  yield take(LOCATION_CHANGE);
  watchers.forEach(cancel);
}

// All sagas to be loaded
export default [
  root,
];

现在如上所示,我们使用一些 redux-saga/effectsfork saga watchers 使用 component/container 然后使用 cancel 摧毁 LOCATION_CHANGE.

上的观察者

此外,您需要在 LoginComponent 中对 buttonClick 发送 LOGIN_REQUEST 操作。


如有不明之处,请多多指教。

redux-saga 文档 here 中阅读有关任务取消的更多信息。