停止追逐 React 重渲染

大多數開發者都在浪費時間對抗多余的重渲染。真正的 React 架構師根本讓問題無從產生——下面就來揭開他們的思路,以及為何大多數所謂的性能優化技巧反而拖慢了你的應用。


重渲染的無盡輪回

先來直擊痛點:如果還在項目里到處撒?useMemouseCallback,卻依然被卡頓困擾,接下來的內容務必深讀。

無數人在出現卡頓后,第一反應都是:追蹤渲染次數→猛貼優化鉤子→結果性能提升微乎其微→無限循環。實際上,重渲染只是“發燒”,真正的病灶在于設計層面的結構缺陷


四大隱藏性能殺手

經手數十個大中型 React 項目,以下四類反模式始終如影隨形,讓重渲染浪潮肆虐。

1. 全局狀態泛濫

“什么都往 Redux 丟”往往是成本最高的建議。

  • 問題:全局狀態一變動,所有訂閱組件都要檢查更新。

  • 案例:某電商項目中,購物車中一個價格變更竟觸發了 30 多個完全無關組件的重新渲染。

  • 對策:僅對真正全局的數據(如用戶認證)使用全局狀態,其它 UI 狀態盡量放到局部組件或更貼近使用場景的上層組件里。

2. 過度傳參(Prop Drilling)

深層組件鏈上反復傳遞 props,看似顯式卻會形成“瀑布效應”。

  • 問題:頂層過濾條件變化,一連串子孫組件都被迫重新渲染。

  • 案例:某儀表盤項目,切換一次篩選就導致 50+ 組件渲染,唯獨圖表組件才真正用到那條數據。

  • 對策:介于全局狀態與深度傳參之間,沿功能模塊中層節點建立局部 Context,真正做到只觸發相關區域重渲染。

3. Context 一鍋端

把所有狀態都放進一個 Context 看似方便,運行時賬單才會讓人心塞。

  • 問題:Context 更新會讓所有使用該 Context 的組件都重渲染。

  • 案例:某金融看板里將用戶數據、設置、實時行情都塞進同一 Context,導致行情每次波動時,設置面板也跟著刷新。

  • 對策:拆分 Context——按領域(用戶、UI、數據)或按更新頻率(靜態 vs. 動態)建立多個小 Context。

4. Key 用錯位

看似不起眼,卻能讓 React 重拆 DOM 而非局部更新。

  • 問題:使用數組索引做 key,會在列表重排時強制銷毀并重建所有子組件。

  • 案例:某客戶的列表拖拽效果卡頓幾秒,排查后發現正是索引 key 導致的整個列表重渲染。

  • 對策:務必用穩定且唯一的標識(如數據庫主鍵)作為 key,保證 React 精確復用組件。


五步性能制勝法則

真正的高手從不事后追渲染,而是從架構層面預防。以下五條策略,能讓應用從一開始就高效運行。

1. 狀態貼近使用場景

  • 原則:把 state 放在最近公共祖先

  • 實踐:將全局存儲中的小型 UI 狀態(展開/收起、選中項)轉移到相應組件內部或更低層次的父組件。

2. 有的放矢地 Memo 化

  • 原則:只對真正昂貴且頻繁執行的計算或組件使用?memouseMemouseCallback

  • 實踐:先通過 Profiling 確定性能瓶頸,再集中優化;避免對簡單字符串或小數組做無謂 memo。

3. Context 切片

  • 原則:用多個小 Context 取代一個巨 Context。

  • 實踐:按功能域(如 AuthContext、MarketDataContext)和更新頻率拆分上下文,確保微小更新不會連累無關組件。

4. 精準數據選擇器

  • 原則:組件只訂閱所需數據切片。

  • 實踐:在 Redux 中用?useSelector?精選字段;在 Context 中封裝自定義選擇鉤子,只對必要數據做依賴。

