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,这有两种方法:
添加到组件挂载和卸载的操作,然后在您的 React 组件的方法中,componentWillMount
,调度安装操作并在 componentWillUnmount
调度卸载操作并处理您的相应的传奇。
你会在 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/effects
来 fork saga watchers 使用 component/container 然后使用 cancel 摧毁 LOCATION_CHANGE
.
上的观察者
此外,您需要在 LoginComponent 中对 buttonClick 发送 LOGIN_REQUEST
操作。
如有不明之处,请多多指教。
从 redux-saga
文档 here 中阅读有关任务取消的更多信息。
以下是我的登录传奇片段:
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,这有两种方法:
添加到组件挂载和卸载的操作,然后在您的 React 组件的方法中,
componentWillMount
,调度安装操作并在componentWillUnmount
调度卸载操作并处理您的相应的传奇。你会在 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/effects
来 fork saga watchers 使用 component/container 然后使用 cancel 摧毁 LOCATION_CHANGE
.
此外,您需要在 LoginComponent 中对 buttonClick 发送 LOGIN_REQUEST
操作。
如有不明之处,请多多指教。
从 redux-saga
文档 here 中阅读有关任务取消的更多信息。