简介
在今天的前端框架世界中,一个常见的难题是知道何时以及如何采取某些异步行动,例如将数据持久化到后端。如果我们使用的是Redux这样的状态管理库,我们可能会更加困惑,在没有Redux代码的情况下,我们可以把这个逻辑放在哪里。
一个具体的场景
为了这篇博文的目的,让我们假设我们正在使用React和Redux,并希望定期将我们的状态数据保存到后端。我们选择使用debouncing来做这件事,这意味着我们想在我们的状态在一定时间内没有变化后执行保存动作。
考虑我们的选择
那么,在使用React和Redux时,我们有哪些选择?我认为下面的列表涵盖了它。
- 在一个组件中进行--有一个组件订阅我们的状态,当它渲染时,进行解压/保存。
- 在redux动作创建器中进行- 使用类似thunk中间件的东西,在派发相关动作之前,在动作创建器中触发debounce函数。
- 在reducer中进行- 当你在reducer中更新你的网站数据时,调用debounce函数。(见下面的注释,为什么我认为这个选项不好)。
- 在Redux中间件中进行--创建一个中间件,在你的状态发生变化时,运行debounce函数。
注意:我认为除了在还原器中执行保存**之外,**所有这些实际上都是合法的方式。还原器真的应该是纯函数,在还原器中执行数据获取是一个副作用。
为什么我喜欢中间件的方法
正如我在上面提到的,我认为这些方法中的大多数都能正常工作,但我特别喜欢中间件的方法。它很好地隔离了你的保存代码,可以选择性地定义哪些动作会导致保存开始,如果你还没有使用thunk中间件,则不需要安装它,也不需要你包含一个只为处理保存而存在的组件。
实现
首先,我们可以创建一个saveDebounce ,这个函数将被我们的中间件调用。为了实现调试,我们将使用setTimeout 和clearTimeout 。
let saveTimer;
let debounceTime = 10000; // 10 seconds
const saveDebounce = (data) => {
if (saveTimer) {
clearTimeout(saveTimer);
}
saveTimer = setTimeout(() => {
// Use request library of choice here
fetch('my-api-endpoint', {
method: 'POST',
body: JSON.stringify(data),
});
}, debounceTime);
};
接下来是实际的中间件,这很简单。
export const dataSaver = (store) => (next) => (action) => {
saveDebounce(store.getState());
return next(action);
};
当用户在修改状态时,saveDebounce 函数将清除以前的超时并开始一个新的超时。只有当用户在10秒内没有改变状态时,我们的fetch 才会被真正调用。
最后,我们需要向Redux注册我们的中间件。这是在我们创建store 的时候完成的。
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { dataSaver } from '../middleware/dataSaver';
const allReducers = combineReducers(reducers);
const store = createStore(allReducers, applyMiddleware(dataSaver));
一些优化
上面的代码应该能让你很好的开始,但我们可以做一些优化。
让我们停止频繁地调用getState
每次在我们的store 上调用getState 是不必要的,而且可能很昂贵。让我们只在我们真正执行fetch 的时候才这样做。
let saveTimer;
let debounceTime = 10000;
const saveDebounce = (store) => {
if (saveTimer) {
clearTimeout(saveTimer);
}
saveTimer = setTimeout(() => {
fetch('my-api-endpoint', {
method: 'POST',
body: JSON.stringify(store.getState()),
});
}, debounceTime);
};
export const dataSaver = (store) => (next) => (action) => {
saveDebounce(store);
return next(action);
};
当然,这意味着我们的saveDebounce 函数必须知道商店的getState 方法。我认为这种权衡是值得的,因为它可以提高性能。
让我们只保存我们状态的一部分
我们似乎不太可能真的想把整个状态对象保存到后端。更有可能的是,我们只想保存状态对象的一部分,它只被一个或多个动作更新。
让我们假设我们只想在状态的userDetails 部分改变时保存数据。也许我们知道这只发生在UPDATE_USER_DETAILS 动作被调度的时候。相应地,我们可以做如下改变。
let saveTimer;
let debounceTime = 10000;
const saveDebounce = (store) => {
if (saveTimer) {
clearTimeout(saveTimer);
}
saveTimer = setTimeout(() => {
fetch('my-api-endpoint', {
method: 'POST',
body: JSON.stringify(store.getState().userDetails),
});
}, debounceTime);
};
export const dataSaver = (store) => (next) => (action) => {
if (action.type === 'UPDATE_USER_DETAILS') {
saveDebounce(store);
}
return next(action);
};
现在,我们只考虑在UPDATE_USER_DETAILS 动作被派发时触发保存事件。此外,状态的其他部分可以在不取消我们的去抖动的情况下进行更新!