使用 Redux Saga 更新单个状态属性的最佳实践
Best practice for updating individual state properties with Redux Saga
所以我正在使用 Redux Saga 在 React 中实现一个应用程序,我对我的特定用例的信息很少感到困惑,因为它看起来并不奇怪。很可能我使用了错误的术语或以错误的方式思考问题,因为我是 React/Redux 的新手。无论如何,我对 google 这个问题的所有尝试都受阻了,我希望能从比我更有经验的人那里得到一些见解。
我的应用程序状态有一个 userSettings
属性,它管理登录用户的一些配置选项。在应用程序的某个位置,用户可以轻按开关以禁用 "at a glance" 仪表板小部件的显示,我需要将此信息传递到后端 API 以更新他们在数据库,然后根据本次后台更新是否成功更新状态。
我的代码目前有一个针对所有用户设置更新的主要 saga,我打算通过一个更具体的 saga 来实现,特别是针对此设置,因此:
Dashboard.js
function mapStateToProps(state) {
const { userSettings } = state;
return { userSettings };
}
...
class Dashboard extends Component {
...
...
hasDashboardAtAGlanceHiddenToggle() {
const { dispatch, userSettings } = this.props;
dispatch(setHasDashboardAtAGlanceHidden(!userSettings.hasDashboardAtAGlanceHidden));
}
}
export default connect(mapStateToProps)(Dashboard);
updateUserSettingsSaga.js
import { take, put, call } from 'redux-saga/effects';
import axios from 'axios';
import {
UPDATE_USER_SETTINGS,
SET_HAS_DASHBOARD_AT_A_GLANCE_HIDDEN,
updateUserSettings,
updatedUserSettingsSuccess
} from '../../actions';
export function* setHasDashboardAtAGlanceHiddenSaga() {
const action = yield take(SET_HAS_DASHBOARD_AT_A_GLANCE_HIDDEN);
const newValue = action.data;
//QUESTION HERE -- how to get full object to pass to updateUserSettings
yield put(updateUserSettings(stateObjectWithNewValuePopulated));
}
export default function* updateUserSettingsSaga(data) {
yield take(UPDATE_USER_SETTINGS);
try {
const response = yield call(axios.put, 'http://localhost:3001/settings', data);
yield put(updatedUserSettingsSuccess(response.data));
} catch (e) {
yield put(updatedUserSettingsFailure());
}
}
如代码中所述,我的问题是,我不确定 where/how 将更新值合并到状态中的逻辑是否应该发生。据我所知,我有三个选择:
在调度初始动作之前在组件中构建更新状态,即:
hasDashboardAtAGlanceHiddenToggle() {
const { dispatch, userSettings } = this.props;
const newState = Object.assign({}, userSettings , {
hasDashboardAtAGlanceHidden: !userSettings.hasDashboardAtAGlanceHidden
});
dispatch(setHasDashboardAtAGlanceHidden(userSettings));
}
}
使用 redux-saga 的 select
效果并在更具体的初始 saga 中构建完整状态对象,即:
export function* setHasDashboardAtAGlanceHiddenSaga() {
const action = yield take(SET_HAS_DASHBOARD_AT_A_GLANCE_HIDDEN);
const newValue = action.data;
const existingState = select(state => state.userSettings);
const updatedState = Object.assign({}, existingState, {
hasDashboardAtAGlanceHidden: newValue
});
yield put(updateUserSettings(updatedState));
}
在更新之前检索用户设置对象的服务器副本,即:
export default function* updateUserSettingsSaga() {
const action = yield take(UPDATE_USER_SETTINGS);
try {
const current = yield call(axios.get, 'http://localhost:3001/settings');
const newState = Object.assign({}, current.data, action.data);
const response = yield call(axios.put, 'http://localhost:3001/settings', newState);
yield put(updatedUserSettingsSuccess(response.data));
} catch (e) {
yield put(updatedUserSettingsFailure());
}
}
所有这些(我认为)都将作为选项工作,但我一点也不清楚在 Redux Saga 的背景下 idiomatic/accepted/preferable 方法是什么,而且令人困惑的是缺乏在与外部 API 交互时使用 POST/PUT 而不是 GET 的示例(至少我已经能够找到)。任何帮助或指导将不胜感激——即使只是我以错误的方式思考这个问题。 :D
GET/PUT/POST
方面与问题无关。总的来说,您的问题实际上归结为 the frequently asked question "How do I split logic between action creators and reducers?"。引用该答案:
There's no single clear answer to exactly what pieces of logic should go in a reducer or an action creator. Some developers prefer to have “fat” action creators, with “thin” reducers that simply take the data in an action and blindly merge it into the corresponding state. Others try to emphasize keeping actions as small as possible, and minimize the usage of getState() in an action creator. (For purposes of this question, other async approaches such as sagas and observables fall in the "action creator" category.)
There are some potential benefits from putting more logic into your reducers. It's likely that the action types would be more semantic and more meaningful (such as "USER_UPDATED" instead of "SET_STATE"). In addition, having more logic in reducers means that more functionality will be affected by time travel debugging.
This comment sums up the dichotomy nicely:
Now, the problem is what to put in the action creator and what in the reducer, the choice between fat and thin action objects. If you put all the logic in the action creator, you end up with fat action objects that basically declare the updates to the state. Reducers become pure, dumb, add-this, remove that, update these functions. They will be easy to compose. But not much of your business logic will be there. If you put more logic in the reducer, you end up with nice, thin action objects, most of your data logic in one place, but your reducers are harder to compose since you might need info from other branches. You end up with large reducers or reducers that take additional arguments from higher up in the state.
我前阵子也写过my own thoughts on "thick and thin reducers"
因此,最终取决于您更喜欢如何构建逻辑。
所以我正在使用 Redux Saga 在 React 中实现一个应用程序,我对我的特定用例的信息很少感到困惑,因为它看起来并不奇怪。很可能我使用了错误的术语或以错误的方式思考问题,因为我是 React/Redux 的新手。无论如何,我对 google 这个问题的所有尝试都受阻了,我希望能从比我更有经验的人那里得到一些见解。
我的应用程序状态有一个 userSettings
属性,它管理登录用户的一些配置选项。在应用程序的某个位置,用户可以轻按开关以禁用 "at a glance" 仪表板小部件的显示,我需要将此信息传递到后端 API 以更新他们在数据库,然后根据本次后台更新是否成功更新状态。
我的代码目前有一个针对所有用户设置更新的主要 saga,我打算通过一个更具体的 saga 来实现,特别是针对此设置,因此:
Dashboard.js
function mapStateToProps(state) {
const { userSettings } = state;
return { userSettings };
}
...
class Dashboard extends Component {
...
...
hasDashboardAtAGlanceHiddenToggle() {
const { dispatch, userSettings } = this.props;
dispatch(setHasDashboardAtAGlanceHidden(!userSettings.hasDashboardAtAGlanceHidden));
}
}
export default connect(mapStateToProps)(Dashboard);
updateUserSettingsSaga.js
import { take, put, call } from 'redux-saga/effects';
import axios from 'axios';
import {
UPDATE_USER_SETTINGS,
SET_HAS_DASHBOARD_AT_A_GLANCE_HIDDEN,
updateUserSettings,
updatedUserSettingsSuccess
} from '../../actions';
export function* setHasDashboardAtAGlanceHiddenSaga() {
const action = yield take(SET_HAS_DASHBOARD_AT_A_GLANCE_HIDDEN);
const newValue = action.data;
//QUESTION HERE -- how to get full object to pass to updateUserSettings
yield put(updateUserSettings(stateObjectWithNewValuePopulated));
}
export default function* updateUserSettingsSaga(data) {
yield take(UPDATE_USER_SETTINGS);
try {
const response = yield call(axios.put, 'http://localhost:3001/settings', data);
yield put(updatedUserSettingsSuccess(response.data));
} catch (e) {
yield put(updatedUserSettingsFailure());
}
}
如代码中所述,我的问题是,我不确定 where/how 将更新值合并到状态中的逻辑是否应该发生。据我所知,我有三个选择:
在调度初始动作之前在组件中构建更新状态,即:
hasDashboardAtAGlanceHiddenToggle() { const { dispatch, userSettings } = this.props; const newState = Object.assign({}, userSettings , { hasDashboardAtAGlanceHidden: !userSettings.hasDashboardAtAGlanceHidden }); dispatch(setHasDashboardAtAGlanceHidden(userSettings)); }
}
使用 redux-saga 的
select
效果并在更具体的初始 saga 中构建完整状态对象,即:export function* setHasDashboardAtAGlanceHiddenSaga() { const action = yield take(SET_HAS_DASHBOARD_AT_A_GLANCE_HIDDEN); const newValue = action.data; const existingState = select(state => state.userSettings); const updatedState = Object.assign({}, existingState, { hasDashboardAtAGlanceHidden: newValue }); yield put(updateUserSettings(updatedState)); }
在更新之前检索用户设置对象的服务器副本,即:
export default function* updateUserSettingsSaga() { const action = yield take(UPDATE_USER_SETTINGS); try { const current = yield call(axios.get, 'http://localhost:3001/settings'); const newState = Object.assign({}, current.data, action.data); const response = yield call(axios.put, 'http://localhost:3001/settings', newState); yield put(updatedUserSettingsSuccess(response.data)); } catch (e) { yield put(updatedUserSettingsFailure()); } }
所有这些(我认为)都将作为选项工作,但我一点也不清楚在 Redux Saga 的背景下 idiomatic/accepted/preferable 方法是什么,而且令人困惑的是缺乏在与外部 API 交互时使用 POST/PUT 而不是 GET 的示例(至少我已经能够找到)。任何帮助或指导将不胜感激——即使只是我以错误的方式思考这个问题。 :D
GET/PUT/POST
方面与问题无关。总的来说,您的问题实际上归结为 the frequently asked question "How do I split logic between action creators and reducers?"。引用该答案:
There's no single clear answer to exactly what pieces of logic should go in a reducer or an action creator. Some developers prefer to have “fat” action creators, with “thin” reducers that simply take the data in an action and blindly merge it into the corresponding state. Others try to emphasize keeping actions as small as possible, and minimize the usage of getState() in an action creator. (For purposes of this question, other async approaches such as sagas and observables fall in the "action creator" category.)
There are some potential benefits from putting more logic into your reducers. It's likely that the action types would be more semantic and more meaningful (such as "USER_UPDATED" instead of "SET_STATE"). In addition, having more logic in reducers means that more functionality will be affected by time travel debugging.
This comment sums up the dichotomy nicely:
Now, the problem is what to put in the action creator and what in the reducer, the choice between fat and thin action objects. If you put all the logic in the action creator, you end up with fat action objects that basically declare the updates to the state. Reducers become pure, dumb, add-this, remove that, update these functions. They will be easy to compose. But not much of your business logic will be there. If you put more logic in the reducer, you end up with nice, thin action objects, most of your data logic in one place, but your reducers are harder to compose since you might need info from other branches. You end up with large reducers or reducers that take additional arguments from higher up in the state.
我前阵子也写过my own thoughts on "thick and thin reducers"
因此,最终取决于您更喜欢如何构建逻辑。