React中使用useEffect触发数据请求是否属于不良实践?
该问题来自一次代码评审工作,我和同事对相关场景的实现方案存在意见分歧。
我们正在开发一款文档展示类应用,展示的文档支持按时间正序或倒序排列,排序规则由应用顶部栏的按钮控制,默认值为时间正序;该排序值存储在Redux全局状态中,所有拉取文档的接口请求都需要传入该参数。
当前示例的实现逻辑为:点击按钮更新sortOrder状态,以状态变化作为副作用触发数据拉取。按照我的理解,sortOrder状态变化时组件会触发一次渲染,数据拉取完成后组件会再次触发渲染。
近似伪代码
interface AppState = { sortOrder: SortOrder; documentation: Documentation[]; } reducer(){ case toggleSortOrder: const order = state.sortOrder === 'asc' ? 'desc' : 'asc'; return { ...state, sortOrder: order; } } const AppBar = () => { const dispatch = useDispatch(); return <div><button onClick={() => dispatch(toggleSortOrder)}>Change sort order</button> </div>; } const DocumentationList = ({type}: {type: DocumentationType}) => { const dispatch = useDispatch(); const sortOrder = useSelector((state) => state.appState.sortOrder); const documentation = useSelector((state) => state.appState.documentation); useEffect(() => { // action is caught by redux-saga and a call to docApi is made through axios dispatch(getDocumentation.request(type, sortOrder)) },[sortOrder, type, dispatch]); return documentation.map((doc) => <Documentation key={doc.id} data={doc} />); }
注:上述伪代码修正了原示例中部分语法疏漏(比如扩展运算符书写错误、onClick未包裹函数、组件props解构缺失、useEffect依赖不全、列表缺key等),不改变原有实现逻辑。
请问上述实现是否属于不良实践?是否应该避免使用useEffect,改为在按钮点击时直接触发数据拉取、在redux-saga中更新sortOrder状态?
我查阅官方文档和技术博客时,大多只看到useEffect的使用方式和适用场景示例,未找到该类场景下的明确最佳实践指引。
回答
核心结论
你当前的useEffect实现不属于不良实践,完全不需要为了规避两次渲染刻意去掉useEffect,两种实现方案没有绝对的对错,只有场景适配的区别,不存在必须二选一的铁则。
关于重渲染问题的说明
排序状态变更触发一次渲染、接口返回后数据更新触发第二次渲染,是React状态驱动模型下的正常流程,不属于需要优化的冗余渲染:
- 第一次渲染可以同步给用户操作反馈:比如给排序按钮加激活态、列表区域展示loading占位,让用户明确感知到操作已生效
- 第二次渲染是数据就绪后的正常内容更新,整个链路符合React「状态变更→调度副作用→副作用执行后更新状态→视图同步」的核心设计逻辑,没有多余的性能开销。
两种方案的优劣势对比
- 当前useEffect响应状态变更拉取数据的方案
优势是数据拉取逻辑和排序状态完全解耦:后续不管是顶部按钮修改排序、URL参数同步排序值、读取用户默认偏好设置、其他业务模块触发排序变更,只要全局的sortOrder状态发生变化,文档列表就会自动拉取对应排序规则的数据,不需要在所有触发排序变更的位置重复编写数据拉取逻辑,长期可维护性更强。
这种写法只需要注意两个细节:一是补全useEffect的全部依赖项(比如示例中的type参数必须加入依赖数组,否则文档类型切换时会用旧的排序值拉取数据);二是做好请求竞态处理,比如快速连续切换排序时,只保留最后一次发起请求的返回结果,避免旧请求晚返回覆盖新数据。 - 按钮点击直接触发拉取、saga同步更新排序状态的方案
优势是用户操作到请求发起的链路更直观,少了一次effect调度的微小开销;但劣势非常明显:数据拉取逻辑和按钮操作强耦合,后续只要新增其他修改sortOrder的入口,就必须手动同步调用数据拉取的action,非常容易出现「排序值已经变了,但列表数据没刷新」的一致性bug。
useEffect的正确使用原则
从来不存在「要尽可能避免使用useEffect」的开发规则,官方明确不推荐的是滥用useEffect制造多余的更新链路:比如把可以在渲染阶段直接通过props/state计算得到的派生值,单独存成state再用useEffect做同步更新,这类会制造冗余渲染的写法才是需要规避的不良实践。
而数据请求、订阅外部事件、操作DOM这类和React渲染流程无关的外部副作用,用useEffect响应依赖状态的变化来触发,是完全符合React设计预期的标准用法。
内容的提问来源于stack exchange,提问作者Mangs

