资讯动态

React实战进阶:掌握Hooks、状态管理与性能优化的工程化指南

发布时间:2026/10/20 17:06:05 来源:尧图企业网站定制
1. 为什么我把rea解读为前端最该掌握的核心看到一个标题只有rea三个字母可能有人会一头雾水这是某个项目的缩写是某个库的名字还是随手敲的乱码坦白讲我刚拿到这个题目也愣了一下。但如果把它放到前端开发领域来理解rea最自然的联想就是React——那个由众多开发者共同维护、目前前端生态中最具统治力的视图层框架。这个解读并非牵强。在我接触过的绝大多数实战项目中React的出现频率高得离谱。从某企业内部的管理后台系统到某创业团队的移动端H5项目再到某高校实验室的数据可视化平台几乎每个技术选型讨论会上React都会被拎出来和另一个名字对比一轮。很多人说前端框架已死认为底层浏览器原生能力已经够用但现实是只要项目规模跨过几个静态页面这条线组件化、状态管理、性能优化这些问题就会像潮水一样涌过来而React正是应对这些问题的成熟方案。这篇文章不是官方的API文档复读而是我从零开始接触React、经历多个项目的搭建与重构、踩过无数性能坑之后沉淀下来的实战笔记。适合几类人阅读刚学完三件套、想搞明白框架到底解决了什么问题的前端新人已经在用React但主要靠复制粘贴组件、改改就能跑的初级工程师以及团队正在做技术选型、需要对React的能力边界和生态现状有清晰判断的开发者。我尽量用项目里真实发生过的事来讲把每个知识点背后的为什么也一并交代清楚。毕竟只看官方文档你永远不知道为什么某个写法在线上环境会内存暴涨也永远不知道为什么团队里另一半人坚持不用某个状态管理库。2. 从Class组件到Hooks为什么函数组件成了主角React早期最让人诟病的一点就是Class组件的重。每写一个组件得先继承再考虑constructor、this绑定、生命周期函数一套流程下来光是模板代码就占了半个文件。我当年接手某后台管理系统的第一个任务就是在一个Class组件里加一个定时刷新列表的功能。代码大概是这个样子的import React from react class ListPage extends React.Component { constructor(props) { super(props) this.state { list: [], loading: false } this.fetchList this.fetchList.bind(this) } componentDidMount() { this.fetchList() this.timer setInterval(this.fetchList, 30000) } componentWillUnmount() { clearInterval(this.timer) } async fetchList() { this.setState({ loading: true }) try { const res await getList() this.setState({ list: res.data }) } finally { this.setState({ loading: false }) } } render() { // ...渲染逻辑 } }光看这段代码你已经能感受到几个痛点this的指向问题需要手动bind定时器的创建和清理散落在不同生命周期里状态更新是异步的、必须依赖setState的回调机制。如果再加一个筛选条件变化时重新请求的需求就得再补一个componentDidUpdate然后小心翼翼地比较前后props稍不留神就造成死循环。Hooks出现之后同样的逻辑变成了这样function ListPage() { const [list, setList] useState([]) const [loading, setLoading] useState(false) const [filter, setFilter] useState() const fetchList useCallback(async () { setLoading(true) try { const res await getList({ filter }) setList(res.data) } finally { setLoading(false) } }, [filter]) useEffect(() { fetchList() const timer setInterval(fetchList, 30000) return () clearInterval(timer) }, [fetchList]) // ...渲染逻辑 }两段代码做的事完全一样但后者的心智负担要小得多fetchList是一个普通函数不再依赖this定时器的创建和清理放在同一个useEffect里逻辑内聚筛选条件变化后通过依赖数组自动重新执行不需要手动对比。函数组件没有了生命周期函数的强行拆分跟数据请求相关的逻辑天然聚集在一起这才是Hooks之所以能取代Class的根本原因——不是花哨而是符合人类思考自然逻辑。可能有人会问既然Class组件也能实现为什么非要用Hooks我的看法是Class组件更像背一本说明书来写代码Hooks则是直接表达代码意图。前者要求你在正确的时间点插入正确的方法后者允许你按照数据流的自然顺序组织一切。初次上手Hooks可能会被useEffect的执行时机搞晕但一旦适应你就再也回不去了。至少我自己重构完那个系统后所有新代码全部是函数组件之前遗留的Class组件也在迭代中一个个被替换掉了。2.1 理解useEffect它不是componentDidMount的替代品这是很多新手最大的误区。有人把useEffect当成挂载完成后干点啥的工具于是写出一堆跟原来生命周期一一对应的代码。但实际上useEffect的设计核心是人称的同步外部系统——它负责让组件跟外部世界数据请求、订阅、DOM操作、localStorage读写保持一致。我见过一个典型错误在useEffect里请求数据然后依赖数组传了一个每次渲染都变化的新对象导致组件陷入请求-渲染-再请求的死循环。调试的时候怎么也找不到原因因为数据明明在变化但useEffect就是不按预想的那样只跑一次。// 错误示范每次渲染都会创建一个新对象导致无限循环 useEffect(() { fetchList(params) // params是每次渲染新建的对象 }, [params])解决这个问题有几个办法如果是普通值字符串、数字、布尔值直接放进依赖数组即可如果一定要传对象用useMemo把它缓存起来实在不行把请求函数定义在Effect内部依赖数组留空或者只依赖真正变化的原始值。我个人的原则是依赖数组里永远只放原始值和被缓存过的引用绝不直接放每次渲染都会新建的对象或函数。3. 核心Hooks逐个拆解useState、useRef、useMemo背后的小心思3.1 useState不是简单的变量很多人觉得useState就是组件里存点数据但真正用得明白的人都知道它有几个容易忽略的细节。第一state的更新是异步且批处理的。React 18之后自动批处理已经覆盖到几乎所有场景这意味着const handleClick () { setCount(c c 1) setCount(c c 1) setCount(c c 1) } // 最终count只加1不React会依次应用每个更新函数最终加3第二不要在setState时直接修改原对象。比如// 错误直接修改了原有的数组React可能检测不到变化 const list [...state.list] list.push(newItem) setState({ list }) // 正确每次返回全新数组 setState(prev ({ list: [...prev.list, newItem] }))第三如果state的结构很复杂比如多层嵌套对象别老想着用一次setState去更新深层字段。这时候要么扁平化数据结构要么干脆引入useReducer。我自己在处理复杂业务逻辑时更愿意用useReducer——它的reducer函数是纯函数更新逻辑可以单独测试调试时还能配合日志工具看到每次派发的action和状态变化比散落各处的useState好排查得多。3.2 useRef不只是能变的盒子大多数教程告诉你useRef可以保存一个不会触发渲染的值比如定时器ID、DOM引用。这当然对但它还有一个鲜为人知的妙用解决闭包陷阱问题。场景是这样的你有一个定时器每次触发都要读取某个state的最新值但如果把这个state写进useEffect的依赖数组定时器会频繁重建。不写进去闭包里捕获的又是旧值。完美解法是function Timer() { const [count, setCount] useState(0) const countRef useRef(count) useEffect(() { countRef.current count }, [count]) useEffect(() { const id setInterval(() { console.log(countRef.current) // 始终是最新的count }, 1000) return () clearInterval(id) }, []) }定时器全程只建一次但每次触发都能拿到最新的count。这种做法在实现节流/防抖、滚动监听、WebSocket长连接这类需要持久引用最新状态的场景里极其好用。3.3 useMemo与useCallback性能优化别乱用很多初学者一听性能优化就到处加useMemo、useCallback结果代码可读性变差性能反而没提升。道理很简单useMemo本身也有计算成本——它需要对比依赖数组这个对比也需要时间。我一直遵循三个原则只有计算量确实很大的场景才用useMemo比如渲染几千行列表时的复杂过滤逻辑只有子组件依赖引用相等性比如被React.memo包裹、或在useEffect的依赖数组里使用才用useCallback不要为了优化而优化先跑起来再借助Profiler工具定位真正的性能瓶颈。这里插一句React.memo配合useCallback是常用的组合拳。父组件重新渲染时如果传给子组件的props引用不变memo包裹的子组件可以跳过渲染。但如果你传了一个每次渲染都新建的箭头函数memo就完全失效了。所以正确姿势是useCallback包裹函数再传给memo子组件。3.4 useReducer状态逻辑复杂时的解药上面提到了useReducer的适用场景这里补充一个实战案例。我在某工单管理系统里维护过一个筛选条件对象字段多达七八个每次变更都要深克隆、合并。如果用useState代码会变成一堆分散的setState调用维护起来异常痛苦。改用useReducer之后每个动作一目了然const initialState { status: ALL, keyword: , priority: 0, dateRange: null, tags: [] } const filterReducer (state, action) { switch (action.type) { case SET_STATUS: return { ...state, status: action.payload } case SET_KEYWORD: return { ...state, keyword: action.payload } case SET_DATE_RANGE: return { ...state, dateRange: action.payload } case TOGGLE_TAG: { const hasTag state.tags.includes(action.payload) return { ...state, tags: hasTag ? state.tags.filter(t t ! action.payload) : [...state.tags, action.payload] } } case RESET: return initialState default: return state } }dispatch一个动作就能明确知道状态发生了什么变化测试时也能直接对reducer函数做单元测试。如果哪天状态逻辑继续膨胀还可以配合Context实现跨层级的状态分发这是后面要聊的内容。4. 跨组件传值Context与状态管理库的边界到底在哪4.1 什么时候该用Context很多初级项目一上来就装Redux Toolkit或者Zustand然后存了一大堆全局状态连当前输入框的值都放进了store。这完全搞反了。React官方的Context只适合放低频变化、层级较深的共享数据典型的例子有主题色、当前登录用户、语言环境、功能开关等。我见过一个项目把搜索表单的当前草稿放进了全局store结果用户在页面间跳转时这份草稿被保留下来了——但你根本分不清这是产品想要的功能还是无意留下的bug。实践建议是先用组件树自身的props传递解决80%的问题当props层层穿透变得难以维护时再考虑Context只有真正出现两个不相关组件要同步同一份数据比如购物车数量受头部、列表页、结算页共同影响时才动用全局状态管理库。4.2 Zustand、Redux Toolkit与Jotai的选型心法现在的前端生态里状态管理库三个主流派系。我梳理一下实战感受库心智负担样板代码适用场景Zustand低低中小型项目、快速迭代Redux Toolkit中高中大型项目、强规范团队Jotai极低极低颗粒度状态、与React哲学契合的团队Zustand是我目前在中小型项目里最推荐的选择。它不需要Provider包裹store创建和使用都非常小巧而且天然支持在组件外读取/修改状态——这对处理路由守卫、工具函数里的状态读取特别友好。代码长这样import { create } from zustand const useStore create(set ({ userInfo: null, setUserInfo: info set({ userInfo: info }), logout: () set({ userInfo: null }) }))组件里直接import这个hook就能用配合selector还能精准订阅局部变化避免整棵组件树跟着重渲染。Redux Toolkit在大型团队里的价值在于纪律性。它强制你用可预测的方式组织action、reducer和状态切片配合开发者工具整个应用的状态变化可以完整回溯。但代价是代码量大新成员的上手成本不低。我的建议是如果团队已经有Redux心智或者项目有严格的单一数据源要求选它没错如果是创业团队想快速出活别硬上。Jotai则代表了另一种思路把状态拆成原子组件各取所需完全绕开了全局store的概念。它适合那种状态颗粒度极细、频繁组合拆分的场景比如复杂表单、图表配置面板。用熟了Jotai的人往往再也回不到一个大store的模型里。选型没有绝对标准但有一条红线不要让状态管理库成为项目的主架构它只是辅助工具。如果大部分状态都可以用Context 组件内部state解决那就别引入额外的库减少依赖也是一种优化。4.3 服务端状态是另一回事这里我要特别强调一个被很多人混淆的点从后端API获取的数据本质上是服务端状态它跟客户端UI状态弹窗开关、表单草稿、主题色完全是两类东西。服务端状态有自己的一系列问题缓存、失效、重试、乐观更新、多端同步。如果你用Zustand把人列表存进store还得自己写各种请求逻辑、loading状态、错误处理代码很快会变得臃肿。现代React项目里服务端状态直接交给专门的工具更省心。比较常用的方案是TanStack Query以前叫react-query和SWR。以TanStack Query为例核心API是import { useQuery } from tanstack/react-query function UserList() { const { data, isLoading, error } useQuery({ queryKey: [users, page], queryFn: fetchUsers }) if (isLoading) return Spinner / if (error) return ErrorFallback / return List data{data} / }queryKey是缓存的标识queryFn是数据请求函数。它会自动做好缓存、请求去重、后台刷新当你需要让某个key的数据失效重新请求时调用queryClient.invalidateQueries([users])即可。配合mutation还能轻松实现乐观更新——请求发出去先改界面失败了再回滚。这才是处理服务端数据的正确姿态。5. 性能优化实战从一个卡死的列表说起5.1 渲染瓶颈的定位方法我之前接过一个需求某报表页面渲染300行数据每行里面有图表和多个下拉框用户切换筛选条件后整个页面卡了快2秒。新手的直觉往往是数据量太大但我打开React DevTools的Profiler一看瓶颈根本不在那300行数据的渲染上——而是在于每次点击筛选按钮时整个页面顶层组件重新渲染导致所有子组件包括那些完全没变化的侧边栏都跟着重跑一遍。定位方法非常简单用Profiler录制一次交互看每个组件树的渲染耗时和触发原因。下面是我惯用的排查思路有没有被React.memo包裹的组件依然重渲染查看props引用是否每次都在变。有没有巨型列表没有使用虚拟化当渲染项数超过1000时即使单组件很快累计耗时会非常可观。有没有在渲染期间执行了高开销计算比如在render里filter一个大数组。有没有不小心把非原始对象写进useEffect依赖数组导致循环有循环的时候页面根本不会卡而是直接崩溃。5.2 虚拟列表3000行也能流畅如果数据量真的很大比如几千行日志、数万条消息那唯一的正解是虚拟列表。它的核心思想特别朴素视口能看到的只有那么高那就只渲染可视区域内的那些行上下加两个占位块撑住滚动条的高度。我常用react-window或react-virtualized这两个库。以react-window为例import { FixedSizeList as List } from react-window const Row ({ index, style }) ( div style{style}{index 1}: 行内容/div ) const Example () ( List height{600} itemCount{10000} itemSize{40} width100% {Row} /List )它只渲染高度600px范围内的大约15行其余全是空白垫片。渲染10万行也毫无压力。值得注意的坑是当行高不固定时比如某些单元格文字折行不能用FixedSizeList得换DynamicSizeList或者主动测量行高——否则会出现滚动跳动、内容错位的bug。5.3 代码分割首屏加载时间压缩50%老项目最容易犯的错误是打包成一个巨大的bundle。我见过某系统首屏加载要拉取3MB的JS白屏时间长得能去泡杯茶。解决办法是路由级别的代码分割配合React.lazy和Suspenseimport { lazy, Suspense } from react const Dashboard lazy(() import(./pages/Dashboard)) const UserManagement lazy(() import(./pages/UserManagement)) function App() { return ( Routes Route path/dashboard element{ Suspense fallback{Loading /} Dashboard / /Suspense } / Route path/users element{ Suspense fallback{Loading /} UserManagement / /Suspense } / /Routes ) }这样每个页面路由会单独拆成chunk用户访问哪个页面才加载对应的JS首屏体积大幅下降。再配合Ant Design这类UI库的按需引入、图标库的Tree Shaking首屏加载时间能轻松砍半。6. 路由方案与请求层的工程化组合6.1 路由策略用嵌套路由组织页面骨架React Router是目前最常见的路由方案。除了最基础的路由匹配我更想强调嵌套路由的设计价值。在后台管理类项目里页面结构往往是整体布局侧边栏顶栏内容区嵌套路由可以把布局组件抽出来让它的children根据子路由动态渲染import { Routes, Route, Outlet } from react-router-dom function AdminLayout() { return ( div Sidebar / Topbar / main Outlet / {/* 子路由在这里渲染 */} /main /div ) } Routes Route path/admin element{AdminLayout /} Route index element{Dashboard /} / Route pathusers element{UserManagement /} / Route pathsettings element{Settings /} / /Route /Routes这样做的好处是布局组件只需要渲染一次侧边栏不会在页面切换时闪动子路由代码也更干净。嵌套路由很好地映射了UI嵌套的现实结构。6.2 Router的状态同步与鉴权拦截路由不只是切页面它还是一种状态。搜索参数、筛选条件、分页页码都应该放进URL而不是藏在组件内部state里。理由很简单URL可以被收藏、被分享、被浏览器前进后退如果这些信息丢失用户的操作场景就断了。我习惯用useSearchParams来读写URL参数const [searchParams, setSearchParams] useSearchParams() const page Number(searchParams.get(page) || 1) const changePage (p) { setSearchParams(prev { const next new URLSearchParams(prev) next.set(page, String(p)) return next }) }页面刷新、分享链接、浏览器前进后退都能正确恢复同样的视图状态。这个习惯在数据密集、筛选项多的业务系统里能省掉大量用户反馈刷新后数据丢了的工单。鉴权拦截方面我会在顶层路由外包一层AuthGuard组件它读取登录状态通常来自Context或store如果未登录就重定向到登录页如果已登录但权限不足则渲染403页面。注意不要在每个页面里重复写鉴权逻辑把规则收敛到一个组件里改起来一口价。6.3 封装请求层统一错误处理与取消请求请求层是很多React项目最容易各写各的的部分。有人用fetch有人用axios有人直接复制粘贴一坨XMLHttpRequest代码。我在团队里推行的是用axios实例统一封装拦截器处理token注入和错误提示。const request axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) // 请求拦截注入token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截统一错误处理 request.interceptors.response.use( res res.data, err { const status err.response?.status if (status 401) { // 跳转登录页、清理本地状态 } else if (status 500) { message.error(服务器繁忙请稍后重试) } return Promise.reject(err) } )另外还有一个容易忽略的点组件卸载后请求才返回会导致setState警告甚至在React 18里造成内存泄漏。现代写法是用AbortController取消请求useEffect(() { const controller new AbortController() fetchList({ page }, { signal: controller.signal }) .then(...) .catch(err { if (err.name AbortError) return // 主动取消不用报错 }) return () controller.abort() }, [page])如果你的项目已经在用TanStack Query这一层它已经帮你处理掉了这也是我推荐在复杂项目中引入它的原因之一。7. 数据流与组件职责划分可维护性的地基7.1 展示组件与容器组件的思考组件拆分的艺术直接决定项目能不能熬过三次迭代。我经常会用展示组件 vs 容器组件这个经典框架来思考展示组件接收props渲染UI不关心数据从哪来、怎么变化。比如一个Button、一个Table、一张卡片。容器组件负责获取数据、管理状态、处理用户交互并把数据通过props传给展示组件。但我不建议把这种划分绝对化。在小项目里过度拆分组件会让文件爆炸反而不利于维护。我的建议是当一个文件超过200行、或者一个组件混杂了数据获取复杂UI多个事件处理时才考虑拆分。拆分时优先保证一件事——每个组件只做一件事并且变化方向单一。7.2 自定义Hooks把逻辑抽出来的正确姿势如果说组件是UI的复用单元那自定义Hook就是逻辑的复用单元。它的精髓在于把一段复杂的状态逻辑封装起来对外只暴露简洁的API。我在项目里大量使用的自定义Hook长这样function useTablePagination(fetcher, defaultPageSize 20) { const [page, setPage] useState(1) const [pageSize, setPageSize] useState(defaultPageSize) const [total, setTotal] useState(0) const [data, setData] useState([]) const [loading, setLoading] useState(false) useEffect(() { let cancelled false setLoading(true) fetcher({ page, pageSize }).then(res { if (!cancelled) { setData(res.rows) setTotal(res.total) } }).finally(() { if (!cancelled) setLoading(false) }) return () { cancelled true } }, [page, pageSize, fetcher]) const refresh () setPage(p p) // 触发重新请求 return { page, pageSize, setPage, setPageSize, total, data, loading, refresh } }任何需要用分页表格的页面一行就能接入const { data, loading, page, setPage } useTablePagination(fetchOrders)如果逻辑里面有bug只需要修这个Hook所有引用它的页面同时修复这就是抽Hook的威力。抽得好的自定义Hook一个个就像积木项目扩功能时拼拼凑凑就能完成。7.3 大项目困境当组件树开始失控最后聊聊大项目的组织问题。组件树可以无限嵌套但人的认知能力有限。当项目规模超过一定阈值某个状态到底放在哪一层会变成日常争论。我的经验是先画一遍信息流明确哪些数据是页面私有的、哪些是跨页面共享的、哪些是服务端数据。页面级状态尽量留在页面容器组件内部别随意提升到全局store。全局store里放的东西越少越好放进去的每一项都要有两个以上不相关页面共用的理由。新人在代码评审时如果说不清某段逻辑为什么放在这个组件里那通常意味着位置不对找到它真正的主人再落位。这套分层思考的方法论比具体用哪个工具都重要。工具可以换但数据边界清晰、组件职责单一的原则不会变。8. 现代React的进阶方向并发渲染、服务端组件与新生态8.1 并发渲染带来哪些真实变化React 18最大的改动是引入了并发渲染机制useTransition、useDeferredValue这批API也随之而来。对实际业务的影响我用一个搜索场景来说明用户每敲一个字母搜索框立刻需要更新输入值同时还需要请求搜索结果。但在并发模式下可以把更新输入框作为紧急任务请求结果并刷新列表作为非紧急任务。用useTransition标记后React会优先处理输入更新让敲字的手感变得极其流畅而结果列表则作为低优先级任务慢慢渲染。const [isPending, startTransition] useTransition() const [keyword, setKeyword] useState() const [searchResult, setSearchResult] useState([]) const handleChange (e) { const value e.target.value setKeyword(value) startTransition(() { setSearchResult(mockSearch(value)) // 非紧急更新 }) }这种区分紧急与非紧急的能力是16.x时代无法想象的。它让React可以在保持交互流畅的同时完成繁重的渲染工作。8.2 Server Components数据获取方式的又一次革命服务端组件React Server ComponentsRSC是React团队近年来的重头戏它的核心思想是让部分组件在服务端执行直接把数据和渲染结果发给客户端从而减少客户端JS体积、提升首屏加载速度。我对RSC的理解有一点特别重要它不是为了替代客户端交互而是把纯数据获取模板渲染的那部分工作搬到服务端。一个典型的RSC示例// ServerComponent.server.js import db from ./db export default async function RelatedProducts() { const products await db.query(SELECT * FROM products WHERE related true) return ( div {products.map(p ( Card key{p.id} product{p} / ))} /div ) }这种组件没有交互逻辑、不需要useState、不需要客户端事件处理就非常适合跑在服务端。它返回HTML片段直接交给客户端客户端不再需要下载这段渲染逻辑的JS。在配合Next.js这样的全栈框架使用时体验非常自然。8.3 时代变化全栈框架成了主流选择现在的React生态已经不像五年前那么React只管视图层。Next.js、Remix这些全栈框架把路由、数据获取、构建、部署都收进了自己的体系里。尤其是Next.js的App Router已经把RSC、嵌套布局、服务端数据获取变成了默认能力。如果你是从零起步的新项目我的建议是直接使用Next.js而不是裸React。它的学习曲线虽然陡一点但规避了React项目如何组织构建、路由、数据获取这一大堆琐事。裸React更像一个库Next.js则是一个框架——前者把所有决策权交给你后者替你做了最佳实践决策。9. 写给不同阶段的开发者学习路径与避坑清单如果我是你的前端师父我会给你这样一条路径第一阶段跑通组件化概念。用create-react-app或vite搭一个项目实现组件拆分、props和state流动理解数据变了视图跟着变。第二阶段掌握Hooks。重点把useState、useEffect、useRef、useMemo、useCallback、useReducer全部过一遍配合实际小项目练手比如一个todolist加筛选器。别停留在能跑而是把每个Hook的特性边界搞清楚。第三阶段摸懂数据流。先用props一层层向下传然后用Context解决穿透问题再引入一个状态管理库。这个过程会让你理解为什么会有Context为什么会有全局store”。第四阶段工程化实战。实现一个完整的后台管理系统包括登录鉴权、路由嵌套、请求封装、权限控制、数据表格。把前面所有知识点串起来用一遍。第五阶段性能与架构。用Profiler定位性能瓶颈学会虚拟列表和代码分割研究一个中大型开源项目比如某个后台管理模板的整体代码组织方式学习别人的分层思路。避坑清单也顺手整理一份不要在render里做复杂计算挪到useMemo或用useDerivedValue类方案。不要随意往依赖数组放对象和函数必要时先缓存引用。不要把所有数据都塞进全局store服务端状态交给专门的请求缓存工具。不要直接用Math.random或者new Date()作为列表的key用稳定唯一的ID。不要把所有组件都包一层memo过度优化和从不优化一样危险。不要在一个useEffect里包含多个互不相关的副作用拆开写更清晰。React的核心心智模型说到底是数据驱动视图这六个字但真正吃透它需要你亲自经历几次debug、几次重构、几次被用户吐槽卡顿的时刻。框架更新迭代很快今天的新特性可能两年后就过时了但组件边界清晰、数据流可控、性能可预期这些工程原则永远不会过时。这也是我在无数个React项目之后最想传递给后来者的东西。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