5. Profile-First 開發

  • 原則測量勝于臆斷

  • 實踐:把 React DevTools Profiler 當做日常工具;遇到卡頓先 Profile,再針對最耗時的渲染鏈條下手;借助?why-did-you-render?即時揭示多余渲染。


重構前后對比(一瞥)

重度耦合的 Todo 應用(重渲染災難版)

function?TodoApp()?{const?[todos, setTodos] = useState([]);const?[filter, setFilter] = useState('all');// 每次 render 都重建這些函數const?addTodo =?()?=>?{?/* ... */?};const?toggleTodo =?id?=>?{?/* ... */?};const?filtered = todos.filter(/* 多次執行 */);return?(<>{filtered.map(todo => (<TodoItemkey={todo.id}text={todo.text}onToggle={()?=>?toggleTodo(todo.id)}/>))}<Stats?count={todos.length}?/></>);
}

分層拆解后的高性能版

// 頂層只負責狀態管理
function?TodoApp()?{const?[todos, setTodos] = useState([]);const?[filter, setFilter] = useState('all');const?addTodo = useCallback(text?=>?{?/* 穩定引用 */?}, []);const?toggleTodo = useCallback(id?=>?{?/* 穩定引用 */?}, []);return?(<><AddTodoForm?onAdd={addTodo}?/><FilterControls?filter={filter}?onChange={setFilter}?/><TodoList?todos={todos}?filter={filter}?onToggle={toggleTodo}?/><TodoStats?todos={todos}?/></>);
}// 子組件按需 memo 和 useMemo
const?TodoList = memo(({ todos, filter, onToggle }) =>?{const?filtered = useMemo(()?=>todos.filter(/* ... */),[todos, filter]);return?filtered.map(todo?=>?(<TodoItem?key={todo.id}?todo={todo}?onToggle={onToggle}?/>));
});

實戰立刻可用的七條錦囊

  1. 耗時邏輯放進?useEffect避免在渲染階段執行重計算,改為渲染后再執行:

    // 錯誤示范:阻塞渲染
    function?MyComponent({ data })?{const?result = heavyCompute(data);return?<div>{result}</div>;
    }// 優化后:在 useEffect 中執行
    function?MyComponent({ data })?{const?[result, setResult] = useState(null);useEffect(()?=>?{const?res = heavyCompute(data);setResult(res);}, [data]);if?(result ===?null)?return?<div>Loading...</div>;return?<div>{result}</div>;
    }
  2. 列表虛擬化對于長列表,只渲染可視區域,推薦用?react-window?或?react-virtualized

    import?{ FixedSizeList?as?List }?from?'react-window';function?VirtualizedList({ items })?{return?(<Listheight={500}itemCount={items.length}itemSize={50}width="100%">{({ index, style }) => (<div?style={style}>{items[index]}</div>)}</List>);
    }
  3. 輸入防抖對于搜索、過濾等高頻輸入,使用防抖減少無效請求:

    import?{ useState, useEffect }?from?'react';function?useDebounce(value, delay)?{const?[debounced, setDebounced] = useState(value);useEffect(()?=>?{const?handler = setTimeout(()?=>?setDebounced(value), delay);return?()?=>?clearTimeout(handler);}, [value, delay]);return?debounced;
    }function?SearchComponent()?{const?[query, setQuery] = useState('');const?debouncedQuery = useDebounce(query,?300);useEffect(()?=>?{if?(debouncedQuery) {fetchData(debouncedQuery);}}, [debouncedQuery]);return?<input?value={query}?onChange={e?=>?setQuery(e.target.value)} />;
    }
  4. 組件邊界要合理將大而全的組件拆成職責單一的小組件:

    // 錯誤示范:所有邏輯都堆在一個組件里
    function?ProfilePage({ user, posts, comments })?{return?(<div><img?src={user.avatar}?alt=""?/><h1>{user.name}</h1>{/* ... posts 和 comments 也都在這里渲染 ... */}</div>);
    }// 優化后:拆分成多個子組件
    function?ProfilePage({ user, posts, comments })?{return?(<><ProfileHeader?user={user}?/><UserPosts?posts={posts}?/><UserComments?comments={comments}?/></>);
    }
  5. 代碼分割 + 懶加載對重量級組件動態加載,首屏加載更快:

    import?React, { Suspense, lazy }?from?'react';const?HeavyComponent = lazy(()?=>?import('./HeavyComponent'));function?App()?{return?(<Suspense?fallback={<div>Loading...</div>}><HeavyComponent?/></Suspense>);
    }
  6. 避免匿名函數出現在 JSX 中匿名函數每次渲染都會新建,導致子組件無謂重渲染:

    // 錯誤示范:每次渲染都會創建新的函數引用
    <button onClick={() => handleSubmit(id)}>Submit</button>// 優化后:用 useCallback 保持函數引用穩定
    import { useCallback } from 'react';function SubmitButton({ id, handleSubmit }) {const onClick = useCallback(() => handleSubmit(id), [handleSubmit, id]);return <button onClick={onClick}>Submit</button>;
    }
  7. 使用?use-context-selector?精準訂閱 Context只在真正使用的數據變化時觸發重渲染:

    import?React?from?'react';
    import?{ createContext, useContextSelector }?from?'use-context-selector';const?MyContext = createContext({?count:?0,?user: {} });function?Counter()?{// 只有 count 改變時才會重新渲染const?count = useContextSelector(MyContext, ctx => ctx.count);return?<div>Count: {count}</div>;
    }

