[TOC] --- 導讀人: Var 筆記工: Robert [本週簡報連結](https://gamma.app/docs/-25vpjd4aqjjo9r4?mode=present#card-pfx6e9xxc9q4k3v) --- # 2024/3/20 2-6 ~ 2-7 ## 1. 導讀討論 ### 1.1. Component 首字大寫 - 詳見 [4.3.2.](#432-React%E4%B8%8D%E6%9C%83%E7%94%A8%E9%A6%96%E5%AD%97%E6%AF%8D%E5%A4%A7%E5%AF%AB%E4%BE%86%E6%B1%BA%E5%AE%9A%E6%98%AF%E5%90%A6%E7%82%BAComponent%EF%BC%8C%E4%B9%9F%E4%B8%8D%E6%9C%83%E6%A0%B9%E6%93%9A%E5%AE%9A%E7%BE%A9%E6%96%B9%E5%BC%8F) ## 2. 觀念自我檢測 ### 2.1. 單向資料流是什麼 - 畫面最後的結果,是由原始的資料通過特定邏輯的處理而產生 - 只有在資料更新的時候才會觸發畫面更新 - 單向則代表不能逆向、不能去修改前面的原始資料 ### 2.2. 實現單向資料流常見的渲染策略有哪些?分別的優缺點是什麼? - 兩種 - 1. 人工比對,再針對特定的部分人工去更新 - 優點:因為只改動要更動的部分,所以效能較好 - 缺點:專案複雜龐大時,較難以真正判斷哪些 DOM Elements 需要被更新 - 2. React採用的、一律 (清空) [重繪],每次根據 更新 / 沒變 的資料 [重新渲染] - 優點:不需要去做判斷 - 缺點:一律清空 DOM Elemenets 再重繪可能導致效能問題 ### 2.3. React 為了維護單向資料流所採用的渲染策略為何?怎麼解決該策略的缺點? - 渲染策略 - 資料變則一律重繪 - 採取該策略的缺點 - 每次重繪直接修改Real DOM的話會有效能問題 - 如何解決 - 每次重繪是修改Virtual DOM 而不是 修改 Real DOM,最後確認修改後才去apply到Real DOM - 補充 - Virtual DOM 的 結構和資料 因為 沒有 跟瀏覽器的DOM去做綁定,所以可以藉由比較新舊的差異,去達成 DOM操作範圍的最小化 - Check 2-1 for more. ### 2.4. 什麼是 component? - 即模組化的元件、自訂義的畫面藍圖,方便我們重用程式碼片段 - 這個片段是為了達成特定的目的還有邏輯,才將這個片段抽象化成為component - component可由React.createElement的方式建立 ### 2.5. 什麼是抽象化 - 為了讓 component 更有重用性、通用性,進而歸納其特徵與行為,使其在特定情境下可使用、重用 - 根據特定情境,將相關特性抽象化 - 類似物件導向 (OOP) 的抽象性 (Abstract) 及 封裝 (Encapsulation) - 邏輯、流程的抽出、抽象 及 歸納 - 在React中就是定義Component、props、及state的過程 ### 2.6. 什麼是 props?為什麼 props 是唯讀的? - 用來讓你將特定參數傳入 component - why it's read-only - it would [violate the single-source-of-truth] if it's NOT read-only - further, you might be able to change the data of parent components from children if [that] really happens - i.e. 假如props是obj.或arr.的話,可以繞過react的機制去修改資料,但也可能導致非預期的結果 - 所以,保持props唯讀是必要的,以避免非預期結果的發生 - 真的需要修改 props 的 values,則經由 Child Comp. 中定義變數去接props,再去修改變數,才是相對正確的方式 - 補充 - [component] => 藍圖 - {props} => 讓藍圖可以客製化 - i.e. [飲料] 和 {去冰、少冰、少糖} - 唯讀是為了維護單向資料流 ### 2.7. 為什麼 component 命名中的首字必須大寫? - 詳見 [4.3.2.](#432-React%E4%B8%8D%E6%9C%83%E7%94%A8%E9%A6%96%E5%AD%97%E6%AF%8D%E5%A4%A7%E5%AF%AB%E4%BE%86%E6%B1%BA%E5%AE%9A%E6%98%AF%E5%90%A6%E7%82%BAComponent%EF%BC%8C%E4%B9%9F%E4%B8%8D%E6%9C%83%E6%A0%B9%E6%93%9A%E5%AE%9A%E7%BE%A9%E6%96%B9%E5%BC%8F) ## 3. 問題討論 ### 3.1. pg 122, 開發環境版本、Object.freeze - 環境:dev.、staging、prod. - 開發環境版本:dev. env. - 開發環境會限制你的操作 - 但是打包後的內容,React就 [不會] 做相關的操作和限制 -  ## 4. Zet 的講解 ### 4.0. Discord討論區的問題:React Element 用 \<Tag\> vs call function() 的寫法差異 - [React Element 用 \<Tag\> vs call function() 的寫法差異](https://discord.com/channels/1174010592282034216/1216737172162609283/1216737172162609283) ### 4.1. What would the content be if u consolelog a component? 1. a1 等同於 a2 ```javascript= /** Foo.js * export default function Foo () { * console.log('render Foo'); * return ( * <div> * <h1>Foo</h1> * </div> * ); * } */ const a1 = <Foo />; const a2 = React.createElement(Foo, null); ``` 2. consolelog a,則內容為何 ```javascript= const a = <Foo />; console.log(a); /** * A. * <div><h1>Foo</h1></div> * * B. * <Foo /> */ ``` - Ans.: B ### 4.2. 下列程式碼,當執行完 line 1,已經來到 line 2,則 the Foo Component 已經被呼叫到了沒?該 function 有沒有被至少呼叫 1 次? ```javascript= const a = <Foo />; ``` - Ans.: 還沒、呼叫 0 次 - 實際執行該檔案後並 **沒有** consolelog 'render Foo' ### 4.3. 兩種 呼叫方式 的差別 - [呼叫方式 1.]: `<Foo />` - \[呼叫方式 2.]: `Foo()` #### 4.3.1. [呼叫方式 1.] ==不會== 馬上呼叫 Component Function ```javascript= const a = <Foo />; console.log(a); // print nothing !! const b = Foo(); console.log(b); // print 'render Foo' ``` - 兩種呼叫方式最大的差別在於,必須透過 React.createElement 的方式 (即 [呼叫方式 1.]) 才能夠把 Foo 當作 React Element / Component 來呼叫 - \[呼叫方式 2.] 並 **不會** 被認為是 React Element / Component - 只會被認為是首字母大寫的 Function #### 4.3.2. React不會用首字母大寫來決定是否為Component,也不會根據定義方式 - 首字母大寫對React來說沒有太大意義,最後執行都是 React.createElement - 小寫一樣可以被當作 React Element (用 React.createElement 的方式) - 下面的 b 是 **==被當作 Function 傳入==** - `React.createElement(b, ...)` - 但是,用小寫就不能寫成jsx,不然 {{只會被當成 DOM Element}} - 相當於在 React.createElement 時 {{被 **當成字串** 傳入}} - `React.createElement('b', ...)` - 首字母大寫只對 Transpiler 有意義 - 拋開jsx,React會不會將一個 Function 視為一個 Component,只跟 **如何使用該 Function** 有關,跟如何定義 Function 無關 - 所以,`Foo()` 對 React 來說,完全不覺得要建立一個 Component (見 4.3.3.) #### 4.3.3. 用 [React Developer Tools](https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?pli=1) 檢視 兩種呼叫方式 的 結果 ```javascript= React.createElement( <div> {/* [呼叫方式 1.] */} <Foo /> {/* [呼叫方式 2.] */} {Foo()} </div> ); ``` - [呼叫方式 1.] 會被 React 產生 Component Instance / Fiber Node - Component Instance (Component 實例) - 顯示 Component 層級 - [呼叫方式 2.] 相當於在 Function 呼叫處 直接貼上 return 的內容,所以完全 **不會** 被React 認為 需要建立 Component 實例 - **不會** 建立 Foo 那一層的 React 實例 - 所以,必須要注意,如果希望被當成 React Component (一般都是),就 **不要** 直接用 Function 的方式呼叫 ### 4.4. 為何要用 React.createElement 的方式呼叫,這樣的設計有什麼好處 #### 4.4.1. 為了達成 延遲呼叫 - 延遲呼叫背後還有個概念,控制反轉 - 延遲呼叫白話來說就是 ==**我跟你說我想要 呼叫 / 產生 什麼,你 (即 React ) 幫我呼叫**== - 我希望在 什麼地方 產生什麼 - 對 developer (以下簡稱 dev.) 來說,只是在 ==**描述 (還沒發生!!)**==,最後真正的執行是由 React (的機制) 來運作完成 - ==`<Component />` only means that u pass [the function / component] to React.createElement as an argument,**還沒有被執行**== - also check the example on pg.134 - 當 MyComponent1 已經開始被 render - 當執行到 line7: 發現是可以直接對應 DOM Element 的 React Element - 當執行到 line8: 發現 不可以 直接對應 DOM Element => **這時候才去執行 MyComponent2 (function / functional component),之後才填回原來描述的位置 (即 line8)** #### 4.4.2. 為什麼用 function call 的方式 不會 產生Component ```javascript= const Foo = () => (<span>Foo</span>); // ... // at the jsx return in some component return ( <div> {Foo()} <span>Foo</span> </div> ); ``` - 如上例,在 runtime 時, line8 和 line9 對 JS Engine 來說 **是一樣的**、無法區分,只會知道要在位置上產生相對應的內容,當然也 **不會知道** line8 是個 Component (function call 的方式只知道執行、並回填相對應的內容) - 並且,之所以能夠在 React Component 使用 hooks,就是因為 ==Functional Component (以下簡稱 FC)== 是 ==**加料** 過後的 function== - Function 本身是 **不應該** 有狀態的,則為何我們在 FC 中可以用 hooks 憑空掏出一些狀態? - => 因為每次在 FC 呼叫前,會先對 hooks 加料、讀取 React 內部 Component實例 的資料,使每次從 hooks 掏出的 狀態資料 會對應到 [Component實例的資料、Fiber Node裡面的資料] - **因此,如果直接用 function call 的方式在 jsx return 的地方去描述 Component,則 hooks 叫出來的資料可能產生問題,因為不是React機制的正常運作** - 若 FC 中沒有用到hooks則不會有出錯的問題,相當於普通的 function - so, `const foo = ({ yo }) => (<div>{yo}</div>);` is fine even with props passing in - 小寫 function,根據傳入的 props 產一段 React Element (以下簡稱 RE) - fine,we don't mean to treat it like a ==**React Component**== - ==**again,是否被視為 React Component 跟 怎麼被執行、怎麼被呼叫 有關,跟如何定義無關**== - 綜上可知,延遲執行 在 React Component 的運作有其必要 - 另,延遲呼叫的驗證範例 ```javascript= const Foo = () => { console.log('render Foo'); return <div>Foo</div>; }; const Yo = () => { console.log('render Yo'); const result = ( <div> <Foo /> </div> ); console.log('hihihi'); return result; }; /** * consolelog 結果 * * 'render Yo' * 'hihihi' * 'render Foo' // 執行到 line10,發現不知道該怎麼轉成對應的 DOM Element,才去呼叫 Foo function * * / ``` #### 4.4.3. 延遲呼叫的另一好處:使 不用 每次都 re-render the component that is [in the specific condition],在運作上更有效率 - [某些情況] 下,React (之後簡稱 R) 會用延遲呼叫的設計來達成效能優化 - React => 描述 特定歷史時刻 版本的畫面 - [如果有一個 Component 類型的 RE,在 兩次 render 之間,都是 **同一個物件、同一個 RE**] - [同一物件、同一個 React Element] => 相同則 沒有 變動需求 => 不會 / 不需要 re-render - 每次的 re-render,在 FC 的 function body 內,都會 重新產生 新的物件 / 新的 RE (該 function 在每次 re-render 被不斷的重新執行,每次都用 React.createElement 重新建立一個新的、每次都是不同的物件,只是看起來長得一樣,==**但是在記憶體中是不同的物件參考、記憶體位址不同**==) - 除非做 memo 或 等 其他額外的處理 - 假設 特定 Component 很吃效能,則可以用 [這個方式] 來撰寫,以達成效能優化 ```javascript= const Foo = () => <div>Foo</div>; // 狀況 1. 同一物件、同一個 React Element const a = <Foo />; const App = () => { const [count, setCount] = useState(0); const clickHandler = () => { setCount(count + 1); }; // 狀況 2. 每次渲染重新產生 const a = <Foo />; return ( <div> <button onClick={clickHandler}>+1</button> {/* 比較上面的 狀況 1. 和 狀況 2. */} {a} </div> ) }; ```
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up