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。
- 每個 component 都會 return 一個 React element,
- React element 是一個物件,裡面包含了這個 component 的資訊,例如:
type
,props
,children
等等, - React runtime 解析 React element 成 DOM node,然後 render 到瀏覽器上。
React.createElement
負責將jsx
語法糖轉換成 React 看得懂的 object,因為有點離題,我把createElement
的解釋搬到文章最後。
重點來了,React 怎麼決定要不要 re-render?
- React 會用
Object.is(ElementBefore,ElementAfter)
去比較剛剛被createElement
轉成的物件差異,如果 returntrue
那麼就不會去 deep comparison 兩者的差異;如果 returnfalse
那麼 React 就會去先去找物件的type,
- 如果 物件的
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 的更深一層認識。