將這些實戰錦囊逐條落地,你的 React 應用性能將從“修修補補”一躍到“結構先行”,讓優化變得水到渠成。


精英級思維:以數據流為核心

真正頂尖的 React 工程師,先思考「數據怎么流」,再設計組件。當數據來源、使用頻率、目的地都理清后,組件結構和狀態層次自然而然地對性能友好。


從“打怪”到“布局”——五步行動計劃

  1. 現狀體檢:Profile → 找出高頻渲染與濫用 Context/Prop Drilling。

  2. 重構組件樹:拆解垂直職責,狀態放最近公共祖先。

  3. 精準 Memo 化:先測再優化,去掉無效 memo。

  4. 細粒度選擇:改用自定義 Selector 鉤子,僅訂閱必要數據。

  5. 持續 Profile:每次改動后都要測,關注用戶可感知的卡頓。


真相大白

性能不是臨時加上的“辣椒粉”,而是從架構層面烹飪出來的佳肴。頂級工程師要做的,不是盲目貼HOOK,而是從一開始就讓渲染“剛剛好”,讓每一次更新都精準命中目標組件。

若仍在重渲染的陷阱里苦苦掙扎,不妨換個思路:數據流——組件邊界——狀態層次。掌握這三步,性能問題便無處藏身。

前端AI·探索:涵蓋動效、React Hooks、Vue 技巧、LLM 應用、Python 腳本等專欄,案例驅動實戰學習,點擊原文了解更多詳情。

圖片

最后:

python 技巧精講

React Hook 深入淺出

CSS技巧與案例詳解

vue2與vue3技巧合集

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/85387.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/85387.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/85387.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

流水線的安全與合規 - 構建可信的交付鏈

流水線的安全與合規 - 構建可信的交付鏈 “安全左移 (Shift-Left Security)”的理念 “安全左移”是 DevSecOps 的核心理念,指的是將安全測試和考量,從軟件開發生命周期 (SDLC) 的末端(發布前),盡可能地向左移動到更早的階段(如編碼、構建、測試階段)。 為何對 SRE 至…

???????神經網絡基礎講解 一

??一.神經網絡 ? ??1. 全連接神經網絡&#xff08;Fully Connected Network, FCN&#xff09;?? ??核心概念&#xff1a;?? ??輸入層??&#xff1a;接收原始數據&#xff08;如數字、圖片像素等&#xff09; 數字矩陣 。??隱藏層??&#xff1a;對數據…

