Josh Chou

Josh Chou • 2023-10-19

Basic React render mechanism

範例

Description

“ADVANCED REACT” 初探 React render 的機制

前言

最近開始翻了 “ADVANCED REACT” 這本書,發現實在講得太好,很多實務操作可以直接應用在工作上,書中提到很多 commonly misused ways,可以拿來重新審視自己平常寫的(💩?)code。 因為本書的書名就是”Advanced”,所以幾乎沒有篇幅在介紹 React 官網就有的 useXXX 怎麼使用,較多著墨在 How to optimize your code,推薦給目前只有兩招 useState, useEffect 走天下,快要被看破手腳的同學。

React 是在 render 什麼?

借用書中 onScroll 監聽的例子,如果用如下最直覺的寫法,主管可能不會 approve 你的 PR:

const MainScrollableArea = () => {
  const [position, setPosition] = useState(300);

  const onScroll = (e) => {
    // calculate position based on the scrolled value
    const calculated = getPosition(e.target.scrollTop);
    // save it to state
    setPosition(calculated);
  };
  return (
    <div className="scrollable-block" onScroll={onScroll}>
      {/* pass position value to the new movable component */}
      <MovingBlock position={position} />
      <VerySlowComponent />
      <BunchOfStuff />
      <OtherStuffAlsoComplicated />
    </div>
  );
};

為什麼呢? 如果每一個 component 都這樣寫,那麼每次 scroll 都會觸發所有 component 的 render,這樣的效能是很差的,因為只有 MovingBlock 需要根據 scroll 的位置去 render,其他的 component 都不需要,我們要做的是把這一坨不需更新的部分用 props 的方式傳進來,如下:

  const ScrollableWithMovingBlock = ({ content }) => {
    const [position, setPosition] = useState(300);
    const onScroll = () => {...} // same as before
    return (
      <div className="scrollable-block" onScroll={onScroll}>
        <MovingBlock position={position} />
        {content}
      </div>
    )
  }

等等,這時候應該會問問題:「為什麼 position 更新了但是{content}不會被觸發更新?他們不是放在同個 component 裡嗎?」作者這時候會說你問的問題很棒,但事實並不是你想的那樣。們先來定義什麼是 components、什麼是 re-render?

什麼是 component? 其實就是一個 function。

  1. 每個 component 都會 return 一個 React element,
  2. React element 是一個物件,裡面包含了這個 component 的資訊,例如:type, props, children 等等,
  3. React runtime 解析 React element 成 DOM node,然後 render 到瀏覽器上。 React.createElement 負責將 jsx 語法糖轉換成 React 看得懂的 object,因為有點離題,我把 createElement 的解釋搬到文章最後。

重點來了,React 怎麼決定要不要 re-render?

  1. React 會用 Object.is(ElementBefore,ElementAfter) 去比較剛剛被 createElement 轉成的物件差異,如果 return true 那麼就不會去 deep comparison 兩者的差異;如果 return false 那麼 React 就會去先去找物件的 type,
  2. 如果 物件的 type 相同,React 就會依據 props 去 re-render component,如果 type 不同,React 就會把舊的 component 從 DOM tree 中移除,然後把新的 component render 到 DOM tree 中。

到這邊就解釋完為什麼 state 更新了但是 component 卻沒有 re-render,恭喜你的 PR 被主管 approve 了!

React.createElement 解釋

這邊借用官網的範例,他說明了如果不想用 jsx 的話,可以自行用 React.createElement 操作,上述提到的 jsx 轉成 React element 的過程就是透過 React.createElement 來完成的。

import { createElement } from 'react';

function Greeting({ name }) {
  return createElement(
    'h1',
    { className: 'greeting' },
    'Hello ',
    createElement('i', null, name),
    '. Welcome!'
  );
}

其實相等於

function Greeting({ name }) {
  return (
    <h1 className="greeting">
      Hello <i>{name}</i>. Welcome!
    </h1>
  );
}

下一篇文章會介紹一個 React pattern: Children as props,時至今日這個 pattern 大部分已經被 React hooks 取代,但是有些特定情境會用到,同時也會增加你對 React pattern 的更深一層認識。

Reference