日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

理解這個機制,是成為React性能優化高手的關鍵

來源: 責編: 時間:2024-01-16 10:14:01 232觀看
導讀本來是準備優先分享兩個官方定義的 Hook useMemo,useCallback,不過這兩個 hook 本身其實沒有太多探討的空間,他們只是兩個記憶函數,本身并沒有特殊的、更進一步的含義。許多人的困惑往往來源于對于它們兩個過度解讀,認為他

QE928資訊網——每日最新資訊28at.com

本來是準備優先分享兩個官方定義的 Hook useMemo,useCallback,不過這兩個 hook 本身其實沒有太多探討的空間,他們只是兩個記憶函數,本身并沒有特殊的、更進一步的含義。QE928資訊網——每日最新資訊28at.com

許多人的困惑往往來源于對于它們兩個過度解讀,認為他們的存在對 React 性能的優化有非常重要的意義。過渡解讀導致了對他們的濫用。在我看過的項目中,有個別優秀前端團隊里的項目規范里,也錯誤抬高了他們的作用,把他們用在了每一個組件里。QE928資訊網——每日最新資訊28at.com

出現這樣問題的根源就在于對 React 的自身機制理解不夠精準。因此我決定換一個角度去帶大家理解 React 本身的優化機制,從而能夠正確的使用 useMemo 與 useCallback。QE928資訊網——每日最新資訊28at.com

本文將會從應用層面來為大家分析我們應該怎么做。后續的章節將會從 Fiber 的雙緩存策略開始分享底層的優化機制。QE928資訊網——每日最新資訊28at.com

一、精簡節點

首先我們要明確一些前置知識。QE928資訊網——每日最新資訊28at.com

React 在內存中維護了一顆 虛擬 DOM 樹,這顆樹的每一個節點是一個 Fiber,每一個 Fiber 都由 JSX 中的組件解析而來。QE928資訊網——每日最新資訊28at.com

