---
# System prepended metadata

title: 《React 思維進化》第一組讀書會小組討論筆記
tags: [React 思維進化]

---

🔗 [第一組章節分工](https://hackmd.io/Jn-rU9Y8QYCMj5yCo0robw)

[TOC]

# 2024/3/17 2-6 ~ 2-7

簡報：
- 2-6 單向資料流與一律重繪渲染策略： https://hackmd.io/@NoName21/S1XpSzspa#/
- 2-7 畫面組裝的藍圖：component 初探： https://hackmd.io/@NoName21/B1PMLS4Cp#/

導讀人： Aya
筆記工： Lois

## 問題討論

Q： 每個檔案最多可以有幾個 default export ?
A： 1 個

Q： UI 跟 Component 的區別
A： UI = 畫面 = 使用者介面，不是在指某個機制

Q： 策略二的一律清空畫面是模擬 React 的行為嗎？
A： 不是，2-6 在講實現單向資料流的 DOM 渲染策略都不是在指任何一個框架，而是在純 JavaScript 情境中能夠實現單向資料流的常見策略

Q： React 官方文件提到的 one-way data flow 是指它的東西會從最上層傳到最下層，還是指資料與畫面分離，這兩件事是同一件事嗎
A： 這是在講同一件事，但這裡不是在講單向資料流本身的意義，而是單向資料流在 React 中要做出的效果，這句話其實就是單一資料流的結果

額外補充：

- React 官方文件都只講結果，不講概念的本質，因為閱讀門檻高，就像翻遍官方文件找不到 React element 的詞，唯一能看到 React element 的詞只有在 createElement 可以找到，因為他放棄治療解釋什麼是 React element，所以現在都用 JSX element 這個說法在指 React element，反正沒有人會手動寫 React.createElement ，大家都寫 JSX，但真的懂 React 的人不可能不知道 React element

- `<Component/>` 跟 `{Component()}` 兩者呼叫的差異
    - 假設今天有一個 Foo component
        ```js
        const Foo = () => {
          return (
            <div>
              <h1>Foo</h1>
            </div>
          );
        };
        ```
        - 問題一：這個 console.log 出來的結果會是什麼？
            ```js
            const a = <Foo />;
            console.log(a);
            ```
            A.
            ```js
            <div>
                <h1>Foo</h1>
            </div>
            ```
            B.
            ```js
            <Foo/>
            ```
            答案是 ||B||
        - 問題二：當程式執行到這段時，`<Foo />` 是否已被呼叫？
            ```js
            const a = <Foo />
            ```
            答案是 ||沒有||
    - 這兩種不同的呼叫方法的差異：
        ```js
            const a = <Foo />
            const b = Foo()
            console.log(a) // {type: f Foo(), ...}
            console.log(b) // {type:"div", ...}
        ```
        - 這兩種呼叫方法最大的差異是，你必須透過建立 React element 的方法才能讓他被當作 component 來呼叫，這是唯一的方式
        - 如果是用 Foo() 來呼叫，React 只會當作它是一個普通的 funcion，它只是一個首字母為大寫的 function
        - React 並不是以首字母為大寫來判斷是不是 component
        - 首字母大寫對 React 來說沒有意義，對 transpiler 才有意義，因為最後 React 都會執行 React.createElement
    - 假設今天有一個小寫 a 作為 component
        ```js
        const a = () => {
          return <div/>
        };
        ```
        如果是使用 React.createElement 來呼叫 a ，仍會被當作是一個 component，只是不能寫 JSX，因為如果寫 JSX ，它會以為是一個 DOM element 類型
    - 所以能讓 React 正確的判斷是不是 component 是以呼叫的方式來決定，而不是由定義的方式來決定
    - 若使用下方這段程式碼，並且在 React 開發者工具來觀察
        ```js
        const Foo = () => {
          return (
            <div>
              <h1>Foo</h1>
            </div>
          );
        };
        root.render(
          <div>
            <Foo />
            {Foo()}
          </div>
        );
        ```
        就會發現，使用 Foo() 來呼叫在結構上並不會有 Foo 這個節點
    ![image](https://hackmd.io/_uploads/B1k7ej4Cp.png)
    所以其實使用 Foo() 來呼叫就等同於直接將 React element 填入
        ```js
        root.render(
          <div>
            <Foo />
            {Foo()}
          </div>
        );
        ```
        等同於：
        ```js
        root.render(
          <div>
            <Foo />
            {
              <div>
                <h1>Foo</h1>
              </div>
            }
          </div>
        );
        ```
    - 為何要使用 react element 的方法去呼叫 component？
        - 這是一種 React 延遲呼叫的設計，背後還有一個概念是控制反轉
        - 白話文是：我這裡想要一個 component 的實例，你幫我產
        - `<Foo />` 只是在描述我希望這裡產生一個 Foo component 實例的這件事，但還沒被呼叫
    - 當 component function return 了 react element 之後，reconciler 會開始進行分析如何將 react element 轉成 DOM element，當遇到不是 DOM element 的東西的時候才會去呼叫
    - 由上而下由外往內的流程產出 react element 的 tree 之後才會交給 renderer 一次轉換成實際的 DOM element
    - 子 component 的呼叫不是在父 component 建立 react element 的過程，會等到描述完了內部開始處理才開始呼叫
    - 所以使用 `{Foo()}` 來呼叫，在 runtime 時 JavaScript 引擎分辨不出這裡要建立一個 component 的實例，只是單純在描述一段 React element 的表達式
    - 為什麼 component 能夠帶有狀態，是因為 React 對 hooks 加料，能夠去讀取 component 內部的資料，所以直接使用 call function 去呼叫 component，就不是在 React 機制底下運作，就會導致 hooks 壞掉
    - 如果不是 component，則完全可以使用一個變數來描述一段 react element，甚至可以根據 props 傳入產生不同的 react element
        ```js
        const renderTextElement = (text) => <p>{text}</p>
        ```
    - 延遲呼叫還有一個好處是當我去建立這個 React element ，我可以知道你想呼叫什麼 component ，甚至知道你想傳入什麼 props ，React 其實會根據某些情況判斷不一定要去 render 這個 component
    - 如果將子 component 定義在父 component 之外，並使用`{}` 來呼叫，即使父 component 被 re-render，該子 component 因為被判定為同一個 component 為不會被 re-render
    - React 有一個內建的優化機制，發現其中有一個節點是 component 類型的，並且在兩次 render 之間為同一個 component 類型，則會略過這個 re-render
        ```js
        import React, { useState } from "react";

        function Foo() {
          console.log("render Foo");
          return (
            <div>
              <h1>Foo</h1>
            </div>
          );
        };

        const a = <Foo />;
        console.log("<Foo /> created");

        export default function App() {
          const [count, setCount] = useState(0);
          const handleClick = () => {
            setCount((prev) => prev + 1);
          };
          console.log("render App");
          return (
            <div className="App">
              {count}
              <button onClick={handleClick}>click</button>
              {a}
            </div>
          );
        }
        ```
    - 如果直接將`<Foo />`填入 component 內，每次這個 component 被 re-render 的時候都會重新建立一個 React element，所以在 component 內部的 `<Foo />` 縱使長相一樣，但仍然是不同的參考
    - 如果 `<Foo />` 沒有動態的 props ，則可以踢出 component 外面，但如果是有動態的 props ，則可以提升到外層，並使用 props 將 `<Foo />` 傳入，如下範例能夠做到在 `Container` 觸發 re-render 時，並不會觸發到 `Foo` 的 re-render
        ```js
        import React, { useState } from "react";

        function Foo({ count }) {
          console.log("render Foo");
          return (
            <div>
              <h1>Foo</h1>
              <p>{`Foo count: ${count}`}</p>
            </div>
          );
        }

        function Container({ content }) {
          const [count, setCount] = useState(0);
          const handleClick = () => {
            setCount((prev) => prev + 1);
          };
          console.log("render Container");
          return (
            <div>
              <h1>Container</h1>
              <p>{`Container count: ${count}`}</p>
              <button onClick={handleClick}>{`Container click`}</button>
              {content}
            </div>
          );
        }

        export default function App() {
          const [count, setCount] = useState(0);
          const handleClick = () => {
            setCount((prev) => prev + 1);
          };
          console.log("render App");
          return (
            <div className="App">
              <h1>App</h1>
              <button onClick={handleClick}>{`App click`}</button>
              <Container content={<Foo count={count} />} />
            </div>
          );
        }
        ```
    - 總結兩個重點：
        1. 一定要使用 React element 的方式呼叫 component function 才會被視為  component，React 判斷是不是一個 component 實例是跟呼叫方式有關，跟定義方式無關
        2. 使用 React element 呼叫才能夠要求 React 去幫你呼叫，這是一種延遲呼叫的設計，也才能夠讓 React 去建立 component 實例，並且做到以下效果：
            - 從實例中取出資料讓 hooks 正確運作
            - 可以在某些時候跳過 component function re-render

    Q： 要避免 children 不必要的渲染，透過 React.memo 包起來跟用 props 傳遞，這兩者有什麼區別？
    A： 有區別，但能做到差不多的結果，通常東西是完全固定的話就不需要使用到 memo，去做 memo 會做額外的比較，也會花效能，雖然可能不大，但如果是 props 是動態的，除了提升到外層，也可以用 memo，只是 memo 會比較所有值是否相同，有的時候提出來會影響到抽象化看起來不直覺或是 component 設計獨立性好不好呼叫的問題，但 Zet 大多數情況下不會使用 memo，除非遇到效能問題，但就結果來說，這兩種做法都可以，但沒有一定要在什麼情況下怎麼樣做。
    Q： 通常用什麼方式檢查 component 渲染很多次
    A：
    console.log 是最簡單的方法，但如果很了解這個機制，本來就會很有意地避免這個問題，但有的情況是這個東西被 re-render 也沒差，因為效能耗費不大，但有的情況是這個東西就不應該放在這裡被 re-render，這在寫的時候，如果你了解這個機制就會特別注意這個情況，或是適合放在這裡，但遇到效能問題，那就會用一些方法處理。
    或是使用 React 開發工具的 [profiler](https://www.youtube.com/watch?v=Qwb-Za6cBws) 來視覺化分析哪些 component 特別花時間跟多次被 re-render。
    基本習慣要養成好，但不需要過早優化，如果一開始寫就包 memo，那也不好，因為 memo 也會花額外的效能，遇到效能上的瓶頸時再來處理效能優化。
    
# 2024/3/31 2-8 ~ 2-9