写给自己,个人对页面接口太多是否会影响性能得一个思考和自己得解决方案,我的兄弟,你可能觉得没有什么意义罢了🤦♂️🤦♂️🤦♂️
铺垫 😊
我有个 Dashboard 页面,大约 7 个接口请求,页面被拆成 6 个子组件。每个子组件在自己的 useEffect 里独立发请求——从业务角度来看这些请求本质上是并行的。我在思考要不要把它们统一合并到 store 层(用 Promise.all 并行),以期望获得性能上的提升。实践中我选择了混合策略:把无参数、稳定、页面初始需要的数据合并到 store 并行获取(Promise.all),把按参数、按交互、依赖上游数据的请求留给组件自身 useEffect(按需、串联或防抖)。
为什么这是个值得深思的问题 🤔
- 并行请求听起来简单,但还要考虑:浏览器并发连接限制(HTTP/1.1 下同域大概 ~6 条),服务端/代理能力,接口间依赖关系,失败策略,用户体验(是否能优雅降级)等。
- HTTP/2/3 大幅缓解了连接限制,但并不等于可以随意开大量小请求(还有渲染阻塞、带宽、CPU 等因素)。
Promise.all优雅,但单点失败会让整组数据被判为失败(可用allSettled或单独的错误处理来缓解)。
我思考得问题代码(我是react代码):
//并行请求
useEffect(() => {
fetchData1();
fetchData2();
fetchData3();
}, []);
//并行请求
useEffect(() => {
const fetchAll = async () => {
const [res1, res2, res3] = await Promise.all([
fetchData1(),
fetchData2(),
fetchData3(),
]);
console.log(res1, res2, res3);
};
fetchAll();
}, []);
try {
const [res1, res2, res3] = await Promise.all([...]);
} catch (err) {
console.error("其中一个请求出错:", err);
}
//串行串联请求得
useEffect(() => {
const fetchAll = async () => {
await fetchData1(); // 等1完成
await fetchData2(); // 再发2
await fetchData3(); // 最后发3
};
fetchAll();
}, []);
- 这几个接口之间是否存在串联关系,请求1是否需要等待请求2
- 接口之前得请求参数是否需要单独维护
- 是否需要统一错误状态处理和loading状态
对上述请求得方案处理步骤分析
- 我的每个接口各自独立,互不干扰,可以纯并联
tips 😉 并联请求,需要查看HTTP 协议版本(HTTP Version),如果说是http/1.1,大多数浏览器默认是同时并发能请求6个请求,多余得会进行排队,如果说是HTTP/2,理论上是可以并发无数个
开启 Protocol 列步骤(Chrome)
- 打开 Chrome 开发者工具 (
F12) → Network 面板 - 在请求列表表头空白处 右键
- 在弹出的列选项里勾选
Protocol
- 我的接口存在参数各自维护情况,我想部分组件实现内部自己维护自己得请求
- 我的接口部分需要统一得错误处理和loading状态
清楚我自己得需求后就是我的代码部分了
我在项目里的关键代码(已简化,只保留关键性代码) 😎
哥们得数据统一放在react-redux里面得仓库进行统一处理得,用了多个extraReducers
页面级合并并行请求(我用的 Promise.all)
export const fetchDashboardAll = createAsyncThunk(
"dashboard/fetchAll",
async (_, { rejectWithValue }) => {
try {
const [dashboardRes, behaviorRes, subscriptionRes] = await Promise.all([
getDashboard(),
getFunctionalBehavior(),
getAnalyseSubscription(),
]);
return {
dashboard: dashboardRes.data,
behavior: behaviorRes.data,
subscription: subscriptionRes.data,
};
} catch (err: any) {
return rejectWithValue(err.message || "请求失败");
}
}
);
时间范围与初始 state(简化)
export const sevDaysAgoStart = dayjs().tz(TZ).subtract(7, "day").startOf("day").unix();
export const todayEnd = dayjs().tz(TZ).endOf("day").unix();
const initialState: InitialState = {
loading: false,
error: null,
dashboard: null,
behavior: null,
funnel: null,
subscription: null,
params: { page: 1, page_size: 10, begin_timestamp: sevDaysAgoStart, end_timestamp: todayEnd },
table: { status: "init", data: undefined },
};
表格请求(单独 thunk)
export const getListAsync = createAsyncThunk(
"dashboard/getListAsync",
async (params) => {
const res = await getAnalyseRegionalDistribution(params);
return { data: res, params };
}
);
串联请求示例(单独获取漏斗,按日期)
export const getFunnelAsync = createAsyncThunk(
"dashboard/getFunnelAsync",
async (params: { date_ymd: string }, { rejectWithValue }) => {
try {
const res = await getAnalyseFunnel(params);
return res.data;
} catch (err: any) {
return rejectWithValue(err.message || "请求失败");
}
}
);
slice 中对并行请求的状态处理(简化)
builder
.addCase(fetchDashboardAll.pending, (state) => { state.loading = true; state.error = null; })
.addCase(fetchDashboardAll.fulfilled, (state, action) => {
state.loading = false;
state.dashboard = action.payload.dashboard;
state.behavior = action.payload.behavior;
state.subscription = action.payload.subscription;
})
.addCase(fetchDashboardAll.rejected, (state, action) => { state.loading = false; state.error = action.payload as string; });
Keep 组件关键请求逻辑(只保留要点)
useEffect(() => {
const newParams = {
...params,
begin_timestamp: sevDaysAgoStart,
end_timestamp: todayEnd,
zone: "ALL",
};
dispatch(changeParams(newParams));
dispatch(getListAsync(newParams));
}, []);
// Select 切换:某个选项触发单独请求(按需)
<Select
value={params.user_type}
onChange={(user_type) => {
dispatch(changeParams({ user_type }));
dispatch(getListAsync({ ...params, user_type }));
if (user_type === "3") {
getAnalyseRetention().then((res) => setMonthData(res.data));
}
}}
options={[...user_type, { label: "月留存", value: "3" }]}
/>
判断并发 vs 串联的决策流程(实战模板)
按下面几步问自己,结果会比较稳妥:
-
接口是否相互依赖?
- 是 -> 串联(
await a(); await b(aResult)) - 否 -> 继续下一步
- 是 -> 串联(
-
是否是页面初始必需且无参数(或参数固定)?
- 是 -> 可以考虑合并到 store,用
Promise.all并行(统一 loading、缓存、去重) - 否 -> 交给组件按需请求(
useEffect)
- 是 -> 可以考虑合并到 store,用
-
是否能容忍部分失败?
- 不能 ->
Promise.all并配合错误回退策略(或先探测接口稳定性) - 能 ->
Promise.allSettled或单独处理每个接口结果
- 不能 ->
-
是否支持 HTTP/2/3?
- 支持 -> 并行开多个请求代价小得多(多路复用)
- 不支持 -> 小心超过浏览器连接数导致排队
具体可落地的优化建议(checklist)
- ✅ 优先确定后端是否支持 HTTP/2/3(多路复用能显著改善并发)
- ✅ 对无参数 / 常用数据放在 Redux(或缓存层),避免重复请求
- ✅ 使用
AbortController在组件卸载或参数变更时取消请求 - ✅ 对非关键接口允许降级展示(失败不阻塞主视图)
- ✅ 对批量小接口,考虑后端做批量合并 API(一个请求拿到多个数据)
- ✅
Promise.allSettled在容忍部分失败时非常好用 - ✅ 搜索/筛选类请求做防抖(减少无意义并发)
- ✅ 区分全局 loading 和 卡片级 loading(避免整页灰屏)
常见误区(别踩)
- “
Promise.all总比单独请求快” —— 不一定,连接竞争、后端吞吐、HTTP 版本都会影响实际效果。 - “支持 HTTP/2 就不需要做任何优化” —— 仍需考虑缓存、批量、UI 渲染优先级等。
- “把所有请求合并就能减少一切问题” —— 合并会牺牲组件自治、增加单点失败风险,并可能增加首屏等待时间。
我最终的实践(一句话)
混合策略:把那些 无参数、页面初次一定要的、且多个子组件可能复用 的接口合并到 fetchDashboardAll(并行);把按需、参数依赖、用户交互触发 的请求留在各自组件的 useEffect(或串联)。如果必须串联的流程,一律 await 串联处理。
结语
优化网络请求,其实像减肥:不是把所有东西都饿掉,而是学会怎么吃,什么时候吃,吃多少。并发请求不是万能药,串联请求也不是禁忌,关键是看你的数据依赖、用户体验需求和后端能力。
最后一句老生常谈但很管用:别慌,先想清楚数据依赖和使用场景,再去动 Promise.all。
其实我也不知道我自己写了啥,我想的就是自己得一个思考方式吧,首先就是页面存在很多个接口,是否存在串联请求,请求1依赖请求2,其次就是是否需要把其它得并行请求统一放到promise.all进行处理,promise支持统一错误处理和loading状态,更健壮,然后就是是否需要组件内部自己维护自己得参数,让代码看起来不复杂,然后就是http1,http2得请求协议得认识 🤐🤐🤐🤐🤐🤐🤐🤐🤐🤐🤐🤐