MySQL 8.0 OCP 英文題庫解析(二十二)

Oracle 為慶祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免費考取原價245美元的MySQL OCP 認證。 從今天開始&#xff0c;將英文題庫免費公布出來&#xff0c;并進行解析&#xff0c;幫助大家在一個月之內輕松通過OCP認證。 本期公布試題201~210 試題2…

【大模型推理】PD分離場景下decoder負載均衡,如何選取decoder

https://mp.weixin.qq.com/s?__bizMzg4NTczNzg2OA&mid2247507420&idx1&sn4b32726abd205c7f94144bcb9105330f&chksmce64b9fc7f1d8de04a40b0153302dee52262c6f104c67195e2586e75c8093b8be493f252c8a3#rd 在非 Local 場景下&#xff0c;Prefill 定時獲取 Decode …

【IP地址】IP應用場景的使用方向

網絡安全領域 通過IP地址查詢&#xff0c;安全系統能夠實時監控網絡流量&#xff0c;識別異常訪問行為。例如&#xff0c;當某個IP地址在短時間內頻繁發起大量請求&#xff0c;且訪問模式與正常用戶存在明顯差異時&#xff0c;系統可將其標記為可疑IP&#xff0c;觸發風險預警…

3-18 WPS JS宏 顏色設置實例應用(按條件設置單元格顏色)學習筆記

前面講解了關于單元格的一些格式的設置&#xff0c;本節課再講解一下各種清除方法。 1.函數解析與用法 Range().clear()//清除全部 Range().Value2null //清除內容 Range().ClearContents()//清除內容 Range().ClearFormats()//清除格式 Range().EntireRow.Range()//以Ra…

從零開始的云計算生活——第二十天,腳踏實地,SSH與Rsync服務

目錄 一.故事背景 二.SSH帶外管理 1.概述 2. 配置文件 3.命令解析 4.登錄方式配置 a.用戶名密碼登錄 b.公鑰驗證登錄 5.實操生成密鑰對 三.Rsyncsersync實現數據實時同步 1.rsync概述 2.rsync運行原理 3.rsync部署 4.備份測試 配置備份目錄 5.rsyncsersync 實現…

SpringAI + DeepSeek大模型應用開發 - 初識篇

一、認識AI 1. AI的發展 AI&#xff0c;人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;使機器能像人類一樣思考、學習和解決問題的技術。 2. 大模型及其原理 在自然語言處理&#xff08;Natural Language Processing, NLP&#xff09;中&#xff0c;…

c++第八天-多態

虛函數虛析構函數純虛函數與抽象類 多態實現的條件&#xff1a;&#xff08;1&#xff09;公有繼承 &#xff08;2&#xff09;派生類重寫基類虛函數 &#xff08;3&#xff09;基類指針/引用指向派生類對象 虛函數不能是構造函數&#xff0c;不能是靜態函數&#xff0c;不能…

全景圖渲染Vue3+TS使用Photo Sphere Viewer插件實現

1.Photo Sphere Viewer插件安裝: title=插件安裝 體驗AI代碼助手 代碼解讀復制代碼npm install photo-sphere-viewer -S 或 yarn add photo-sphere-viewer -S 2.原始全景圖展示 初始化標簽容器 體驗AI代碼助手 代碼解讀復制代碼 // 全景圖的根節點必須要具備寬高 TS引用,創建…

Redis之分布式鎖(3)

這篇文章我們來詳細介紹一下如何正確地基于Redis實現分布式鎖。 基于Redis的分布式鎖實現 組件依賴 首先通過Maven引入Jedis開源組件&#xff0c;在pom.xml文件加入下面的代碼&#xff1a; <dependency><groupId>redis.clients</groupId><artifactId&g…

Java課堂筆記11

