Компилятор React

大家好,我是奶綠
React 最讓人期待的超強工具 React Compiler 登場了,雖然目前還是 beta 版,但已經可以來先研究一下這工具如何改變 React 的生態圈。

React Compiler 並沒有提供任何新語法或函式,他其實是 babel 的 plugins 工具,並支援 React17,18,19 皆可使用。

我們知道 JSX 是 React.createElement 的語法糖。

import React from 'react';  

const Example = () => {  
 const [count, setCount] = React.useState(0);  
 const atIncrement = React.useCallback(() => {  
 setCount((prev) => prev + 1);  
 }, []);  
 return (  

count:{count}
BTN    
    );   }; ```  經過 babel 編譯後會長這樣,只有 JSX 的部份會轉換  ``` import React from "react";   import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";   const Example = () => {    const [count, setCount] = React.useState(0);    const atIncrement = React.useCallback(() => {    setCount((prev) => prev + 1);    }, []);    return _jsxs("section", {    children: [    _jsxs("h1", { children: ["count:", count] }),    _jsx("button", { onClick: atIncrement, children: "BTN" })    ]    });   }; ```  安裝 React Compiler 後會變這樣,JSX 和變數都有轉換  ``` import { c as _c } from "react/compiler-runtime";   import React from "react";      const Example = () => {    const $ = _c(6);    const [count, setCount] = React.useState(0);    let t0;    if ($[0] === Symbol.for("react.memo_cache_sentinel")) {    t0 = () => {    setCount(_temp);    };    $[0] = t0;    } else {    t0 = $[0];    }    const atIncrement = t0;    let t1;    if ($[1] !== count) {    t1 = 
count:{count}
;    $[1] = count;    $[2] = t1;    } else {    t1 = $[2];    }    let t2;    if ($[3] === Symbol.for("react.memo_cache_sentinel")) {    t2 = BTN;    $[3] = t2;    } else {    t2 = $[3];    }    let t3;    if ($[4] !== t1) {    t3 = (    
    {t1}    {t2}    
    );    $[4] = t1;    $[5] = t3;    } else {    t3 = $[5];    }    return t3;   };      export default Example;   function _temp(prev) {    return prev + 1;   } ```  _c(6) 再去追一下原始碼,會發現這是一個名為 useMemoCache 的 React internal hooks,用來記錄需要的 Array。  ``` // useMemoCache source code   function useMemoCache(size) {    var memoCache = null, updateQueue = currentlyRenderingFiber$1.updateQueue;    null !== updateQueue && (memoCache = updateQueue.memoCache);    if (null == memoCache) {    var current = currentlyRenderingFiber$1.alternate;    null !== current && (current = current.updateQueue, null !== current && (current = current.memoCache, null != current && (memoCache = {    data: current.data.map(function(array) {    return array.slice();    }),    index: 0    })));    }    null == memoCache && (memoCache = { data: [], index: 0 });    null === updateQueue && (updateQueue = createFunctionComponentUpdateQueue(), currentlyRenderingFiber$1.updateQueue = updateQueue);    updateQueue.memoCache = memoCache;    updateQueue = memoCache.data[memoCache.index];    if (void 0 === updateQueue)    for (updateQueue = memoCache.data[memoCache.index] = Array(size), current = 0; current < size; current++)    updateQueue[current] = REACT_MEMO_CACHE_SENTINEL;    memoCache.index++;    return updateQueue;    } ```  React Compiler 的運作原理如下:  1 使用 useMemoCache(size) 建立需要的 Array 物件,這裡只有第一次 Render 時會建立,並在建立時將 Array 填滿 Symbol.for(“react.memo_cache_sentinel”)。  2 判斷該 Array 指定 index 的值是否為 Symbol.for(“react.memo_cache_sentinel”),是的話會是第一次 render。  3 看一下範例的 atIncrement 函式,React Compiler 會把 useCallback 自動刪掉,如果有用 useMemo 也會刪掉。  ``` // 第一次 render    if ($[0] === Symbol.for("react.memo_cache_sentinel")) {    // 建立 increment 函式。    t0 = () => {    setCount(_temp);    };    $[0] = t0;    } else {    // 建立過就直接取回    t0 = $[0];    } ```  用最簡單的 if else 判斷,如果 Array index 裡的值為預設的 Symbol.for(“react.memo_cache_sentinel”),就建立函式,否則就從 Array 裡取回,這樣就可以避免重新建立函式,完全等價於 useCallback。  4 JSX 的優化部份  ``` if ($[1] !== count) {    t1 = 
