辽宁省网站建设_网站建设公司_SEO优化_seo优化
2026/1/15 16:51:08 网站建设 项目流程

文章目录

  • 前言
  • Guidelines
  • Medium-Impact
    • 1. Use SWR for automatic request deduplication
      • 核心问题
      • 反例:手写 useEffect
      • 推荐:SWR 自动去重
      • 一句话总结
    • 2. Defer state reads to usage point
      • 核心问题
      • 反例:提前解构
      • 推荐:用到再读
      • Zustand / Redux / Jotai 都适用
      • 一句话总结
    • 3. Use lazy state initialization for expensive values
      • 核心问题
      • 反例
      • 推荐:lazy init
      • 典型场景
      • 一句话总结
    • 4. Use derived state subscriptions
      • 核心问题
      • 反例
      • 推荐:派生 state
      • 在全局状态库中更重要
      • 一句话总结
    • 5. Apply startTransition for non-urgent updates
      • 核心问题
      • 典型场景
      • 反例:同步更新
      • 推荐:startTransition
      • 什么时候该用 startTransition
  • 总结

前言

react-best-practices

React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.

Guidelines


在这个系列,我会逐条拆解,每一条都给出:

  • 核心问题是什么
  • 为什么会慢(本质原因)
  • 典型业务场景
  • 反例代码
  • 推荐写法
  • 在 React / Next.js 中的实际收益

Medium-Impact

这是系列的第三部分。

这一部分开始从“Server 极致性能”回到“Client 交互体验”,重点不再是 RTT,而是:

减少不必要的 re-render、避免同步阻塞、让用户感觉“很跟手”

1. Use SWR for automatic request deduplication

同一时间只发一次请求

核心问题

在 Client 侧:

  • 多个组件
  • 同一个接口
  • 同时 mount

浏览器会发多次相同请求

反例:手写 useEffect

functionuseUser(){const[data,setData]=useState(null)useEffect(()=>{fetch('/api/user').then(r=>r.json()).then(setData)},[])returndata}
<Header/><Sidebar/>

/api/user被请求两次

推荐:SWR 自动去重

importuseSWRfrom'swr'constfetcher=(url:string)=>fetch(url).then(r=>r.json())functionuseUser(){returnuseSWR('/api/user',fetcher)}

SWR 做了什么?

  • 同 key 只请求一次
  • 多组件共享结果
  • 自动缓存 & revalidate

一句话总结

SWR = Client 版 React.cache

2. Defer state reads to usage point

不要为了“可能用”而提前读 state

核心问题

React 中:

  • 读取 state = 建立订阅
  • state 更新 → 组件 re-render

提前读 = 不必要的 re-render

反例:提前解构

const{user,theme,locale}=useAppStore()

即使只用theme

  • user 更新
  • locale 更新

组件都会重渲染。

推荐:用到再读

consttheme=useAppStore(state=>state.theme)

Zustand / Redux / Jotai 都适用

useSelector(state=>state.user.name)

一句话总结

订阅越小,重渲染越少

3. Use lazy state initialization for expensive values

初始值贵,就别每次算

核心问题

useState(expensiveCompute())
  • 每次 render 都会执行expensiveCompute
  • 即使只用第一次

反例

const[value]=useState(buildBigMap(data))

推荐:lazy init

const[value]=useState(()=>buildBigMap(data))

函数只在初次 render 执行一次

useState 的两种“初始化模式”:

  1. 普通初始化(每次 render 都会算),也就是useState(buildBigMap(data)),React 在 render 前,先执行 buildBigMap,把执行结果作为参数传给 useState,每次 render 都会执行一次。

  2. Lazy 初始化,也就是useState(() => buildBigMap(data)),传递给 useState 的是一个 Initializer function,内部处理如下

functionuseState(initialState){if(isFirstRender){if(typeofinitialState==='function'){state=initialState()}else{state=initialState}}return[state,setState]}

因此,只有在 first render(mount)时:才会执行initialState(),后续 render:直接读取已经保存的 state,不会再碰这个函数。

典型场景

  • JSON.parse
  • 构建索引 Map
  • 复杂正则
  • 大数组预处理

一句话总结

初始 state = 函数

4. Use derived state subscriptions

不要存“算得出来的 state”

核心问题

const[filtered,setFiltered]=useState([])

filtered明明来自list + keyword

双源真相,必出 bug

反例

useEffect(()=>{setFiltered(list.filter(i=>i.name.includes(keyword)))},[list,keyword])

推荐:派生 state

constfiltered=useMemo(()=>{returnlist.filter(i=>i.name.includes(keyword))},[list,keyword])

在全局状态库中更重要

useStore(state=>state.items.filter(i=>i.active))

一句话总结

能算出来,就别存

5. Apply startTransition for non-urgent updates

区分“着急”和“不着急”的更新

React 18 引入了 更新优先级(lanes)

  • Urgent lane:输入、点击、focus
  • Transition lane:可延后更新

核心问题

React 默认:

  • 所有 state 更新都是“紧急的”
  • 大量更新 → 卡顿

典型场景

  • 搜索过滤
  • 表格排序
  • 列表分页
  • tab 切换后加载数据

反例:同步更新

onChange={(e)=>{setKeyword(e.target.value)setFilteredData(filter(data,e.target.value))}}

输入会卡。因为 React 内部的理解是:“这两个 setState 同等重要,必须立刻算完”,如果 filter(data) 很重:

  1. 输入法卡
  2. 光标延迟
  3. 掉帧

二、startTransition 到底做了什么?

推荐:startTransition

import{startTransition}from'react'onChange={(e)=>{setKeyword(e.target.value)startTransition(()=>{setFilteredData(filter(data,e.target.value))})}}

效果:

  • 输入优先
  • 列表稍后更新
  • UI 更丝滑

startTransition = 告诉 React:「这次更新不着急,别挡住用户操作」。没有 startTransition

用户输入 ↓ React:必须算完 filter(500ms) ↓UI更新

使用 startTransition

用户输入 ↓ React:先更新 input(5ms) ↓ 空闲时再算 filter ↓ 更新列表

注意,它不是 setTimeout,比如setTimeout(() => setList(...), 0),也不是 debounce。

对比setTimeoutstartTransition
是否理解 UI
可被中断
与调度器协作
与 Concurrent Rendering

更具体的对比可以阅读:WHAT - React startTransition vs setTimeout vs debounce

什么时候该用 startTransition

场景是否适合
搜索过滤
表格排序
分页切换
Tab 内容切换
输入框 value
hover 状态
modal 开关

总结

它们解决的是三类问题

1️⃣请求重复→ SWR
2️⃣订阅过多→ defer reads / derived state
3️⃣计算 & 更新阻塞→ lazy init / transition

一句话 Client 性能心法

少订阅

少算

慢更新

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询