Sukka 写前端、网络和基础设施时,习惯先把边界和代价摊开。把「为什么你不应该在 React 中直接使用 useEffect 从 API 获取数据 | Sukka's Blog」整理成可落地的中文笔记:问题在哪、默认做法会踩什么坑、该怎么选。原站导航和广告已去掉。

TL; DR

设想一下你在编写一个 React 应用,需要从 API 获取产品列表数据、并渲染到页面上。你想到了网络请求不属于渲染、而是渲染的副作用,你还想到了 React 提供了一个专门的 Hook useEffect 用于处理渲染的副作用,最常见的场景就是将属于 React 外部的状态同步到 React 内部中。你不假思索,实现了一个 <ProductList /> 组件:

const ProductList = ( ) => { const [ products , setProducts ] = useState ( [ ] ) ; useEffect ( ( ) => { fetch ( 'https://dummyjson.com/products' ) . then ( res => res . json ( ) ) . then ( data => setProducts ( data ) ) ; } , [ ] ) ; return ( < ul > { products . map ( product => ( < Product { … product } key = { product . id } /> ) ) } </ ul > ) ; } 你运行 npm run dev ,成就感满满地看见产品列表显示在页面上。

从发送一个简单的请求开始

你发现首次加载的时候,直到数据加载完成之前页面都是白屏,用户体验很不好。于是你决定实现一个「加载中」的进度条、引入了一个新的状态 isLoading :