count:{count}
;    $[1] = count;    $[2] = t1;    } else {    t1 = $[2];    } 
過去 React 以 Component 做 Render 的單位,而 React Compiler 則是可以自動細到 JSX 元素,從上方的結果可以看到因為 h1 元素有使用到 count 變數,React Compiler 就把他抽出來判斷,count 有變化,才重新建立 h1 元素,等於幫你把 JSX 元素自動掛上 useMemo。

5 函式抽離

// source
const atIncrement = React.useCallback(() => {
setCount((prev) => prev + 1);
}, []);

// React Compiler
if ($[0] === Symbol.for("react.memocachesentinel")) {
t0 = () => {
setCount(_temp);
};
$[0] = t0;
} else {
t0 = $[0];
}
function _temp(prev) { // 函式被移出 Component 了。
return prev + 1;
}
```

本來的 atIncrement 裡有個 (prev)=> prev + 1 的函式,因為該函式和 Component 無關,是一個 Pure function,React Compiler 就把他抽離 Component 層級。

6 React.memo
那還需要 React.memo 嗎 ? 答案是要看情況。

import React from 'react';  

const SomeComponent = ({data}) => {   
 return 
{data.value}
;   }   const Example = () => {    const [count, setCount] = React.useState(1);    return ;   };      // React Compiler   import { c as _c } from "react/compiler-runtime";   import React from "react";   const SomeComponent = (t0) => {    const $ = _c(2);    const { data } = t0;    let t1;    if ($[0] !== data.value) {    t1 = 
{data.value}
;    $[0] = data.value;    $[1] = t1;    } else {    t1 = $[1];    }    return t1;   };      const Example = () => {    const $ = _c(2);    const [count] = React.useState(1);    let t0;    if ($[0] !== count) {    t0 = ;    $[0] = count;    $[1] = t0;    } else {    t0 = $[1];    }    return t0;   };   export default Example; ```  React Compiler 知道 SomeComponent 會用到 count,當 count 有變化才重新建立,就算是傳 Object 也可以。  以這個範例來看,就不需要 React.memo,如果有需要自行控制 React.memo 的比對方法,還是可以使用 React.memo。  React Compiler 之可以提升效能,是因為可以針對每個 JSX 來 Memo。而且再也不需要 useCallback 和 useMemo 了(Ya)。  如果想在現行專案使用 React Compiler,但又怕影響到現有程式碼,可以在 React Compiler Config 將 compilationMode 設定為 annotation。  ``` const ReactCompilerConfig = {    compilationMode: 'annotation',   }; ```  然後在你需要的 Component 新增這段 ”use memo”,那就只有這個 Component 會啟用 React Compiler。  ``` const MyComponent = () => {    'use memo'; // 加這個就會過 React Compiler   } ```  反正如果沒有設定 compilationMode,那就是全專案啟用,如果遇到不想要過 React Compiler 的話,就可以加 “use no memo”  ``` const MyComponent = () => {    'use no memo'; // 加這個就不會過 React Compiler   } ```  目前奶綠在使用 react-hook-form + React Compiler 時有遇到奇怪的 Bug。  有興趣的朋友可以先在官方的Playground玩看看   [React Compiler Playground](https://playground.react.dev/#N4Igzg9grgTgxgUxALhASwLYAcIwC4AEASggIZyEBmMEGBA5DGRfQNwA6Adl3BJ2IQCiAD1LYANggIBeAgAoAlDIB8BYFwIFe-QgG1eUTngA0BMAjwBhaEYC6M4szwA6KOYDKeUngRyADAocnJraAgTeAJKccEwYCEYOJOQubgiWpOLiAEbkANZyiipqGppmFtaGeAVYTABuStKqNQi1BADUBACMgSUAvqa6tj3BBEx4sMFyJZoAPOYUaHzK06UzABadygZGyMDbeL0zAPQbyyOlBDNZUHh4fAR8luJocLnSwJHRsfEHygBCABUAHLHa63JYrY7zPCLThnTTDXpBLgIYQ4fAEAAmCEopCg4iEogkCCCIF6QA)  參考資料:   [https://www.developerway.com/posts/how-react-compiler-performs-on-real-code](https://www.developerway.com/posts/how-react-compiler-performs-on-real-code)



Перекладено з: [React Compiler](https://milkmidi.medium.com/react-compiler-a40198e15318)

Leave a Reply

Your email address will not be published. Required fields are marked *