React 组件 业务逻辑编码 最佳实践

当我们强调"组件 Render 阶段必须纯净"时,很多刚接触 Hooks 的开发者会产生困惑:如果不写在组件函数体里,我的业务逻辑到底该往哪放?

核心的秘密在于:我们需要把"业务逻辑"分类。 并不是所有业务逻辑都是"副作用"。在 React 中,业务逻辑根据其性质,有四个完美的去处。


业务逻辑的四大分类与去处

1. 纯计算逻辑(计算衍生状态) →\rightarrow→ 直接写在函数体内

如果你的业务逻辑是根据现有的 Props 或 State 计算出一个新数据 (例如:对列表进行搜索过滤、格式化时间、计算总价),这属于纯计算,没有任何副作用。

  • 怎么写 :直接写在组件函数体内。每次渲染重新计算是完全可接受的(如果计算量极大,才用 useMemo 包裹)。
  • ❌ 错误做法 :用 useEffect 监听状态变化,然后去 setFilteredList(这会导致二次渲染)。
javascript 复制代码
function ProductList({ products, filterKeyword }) {
  // ✅ 正确:纯计算逻辑,直接写在函数体内
  const filteredProducts = products.filter(p => p.name.includes(filterKeyword))
  const totalPrice = filteredProducts.reduce((sum, p) => sum + p.price, 0)

  return <div>总价:{totalPrice}</div>
}

2. 用户触发的业务(数据提交/修改) →\rightarrow→ 写在事件处理函数或 React 19 Actions 中

如果你的业务逻辑是因为用户点了某个按钮、提交了表单 才触发的(例如:删除商品、点赞、提交注册),这属于主动意图。

  • 怎么写 :写在 onClick、onSubmit 等事件回调函数内部,或者利用 React 19 的 Actions(结合 useTransition) 来处理异步提交。
  • 特点 :这些函数只在事件发生时调用,绝对不会 在 Render 阶段自动执行,因此你可以安全地在里面写任何副作用(如 fetch、修改全局状态)。
javascript 复制代码
function DeleteButton({ id }) {
  const [isPending, startTransition] = useTransition()

  const handleDelete = () => {
    // ✅ 正确:在事件触发的 Action 中编写异步业务逻辑
    startTransition(async () => {
      await fetch(`/api/delete/${id}`, { method: 'POST' })
      // 更新状态...
    })
  }

  return (
    <button onClick={handleDelete} disabled={isPending}>
      删除
    </button>
  )
}

3. 被动同步逻辑(依赖外部系统) →\rightarrow→ 写在 useEffect 或 React 19 use() 中

如果你的业务逻辑是组件一旦加载出来,或者某个依赖变了,就必须自动去干的一件事(例如:进入页面自动埋点、根据用户 ID 自动获取详情、订阅 WebSocket)。

  • 怎么写 :写在 useEffect 中,或者在 React 19 中使用 use(Promise) 进行声明式的数据流读取。
javascript 复制代码
useEffect(() => {
  // ✅ 正确:属于被动同步的副作用,放在 useEffect 中
  const tracker = new AnalyticsTracker()
  tracker.sendPageView()

  return () => tracker.disconnect() // 别忘了清理
}, [])

4. 复杂的跨组件业务 →\rightarrow→ 抽离到自定义 Hook(Custom Hook)中

当一个组件里塞满了大量的状态、事件处理、和计算,代码变得臃肿时,最优雅的解决方案是把业务逻辑彻底抽离出 UI 组件。

  • 怎么写 :创建一个以 use 开头的自定义 Hook,把 useState、useEffect、各种计算函数都打包塞进去,组件只负责看图说话(渲染 UI)。
javascript 复制代码
// 📦 独立的业务逻辑层 (useCart.js)
function useCart() {
  const [items, setItems] = useState([])
  const totalPrice = items.reduce((sum, i) => sum + i.price, 0)
  const addItem = item => setItems([...items, item])

  return { items, totalPrice, addItem }
}

// 🎨 纯粹的 UI 渲染层 (CartComponent.js)
function CartComponent() {
  // 组件体内只有一行干净的解构,没有任何杂乱的业务逻辑
  const { items, totalPrice, addItem } = useCart()
  return (
    <button onClick={() => addItem({ price: 10 })}>加购 ({totalPrice})</button>
  )
}

总结速查表

业务逻辑类型 举例 应该写在哪里? 是否允许副作用?
衍生数据计算 过滤列表、计算总和、格式化文本 组件函数体内 ❌ 绝对不行(必须是纯函数)
用户交互响应 点击保存、删除、切换开关 事件处理函数 / Actions ✅ 可以
外部系统同步 页面加载取数、监听窗口大小 **useEffect / use()** ✅ 可以
复杂/复用业务 完整的购物车逻辑、分页器逻辑 自定义 Hooks 视内部的具体 Hooks 而定

把 UI(怎么画)和业务(怎么算/怎么变)通过上述规则清晰地解耦,就是写出高质量、可维护 React 19 代码的关键。

相关推荐
拆房老料1 小时前
【第四部分:Agentic 智能体设计模式】16. 一个 Prompt 搞不定复杂任务?试试 Prompt Chaining
java·前端·人工智能·设计模式·prompt
曹牧6 小时前
C#:空合并运算符
java·前端·数据库
小羊没烦恼!7 小时前
铁道部新客票系统设计(二)
开发语言·前端·数据库·python·oracle·c#·php
智能直播9 小时前
RK3518:1.5G 内存 + 8G 存储,如何跑起硬件编解码、Web、AI 识别、绿幕特效
前端·人工智能·5g
掘金酱11 小时前
【10.24 特别活动】AI 时代,程序员的新饭碗在哪里?
前端·人工智能·后端
excel11 小时前
多进程环境下使用 PM2 部署 Socket.IO:需要注意什么
前端
IT_陈寒11 小时前
Redis的原子性竟然失效了?记一次分布式锁翻车实录
前端·人工智能·后端
时间的情敌13 小时前
《回顾 2018 前端:Vue2 成熟期,Webpack4 发布,PWA 大火》
前端
Java后端的Ai之路13 小时前
前端技术Vue-09-webpack-vuejs-实战
前端·vue.js·webpack
LMT流氓兔13 小时前
第五单元——第4课:给工具参数加校验
java·前端·算法·ai·langchain