const ProductList = ( ) => { const [ isLoading , setIsLoading ] = useState ( true ) ; const [ products , setProducts ] = useState ( [ ] ) ; useEffect ( ( ) => { setIsLoading ( true ) ; fetch ( 'https://dummyjson.com/products' ) . then ( res => res . json ( ) ) . then ( data => { setProducts ( data ) ; setIsLoading ( false ) ; } ) ; } , [ ] ) ; if ( isLoading ) { { /* TODO 实现一个骨架屏 <Skeleton /> 改善 UX、避免 CLS */ } return < Loading > 正在玩命加载中… </ Loading > ; }

在 UI 中展示「加载中」和错误

const ProductList = ( ) => { const [ isLoading , setIsLoading ] = useState ( true ) ; const [ products , setProducts ] = useState ( [ ] ) ; const [ error , setError ] = useState ( null ) ; useEffect ( ( ) => { setIsLoading ( true ) ; fetch ( 'https://dummyjson.com/products' ) . then ( res => res . json ( ) ) . then ( data => { setProducts ( data ) ; setIsLoading ( false ) ; } ) . catch ( err => { // TODO 错误日志上报 setError ( err ) } ) ; } , [ ] ) ; if ( isLoa

const useFetch = ( url , requestInit = { } ) => { const [ isLoading , setIsLoading ] = useState ( true ) ; const [ data , setData ] = useState ( null ) ; const [ error , setError ] = useState ( null ) ; useEffect ( ( ) => { setIsLoading ( true ) ; fetch ( url , requestInit ) . then ( res => res . json ( ) ) . then ( data => { setData ( data ) ; setIsLoading ( false ) ; } ) . catch ( err => setError ( err ) ) ; } , [ url , requestInit ] ) ; return { data ,

封装一个新的 Hook

const ProductList = ( ) => { const { isLoading , data , error } = useFetch ( 'https://dummyjson.com/products' ) ; } const Product = ( { id } ) => { const { isLoading , data , error } = useFetch ( ` https://dummyjson.com/products/ ${ id } ` ) ; } 处理 Race Condition 你实现了一个在多个产品之间切换的轮播组件,当前展示的产品存储在状态 curentProduct 中:

const Carousel = ( { intialProductId } ) => { const [ currentProduct , setCurrentProduct ] = useState ( intialProductId ) ; const { data , isLoading , error } = useFetch ( ` https://dummyjson.com/products/ ${ currentProduct } ` ) ; } ; 结果你在测试时发现,在轮播组件中快速切换时,有时候当你点击下一个产品,界面上却展示了上一个产品。 因为你没有在 useEffect 中声明如何清除你的副作用。发送网络请求是一个异步的行为,收到服务器数据的顺序并不一定是网络请求发送时的顺序、出现了 Race Condition:

处理 Race Condition

| =============== Request Product 1 ===============> | setState() | ===== Request Product 2 ====> | setState() | 如果发生了如上所示的第二个产品的数据返回地比第一个产品快的情况,你的 data 就会被第一个产品的数据覆盖掉。

const useFetch = ( url , requestInit = { } ) => { const [ isLoading , setIsLoading ] = useState ( true ) ; const [ data , setData ] = useState ( null ) ; const [ error , setError ] = useState ( null ) ; useEffect ( ( ) => { let isCancelled = false ; setIsLoading ( true ) ; fetch ( url , requestInit ) . then ( res => res . json ( ) ) . then ( data => { if ( ! isCancelled ) { setData ( data ) ; setIsLoading ( false ) ; } } ) . catch ( err => { if ( ! isCance

缓存网络请求

你还可以在清除副作用时检测当前浏览器是否支持 AbortController 、用 AbortSignal 取消中止网络请求:

const isAbortControllerSupported = typeof AbortController !== 'undefined' ; const useFetch = ( url , requestInit = { } ) => { const [ isLoading , setIsLoading ] = useState ( true ) ; const [ data , setData ] = useState ( null ) ; const [ error , setError ] = useState ( null ) ; useEffect ( ( ) => { let isCancelled = false ; let abortController = null ; if ( isAbortControllerSupported ) { abortController = new AbortController ( ) ; } setIsLoading ( true ) ;

值得单独记下的点

  • 大部分时候,首屏需要的数据可以通过服务端渲染 SSR 直出、无需在客户端额外发送网络请求
  • 即使需要客户端在首屏获取数据,未来 React 和社区维护的库会提供基于 Suspense 的数据请求 Pattern、实现「Render as your fetch」
  • 即使在使用「Fetch on render」的 Pattern,也应该直接使用第三方库如 SWR 或 React Query,而不是直接使用 useEffect
  • Error Retry:在数据加载出问题的时候,要进行有条件的重试(如仅 5xx 时重试,403、404 时放弃重试)
  • SSR、SSG:服务端获取的数据用来提前填充缓存、渲染页面、然后再在客户端刷新缓存
  • Mutation:响应用户输入、将数据发送给服务端
  • Optimistic Mutation:用户提交输入时先更新本地 UI、形成「已经修改成功」的假象,同时异步将输入发送给服务端;如果出错,还需要回滚本地 UI
  • Middleware:日志、错误上报、Authentication

落地时建议先做的 5 件事

  1. 用自己的流量和设备测,不要只抄厂商推荐最小配置。
  2. DNS、CDN、代理分流先画清 Fake IP 和 Real IP 的边界。
  3. 前端性能看 CLS、白屏和重复渲染,而不是只看打包体积。
  4. 基础设施变更走 Git,能复现、能回滚。
  5. 结论写成可检查的清单:接口、超时、失败样本、回滚版本。

和智能体产品怎么接

龙虾PRO做 OpenClaw 落地时,网络、DNS 和前端性能笔记最有用的是「边界」:哪一层该加速、哪一层不该假装智能。数字员工调用外部能力前,先把超时和失败路径写死。

本文侧重全链路风控方法论。落地时请用自身业务单据做回放验证,不要把示例阈值直接当生产策略。 相关:风控体检 · 方案资源

常见问题 FAQ

什么是AI智能系统?

「AI智能系统」可概括为:设想一下你在编写一个 React 应用,需要从 API 获取产品列表数据、并渲染到页面上。你想到了网络请求不属于渲染、而是渲染的副作用,你还想到了 React 提供了一个专门的 Hook useEffect 用于处理渲染的副作用,最常见的场景就是将属于 React 外部的状态同步到 React 内部中。你不假思索,实现了一个 本文从定义、方法与实践要点展开说明。

为什么要关注AI智能系统?

关注AI智能系统,是因为它直接影响效率、风险与可复制性。文中指出:设想一下你在编写一个 React 应用,需要从 API 获取产品列表数据、并渲染到页面上。你想到了网络请求不属于渲染、而是渲染的副作用,你还想到了 React 提供了一个专门的 Hook useEffect 用于处理渲染的副作用,最常见的场景就是将属于 React 外部的状态同步到 React 内部中。你不假思索,实现了一个 <ProductList /> 组件:

如何落地AI智能系统?有哪些关键步骤?

建议按以下路径推进AI智能系统:1) 大部分时候,首屏需要的数据可以通过服务端渲染 SSR 直出、无需在客户端额外发送网络请求;2) 即使需要客户端在首屏获取数据,未来 React 和社区维护的库会提供基于 Suspense 的数据请求 Pattern、实现「Render as your f…;3) 即使在使用「Fetch on render」的 Pattern,也应该直接使用第三方库如 SWR 或 React Query,而不是直接使用 useEffect;4) Error Retry:在数据加载出问题的时候,要进行有条件的重试(如仅 5xx 时重试,403、404 时放弃重试);5) SSR、SSG:服务端获取的数据用来提前填…

AI智能系统适合哪些人或团队?

AI智能系统更适合:产品/技术负责人、运营与增长团队、需要落地智能体或自动化的中小团队、关注「AI智能系统」方向的读者。若你只需要单次聊天式问答,可先读概念;若要上生产,请重点看步骤、权限与风控相关段落。

关于「TL; DR」,本文给出了什么结论?

在「TL; DR」部分,要点是:/> 组件: const ProductList = ( ) => { const [ products , setProducts ] = useState ( [ ] ) ; useEffect ( ( ) => { fetch ( 'https://dummyjson.com/products' ) . then ( res => res . json ( ) ) . then ( data => setProducts ( da

关于「从发送一个简单的请求开始」,本文给出了什么结论?

在「从发送一个简单的请求开始」部分,要点是:架屏 改善 UX、避免 CLS */ } return < Loading > 正在玩命加载中… ; } 在 UI 中展示「加载中」和错误 const ProductList = ( ) => { const [ isLoading , setIsLoading ] = useState ( true ) ; const [ products , setProducts ] = u