三個修飾符 一、abstract&#xff08;抽象&#xff09; 1.抽象方法只能在抽象的類里&#xff0c;只有方法的聲明&#xff0c;沒有方法的實現。&#xff08;沒有{}直接&#xff1b;結尾&#xff09;。 2.abstract修飾的類稱為抽象類。 注意&#xff1a;&#xff08;1&#x…

Linux 核心知識點整理(高頻考點版)

一、編譯與工具鏈 GCC 編譯流程 四階段&#xff1a;預處理&#xff08;-E&#xff0c;處理頭文件 / 宏&#xff09;→ 編譯&#xff08;-S&#xff0c;生成匯編&#xff09;→ 匯編&#xff08;-c&#xff0c;生成目標文件&#xff09;→ 鏈接&#xff08;生成可執行程序&…

輕量化社交管理方案:Skout與云手機的巧妙搭配

在移動社交時代&#xff0c;許多用戶開始嘗試通過多賬號管理來拓展社交圈層。近期測試了Skout社交平臺與亞矩陣云手機的搭配使用&#xff0c;發現這個組合為輕量級社交賬號管理提供了一個值得關注的解決方案。 基礎功能介紹 這套組合的核心優勢在于&#xff1a; 通過云手機實…

ETL連接器好用嗎?如何實現ETL連接?

目錄 一、ETL連接器的功能和優勢 1. 數據抽取能力 2. 數據轉換功能 3. 數據加載功能 4. 優勢總結 二、實現ETL連接的步驟 1. 需求分析 2. 選擇合適的ETL連接器 3. 配置數據源和目標系統 4. 設計ETL流程 5. 開發和測試ETL任務 6. 部署和監控ETL任務 三、ETL連接器在…

uniapp實現聊天中的接發消息自動滾動、消息定位和回到底部

前言 前言無需多言&#xff0c;想必大家對聊天軟件的功能已經很熟悉&#xff0c; 這里不做過多贅述&#xff0c;筆者通過uniapp實現聊天中的接發消息自動滾動、消息定位和回到底部。 代碼實現 <template><view class"chat-container"><!-- 消息列表…

MyBatisMyBatis plus

整合 MyBatis 到 Spring 或 Spring Boot 項目中&#xff0c;可以極大地簡化開發流程&#xff0c;尤其是當使用 Spring Boot 時&#xff0c;它提供了自動配置功能&#xff0c;使得集成更加簡便。 在 Spring Boot 中整合 MyBatis 1. 添加依賴 首先&#xff0c;在 pom.xml 文件中…

Stable Diffusion 實戰-手機壁紙制作 第二篇:優化那些“崩臉”和“馬賽克”問題,讓圖像更加完美!

歡迎回來!在《StableDiffusion實戰-手機壁紙制作》系列的第一篇中,我們成功完成了基礎操作,制作出了令人炫目的手機壁紙。 今天,我們將進入一個更高階的領域——優化處理。因為誰不想讓生成的藝術品更完美呢?尤其是避免“崩臉”和“馬賽克”這種讓人抓狂的問題! 創作的路…

408第一季 - 數據結構 - B樹與B+樹

B樹 性質 可以看見一個節點可以有多個數字了 然后也滿足左小右大的特征 然后所有的葉子節點都在同一層&#xff0c;然后2個數字的節點就可以有3個分支 然后呢&#xff0c;每個節點里面到底有幾個數字是有規定的公式的 就這個公式&#xff0c;m是5階的&#xff0c;算出來是2和…

SSRF5 Gopher 協議對內網 Web 服務進行 sql 注入 GET 類型和POST類型

實驗環境&#xff1b; Centos7.6上同時安裝sqli-lib和pikachu 一.Gopher 協議對內網 Web 服務進行 sql 注入 GET 類型 我們先訪問sqli-lib第1關 然后我們構造URL&#xff1a; http://192.168.112.12/pikachu-master/vul/ssrf/ssrf_curl.php?urlhttp://192.168.112.12/sql…