type Fiber = {  // 用于標記fiber的WorkTag類型,主要表示當前fiber代表的組件類型如FunctionComponent、ClassComponent等  tag: WorkTag,  // ReactElement里面的key  key: null | string,  // ReactElement.type,調用`createElement`的第一個參數  elementType: any,  // The resolved function/class/ associated with this fiber.  // 表示當前代表的節點類型  type: any,  // 表示當前FiberNode對應的element組件實例  stateNode: any,  // 指向他在Fiber節點樹中的`parent`,用來在處理完這個節點之后向上返回  return: Fiber | null,  // 指向自己的第一個子節點  child: Fiber | null,  // 指向自己的兄弟結構,兄弟節點的return指向同一個父節點  sibling: Fiber | null,  index: number,  ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,  // 當前處理過程中的組件props對象  pendingProps: any,  // 上一次渲染完成之后的props  memoizedProps: any,  // 該Fiber對應的組件產生的Update會存放在這個隊列里面  updateQueue: UpdateQueue<any> | null,  // 上一次渲染的時候的state  memoizedState: any,  // 一個列表,存放這個Fiber依賴的context  firstContextDependency: ContextDependency<mixed> | null,  mode: TypeOfMode,  // Effect  // 用來記錄Side Effect  effectTag: SideEffectTag,  // 單鏈表用來快速查找下一個side effect  nextEffect: Fiber | null,  // 子樹中第一個side effect  firstEffect: Fiber | null,  // 子樹中最后一個side effect  lastEffect: Fiber | null,  // 代表任務在未來的哪個時間點應該被完成,之后版本改名為 lanes  expirationTime: ExpirationTime,  // 快速確定子樹中是否有不在等待的變化  childExpirationTime: ExpirationTime,  // fiber的版本池,即記錄fiber更新過程,便于恢復  alternate: Fiber | null,}

state 的每次變化,都會引發整棵樹的前后對比,從而導致許多組件重新執行。這也是 React 性能消耗的主要成本。但是 React 內部采用緩存機制和優秀的 Diff 算法極大的減少了這里的成本,后續我們會詳細介紹這兩個機制。QE928資訊網——每日最新資訊28at.com

這里我要重點介紹的是,在使用中,我們可以通過減小這顆 Fiber tree 的方式來達到性能優化的目的。只要 Fiber tree 足夠小,diff 的成本就會非常的低。QE928資訊網——每日最新資訊28at.com

例如,我們有一個非常大的巨石項目,當我們路由切換的時候,會直接刪掉前一個頁面的所有內容,只渲染新頁面的內容,那么,雖然隨著訪問頁面的數量越來越多,緩存在全局狀態管理器中的數據越來越復雜,但是 Fiber tree 的大小其實并沒有變得越來越大,依然維持在一個頁面的量級,此時的 diff 壓力跟一個小型項目沒有什么區別。通過這種手段,我們可以輕松保持一個巨石項目的高性能。QE928資訊網——每日最新資訊28at.com

落實到具體的頁面上,特別是在一些管理系統里,許多開發者喜歡在在列表頁中,維護一個內容超級復雜的彈窗組件,彈窗的內容是列表的詳情。此時,彈窗內容和列表內容同時存在,從而導致了 Fiber tree 的龐大。QE928資訊網——每日最新資訊28at.com

從交互上,我們可以將復雜的彈窗內容移植到一個新的詳情頁,就能極大的緩解 diff 壓力。QE928資訊網——每日最新資訊28at.com

在某些項目中,一個詳情頁有幾百條表單需要填寫。我們可以通過分步驟的方式,把這幾百個表單項切分到不同的步驟里,從而讓同時渲染出來的表單項大量減少,性能也會有很大的提高。QE928資訊網——每日最新資訊28at.com

總的來說,只要我們把 Fiber 節點數量控制在一定范圍內,React 都能保持一個非常高的性能。因此大多數情況下,我們并不需要做額外的性能優化。QE928資訊網——每日最新資訊28at.com

二、比較方式

由于大量的 re-render 存在,我們很容易能想到一個優化策略,在 diff 過程中,當我比較之后發現有的節點并沒有發生任何變化,那么我們就可以跳過該組件的 re-render,從而提高性能。QE928資訊網——每日最新資訊28at.com

而要讓這個優化想法落地,我們就必須了解內部的比較規則,首先要考慮的第一個問題就是。QE928資訊網——每日最新資訊28at.com

如何知道一個組件是否發生了變化

一個 React 組件是否發生了變化由三個因素決定。QE928資訊網——每日最新資訊28at.com

  • props
  • state
  • context

這三個因素中的任何一個發生了變化,組件都會認為自己應該發生變化。state 和 context 都是不可變數據,而且由于是我們主動調用 dispatch 去觸發他們發生改變,因此 state 和 context 的變化一般不會對我們造成理解上的困擾。QE928資訊網——每日最新資訊28at.com

最麻煩的是 props。QE928資訊網——每日最新資訊28at.com

React 組件的每次執行,都會傳入新的 props 對象,雖然內容可能一樣,但是在內存中卻已經發生了變化。QE928資訊網——每日最新資訊28at.com

function Child(props) {}// 執行一次傳入新的對象Child({})// 執行一次傳入新的對象Child({})

與 state 不一樣的是,props 并沒有緩存在別的地方,因此,一個組件 的 props 哪怕什么都沒有變化,比較的結果也是 false。QE928資訊網——每日最新資訊28at.com

var preProps = {}var curProps = {}preProps === curProps // false
var preProps = { name: 'Jake' }var curProps = { name: 'Jake' }preProps === curProps // false

呵!沒想到吧。QE928資訊網——每日最新資訊28at.com

也就是說,當一個子組件接收一個函數作為 props,為了保證函數的引用不發生變化,有的人選擇使用 useCallback 來緩存函數引用,從而期望子組件不會因為 props 發生了變化而導致子組件重新渲染。QE928資訊網——每日最新資訊28at.com

function Demo() {  ...  const change = useCallback(() => {}, [])  return (    <div>      ...      <Filter change={change} />    </div>  )}

結合我們剛才說的,這里只使用 useCallback 是做了無用功。QE928資訊網——每日最新資訊28at.com

preProps = { change: change }curProps = { change: change }preProps === curProps // false

那么問題就來了,如果這樣子的話,豈不是每個組件的 props 都會發生變化了?QE928資訊網——每日最新資訊28at.com

當然不是,React 內部針對 props 有另外一個策略:QE928資訊網——每日最新資訊28at.com

如果父組件被判定為沒有變化,那么,在判斷子組件是否發生變化時,不會比較子組件的 props。QE928資訊網——每日最新資訊28at.com

源碼里少一個判斷,卻衍生出這樣一個精妙的設計。QE928資訊網——每日最新資訊28at.com

高級!QE928資訊網——每日最新資訊28at.com

除此之外,Fiber Tree 的根節點,被判定為始終不會發生變化。QE928資訊網——每日最新資訊28at.com

這樣,根節點的子組件在比較時,react 就一定會跳過 props 的比較,以此類推。我們就有機會構造一個高性能的更新過程。QE928資訊網——每日最新資訊28at.com

回到我們經典的數字遞增案例,來分析這個案例。QE928資訊網——每日最新資訊28at.com

function Child() {  console.log('我不想重新渲染')    return (    <div>我不想重新渲染</div>  )}export default function Demo02() {  const [count, setCount] = useState(0)  return (    <div className="wrapper">      <div onClick={() => setCount(count + 1)}>{count}</div>      <Child />    </div>  )}

當我們點擊數字的時候,數字遞增,父組件 Demo02 被判定為改變,因此,內部的所有子組件都需要比較 props,props 為不可變數據,子組件 Child 的 props 進行了如下比較,結果為 false 。QE928資訊網——每日最新資訊28at.com

{} === {} // false

因此,Child 雖然不想 re-render,但是每次 count 變化都 render 了。QE928資訊網——每日最新資訊28at.com

調整的方式非常簡單,只需要讓父組件的 state 沒有發生變化即可,把變化的部分單獨封裝在另外一個子組件里。QE928資訊網——每日最新資訊28at.com

function Change() {  const [count, setCount] = useState(0)  return (    <div onClick={() => setCount(count + 1)}>{count}</div>  )}export default function Demo02() {  return (    <div className="wrapper">      <Change />      <Child />    </div>  )}

這個時候,父組件被判定為沒有發生變化,因此子組件就會跳過 props 的比較,從而 Child 判定為沒有發生變化。這樣我們的目的就達到了。QE928資訊網——每日最新資訊28at.com

但是,這里有一個前期條件,那就是我們需要確保 Demo02 的父組件也被判定為沒有發生變化,因此,如果你是 React 架構師,頂層結構的設計是你需要關注的重中之重,因為如果頂層出了問題,導致父組件不滿足這樣的穩定結構,那么后續的子組件都會 re-render。QE928資訊網——每日最新資訊28at.com

那么理解這個規則很難嗎?其實不難,難就難在,在看這篇文章之前,可能你壓根就不知道這個設計啊。QE928資訊網——每日最新資訊28at.com

如果我們有一個不靠譜的 React 架構師,頂層組件的穩定結構出了問題,那么我們有什么手段,能夠低成本的讓你能接觸到的頁面結構保持穩定呢?QE928資訊網——每日最新資訊28at.com

答案就是 React.memo。QE928資訊網——每日最新資訊28at.com

memo 函數會讓組件的 props 比較方式發生變化,我們之前都是一直用的 === 全等比較,使用 memo 包裹組件之后,React 內部會改變比較策略,他會遍歷 props 的每個屬性,如果每個屬性都能通過全等比較,那么就判定為 props 沒有發生變化。QE928資訊網——每日最新資訊28at.com

這個遍歷過程只會發生在 props 對象的第一層屬性,不會更進一步深入。QE928資訊網——每日最新資訊28at.com

因此,當我們無法確定上層組件是否發生變化時,我們可以在某一個節點使用 memo 來確保從這一層開始建立穩定的高性能模式。QE928資訊網——每日最新資訊28at.com

function _Child() {  console.log('我不想重新渲染')    return (    <div>我不想重新渲染</div>  )}var Child = memo(_Child)export default function Demo02() {  const [count, setCount] = useState(0)  return (    <div className="wrapper">      <div onClick={() => setCount(count + 1)}>{count}</div>      <Child />    </div>  )}

當我們使用 memo 包裹子組件導致 props 的比較方式發生變化時,useCallback 緩存引用就有用了。這也是 useCallback 的主要作用,他一定要結合 memo 去使用。QE928資訊網——每日最新資訊28at.com

當然,我們也可以用一些騷操作來達到同樣的目標,利用 useMemo 來緩存組件。QE928資訊網——每日最新資訊28at.com

export default function Demo02() {  const [count, setCount] = useState(0)  const _child = useMemo(() => {    return <Child />  }, [])  return (    <div className="wrapper">      <div onClick={() => setCount(count + 1)}>{count}</div>      {_child}    </div>  )}

當你決定要自己設計比較規則時就可以采用這樣的方式。QE928資訊網——每日最新資訊28at.com

三、總結

這篇文章分享了兩個 React 項目性能優化的最重要的手段。我們只要了解了真實的底層機制,就能寫出高性能的代碼,他們的理解難度并不高。我們只需要在項目中正確的去編寫符合他們機制的代碼即可。QE928資訊網——每日最新資訊28at.com

如果你是 React 項目架構師,那么你一定要吃透這個機制,在頂層架構中,我們會額外添加 Router/Redux 等諸多頂層組件,他們會不會導致高性能結構的崩塌,你一定要非常明確。QE928資訊網——每日最新資訊28at.com

除此之外,當頂層的父組件不變判定被破壞,我們也不需要每一個組件都用 memo 包裹起來,只需要在合適的節點包裹一個組件即可。因為 memo 的比較本身也會增加程序的執行成本,大量的 memo 反而會導致性能變得更低。QE928資訊網——每日最新資訊28at.com

除此之外,我們要明確,組件的 re-render 是內存行為,他是執行了一次 JS 函數,他并不會導致瀏覽器真的發生渲染行為,因此 re-render 的執行也是非常快速的,大多數情況下的 re-render 都可以接受,不過超大量的 re-render 會導致執行壓力變大,所以用大量 memo 減少 re-render 并不一定是一件劃算的事情。QE928資訊網——每日最新資訊28at.com

利用少量的 memo 與 React 本身的緩存機制減少大量的 re-render 才是合理的方案。QE928資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-62353-0.html理解這個機制,是成為React性能優化高手的關鍵

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Spring為什么建議構造器注入?看看和你所理解的一樣嗎?

下一篇: 部署過Vue項目嗎?遇到404如何解決?

標簽:
  • 熱門焦點
  • 掘力計劃第 20 期:Flutter 混合開發的混亂之治

    在掘力計劃系列活動第20場,《Flutter 開發實戰詳解》作者,掘金優秀作者,Github GSY 系列目負責人戀貓的小郭分享了Flutter 混合開發的混亂之治。Flutter 基于自研的 Skia 引擎
  • 服務存儲設計模式:Cache-Aside模式

    Cache-Aside模式一種常用的緩存方式,通常是把數據從主存儲加載到KV緩存中,加速后續的訪問。在存在重復度的場景,Cache-Aside可以提升服務性能,降低底層存儲的壓力,缺點是緩存和底
  • 三言兩語說透設計模式的藝術-單例模式

    寫在前面單例模式是一種常用的軟件設計模式,它所創建的對象只有一個實例,且該實例易于被外界訪問。單例對象由于只有一個實例,所以它可以方便地被系統中的其他對象共享,從而減少
  • 學習JavaScript的10個理由...

    作者 | Simplilearn編譯 | 王瑞平當你決心學習一門語言的時候,很難選擇到底應該學習哪一門,常用的語言有Python、Java、JavaScript、C/CPP、PHP、Swift、C#、Ruby、Objective-
  • 谷歌KDD'23工作:如何提升推薦系統Ranking模型訓練穩定性

    谷歌在KDD 2023發表了一篇工作,探索了推薦系統ranking模型的訓練穩定性問題,分析了造成訓練穩定性存在問題的潛在原因,以及現有的一些提升模型穩定性方法的不足,并提出了一種新
  • 大廠卷向扁平化

    來源:新熵作者丨南枝 編輯丨月見大廠職級不香了。俗話說,兵無常勢,水無常形,互聯網企業調整職級體系并不稀奇。7月13日,淘寶天貓集團啟動了近年來最大的人力制度改革,目前已形成一
  • 疑似小米14外觀設計圖曝光:后置相機模組變化不大

    下半年的大幕已經開啟,而誰將成為下半年手機圈的主角就成為了大家關注的焦點,其中被傳有望拿下新一代驍龍8 Gen3旗艦芯片的小米14系列更是備受大家矚
  • iQOO 11S或7月上市:搭載“雞血版”驍龍8Gen2 史上最強5G Soc

    去年底,iQOO推出了“電競旗艦”iQOO 11系列,作為一款性能強機,iQOO 11不僅全球首發2K 144Hz E6全感屏,搭載了第二代驍龍8平臺及144Hz電競屏,同時在快充
  • DRAM存儲器10月價格下跌,NAND閃存本月價格與上月持平

    10月30日,據韓國媒體消息,自今年年初以來一直在上漲的 DRAM 存儲器的交易價格僅在本月就下跌了近 10%,此次是全年首次降價,而NAND 閃存本月價格與上月持平。市
Top 主站蜘蛛池模板: 汉中市| 永济市| 南召县| 乌拉特前旗| 镇雄县| 安岳县| 海林市| 大竹县| 扎兰屯市| 合作市| 如东县| 沐川县| 苗栗县| 绍兴县| 黄陵县| 吐鲁番市| 德清县| 龙山县| 宜君县| 昂仁县| 嘉义县| 内江市| 新邵县| 玉林市| 黄冈市| 文昌市| 厦门市| 平利县| 鄢陵县| 闽侯县| 芦山县| 崇明县| 曲松县| 泌阳县| 萝北县| 华宁县| 永清县| 利川市| 榕江县| 梨树县| 清远市|