## CH.4 Component 的 ## 生命週期與資料流 #### 《React 思維進化》 2024/5/15 導讀人:LJBL Note: spotlight 效果要打開右上角的分享 > 自訂簡報選項,輸入: spotlight: enabled: true --- ## 4-1 Component 的生命週期 | 名詞 | 定義 | ---- | ----- | component | 以函示定義與封裝的 <span>藍圖<!-- .element: class="fragment" style="color: #1b22f5" data-fragment-index="1"--></span>,內含所希望的內容、行為、特徵與流程。 | <span>實例<!-- .element: class="fragment" style="color: #1bf58f" data-fragment-index="3"--></span>|<span>藍圖<!-- .element: class="fragment" style="color: #1b22f5" data-fragment-index="1"--></span>可產生<span>實例<!-- .element: class="fragment" style="color: #1bf58f" data-fragment-index="3"--></span>,每個<span>實例<!-- .element: class="fragment" style="color: #1bf58f" data-fragment-index="3"--></span>獨立存在,互不影響. | 生命週期 |代表的是一個 <span>component 實例<!-- .element: class="fragment" style="color: #fc499a" data-fragment-index="4"--></span>從出現到消失一個完整的過程。 Note: [hint]先講畫面 交互作用: component 要怎麼產生實例?就是在呼叫該 cp 時,React 會產生一個獨立的實例,進而出現在畫面上。而從出現到瀏覽器畫面到消失的過程就是該 component 實例的生命週期。 說著很抽象,但生命週期顧名思義,本來就等同生物從從受精卵開始、出生到世界上、到最後生命終結邁向死亡,這裡只是借來使用、主詞換成 component 實例,我們的世界換成瀏覽器而已。 ---- ### 4-1-1 Component 的三大生命週期 <ol> <li>Mount<!-- .element: class="fragment" data-fragment-index="1" --> </li> <li>Update<!-- .element: class="fragment" data-fragment-index="2" --></li> <li>Unmount<!-- .element: class="fragment" data-fragment-index="3" --></li> </ol> <font size=5>※ 無論是 class component 或 function component 都存在<!-- .element: class="fragment" data-fragment-index="4" --></font> ![](https://www.japaholic.com/storage/files/article_images/MzIwMjMxMTA3MjIzNzE4NjA=.jpg)<!-- .element: class="fragment" data-fragment-index="5" --> Note: 無論你是哪種 cp ,這三大生命週期都是存在的。 透過這章節、幫助我打通一些朦朧的觀念。對於從 class component 就開始學 React 的人來說,這三個生命週期是本來就存在的東西,因此轉變到 fn cp 搭配 hook 的時代,suppose 也是該存在。 但對我這種從 fn cp 往回學的人來說,過去看到 life cycle, mount, unmount 的文章總是自動略過,到了面試的時候,聽到考官一句:『請你解釋什麼是 component 的生命週期?』恍恍惚惚、答非所問。就像葬送的芙莉蓮裡面的弟子費倫、聽到芙莉蓮說『費倫啊~你是不是都沒有讀我給的魔法史書籍~』我跟費倫一樣心虛、或是有看沒有懂。所以今天就是那個直球面對假想大魔王的時刻,也是我選這一章節來報告的契機。 ---- ## coding time 先來看看熟悉的[那張圖](https://codesandbox.io/p/sandbox/4-1-1-life-cycle-example-ftsngf?file=%2Fsrc%2FApp.jsx) Note: [demo]兩個按鈕 > 增加圖片 > 刪除圖片 ---- ## <font class=rainbow-text>那份花了5h+的流程圖</font> <font size=6>大綱:帶過三個週期/含圖2-9-2</font> <font size=6>結果:[成果在此](https://www.figma.com/board/JcwpPwbCHjI86HrfySVp3N/life-cycle-of-function-component?node-id=0%3A1&t=NCmgdOLSy7lCQ9Kz-1)</font> <style> .rainbow-text { background: linear-gradient(to left, indigo, purple, blue, dodgerblue, green, orange, red); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } </style> ---- 剛剛是大家相對熟悉的 function component,那 class component 呢? ---- ### Class Component 的生命週期 API -1 ![c_cp_life_cycle_API](https://hackmd.io/_uploads/BJUU2-RMC.jpg =700x) <font size=5>derive v. 衍生的</font> / <font size=4>W3C school: [React Class Components](https://https://www.w3schools.com/react/react_class.asp)</font> Note: 望文生義,一個個帶過。 ---- ### Class Component 的生命週期 API -2 ![c_life_cycle](https://hackmd.io/_uploads/rk8T0WAzR.png) <font size=5>React 16.3 之後,圖片來源: [Wojciech Maj](https://github.com/wojtekmaj)</font> Note: 把抽象的東西講具體,用圖是最好的。 ---- ## 4-1-2 Function Component ## <font color=red>沒有<!-- .element: class="fragment" data-fragment-index="1" --></font>提供生命週期 API <span>在這個 moment,只要知道沒有就好了<!-- .element: class="fragment" data-fragment-index="2" --></span><span>(??)<!-- .element: class="fragment" data-fragment-index="3" style="color: #fcec03"--></span> <span>而最常被誤會的 useEffect hook <!-- .element: class="fragment" data-fragment-index="4" --></span><span>並不是 function component 所提供的生命週期 API <!-- .element: class="fragment" data-fragment-index="4" --></span><span>(??)<!-- .element: class="fragment" data-fragment-index="5" style="color: #fcec03"--></span> ---- ## 心中充滿<font color=#fcec03>??</font> <span>好吧,等等~~會劇透~~。<!-- .element: class="fragment" data-fragment-index="1" --></span> <span>怕暴雷的可以~~左轉先離開~~。(喂!)<!-- .element: class="fragment" data-fragment-index="4" --></span> Note: 在這個 moment,只要知道沒有就好了,而最常被誤會的 useEffect hook 也並不是 function component 所提供的生命週期 API 。 給 hint 圖 (那篇官方文章三個 method 總和) ---- #### 4-1 觀念檢測 1. 解釋 component 中的三大生命週期運作流程<!-- .element: class="fragment" data-fragment-index="1" --> 2. Function component 有生命週期的 API 嗎?<!-- .element: class="fragment" data-fragment-index="2" --> Note: --- ## 4-2 Function component 與 class component 的關鍵區別 1. 資料流存取的差異: <font color=yellow>`this` 的行為</font> 2. Function component 的特性:<font color=yellow>自動「捕捉」當下的資料</font> Note: 我們會先從 資料流存取的差異 開始說起,再帶到 function component 的關鍵特性、造成他們之間的關鍵區別 ---- ## 4-2-1 this 的存取陷阱 keyword: #非同步 #this.props #mutate #classComponent ---- # `this` 觀念複習 1. this 管的是 ‘before the dot’ 的主體 i.e. Object 2. 直到物件的方法被呼叫之前,this 是沒有值的 i.e. 在 run-time 才被賦值 3. 箭頭函式沒有自己的 this,會向外找到 Object 主體,再沒有就找向 window。 > 好文一生推 [Object method,this`](https://javascript.info/object-methods) ---- # 先說說 class component 書中精彩程式碼[範例](https://codesandbox.io/p/sandbox/4-2-1-function-class-n9g7q6?file=%2Fsrc%2FApp.jsx%3A29%2C9-30%2C32)解說 Note: [Demo 流程 hints] - Class component 直接在內部命名函式(OOP 概念、都是該class 的方法) - 說明 this 指向的地方(找到該主體,也就是該 component) [最後要 提供解法] - handleClick 這個內部函式 (inner) 被建立時,它記住了它所屬的外部作用域(lexical env)中的變數,即 showSuccessAlert 和 this.props 裡的 productName ,隨時可調用。 - 閉包(Closure):是函式以及該函式被宣告時所在的作用域環境(lexical environment)的組合。有內部函式跟外部函式。 ---- # "class" component - class 本質上讓 JavaScript 更加地向物件導向 (OOP) 設計靠攏 - 當資料狀態 (state) 改變時,直接以類別 (class) 中的方法(showAlert) 來 mutate 實例的屬性資料 (props) 就是 OOP 一貫做法 ``` this.props this.state ``` - 與 immutable 核心概念的 React 格格不入 ---- ## <font color=#f91a52>`this`</font> is mutable, ## therefore, it is unreliable. (in React)<!-- .element: class="fragment" data-fragment-index="1" --> Note: ---- # 那我說 function component 呢? 書中精彩程式碼[範例](https://codesandbox.io/p/sandbox/4-2-1-function-class-n9g7q6?file=%2Fsrc%2FApp.jsx%3A29%2C9-30%2C32)解說 Note: [Demo 流程 hints] 帶一下 App.jsx 一樣是傳進 selectedProduct 作為 productName 這個 props 的值, 但是因為 fn cp 在執行的當下、就會創建一個實例,這個實例作用類似快照功能,具體白話地說,就是將當下的 props, state 等資料拍成一張照片留存。 而這裡的 eH 一樣因為 closure 的特性,向該所在作用域找到的 callback fn 及其資料綁定,因而成為該次 render 獨有的 event Handler。這裡就小小劇透到下一個小節。 ---- ### 關鍵區別 ## function component 會自動「捕捉」該次 render 版本的原始資料 - function component 預設幫忙處理<!-- .element: class="fragment" data-fragment-index="1" --> - class component 需要費心留意 `this` 、有意識地提取脫鉤。<!-- .element: class="fragment" data-fragment-index="2" --> - this 的本質與 React immutable data 核心相斥<!-- .element: class="fragment" data-fragment-index="3" --> Note: 1. Function component 很自然地幫我們做好這些事情。該次 render 版本的原始資料都被「捕捉」下來,妥善保存。 2. 而不像 class component 需要費心留意 `this` 、有意識地提取脫鉤。 3. 若從根本上 this 的本質就跟 React immutable data 這個核心相斥,就該思考更適合的設計。 ---- # 因此...在 hooks 誕生之後 React 16.8 hooks 的問世,搭配 function component 的組合逐漸取代 class component、且成為 React 團隊強力推薦的主流選擇。 Note: 而也是因為兩者的關鍵區別,導致有了 hooks 的 function component 如虎添翼、逐漸取代 class component、且成為 React 團隊強力推薦的主流選擇。 ---- #### 4-2 觀念檢測 1. 在 class component 中的非同步事件裡以 `this.props` 來存取 props 的潛在問題? <!-- .element: class="fragment" data-fragment-index="1" --> 2. 「function component 會自動捕捉該次 render 時的 props 與 state 資料」是什麼意思?<!-- .element: class="fragment" data-fragment-index="2" --> Note: 自己寫個答案在這裡 --- ### (optional) ## 回到 4-1-2 Function Component 沒有提供生命週期 API (spoiler alert) Ch. 5-2 ---- class & fn cp 的範例對比 <font size=5>有一個 FriendStatus component,負責顯示朋友是否在線上。</font> | ![THIS_BAD](https://hackmd.io/_uploads/HkrQveJ7C.png) <br> <font size=5>透過 `this.prop` 在<br>(UN)MOUNT 時(取消)訂閱</font> | ![this_prop_2](https://hackmd.io/_uploads/r1zGoryXA.png) | -------- | -------- | Note: 帶著解釋一下程式碼 舊的官方 react.org 內容:有一個叫做 FriendStatus component 的範例,這個 component 顯示朋友是否在線上。 #1 我們的 class 從 this.props 中抓取 friend.id,在 component mount 後訂閱好友狀態, #2 並在 unmount 期間取消訂閱: 但是如果 component 顯示在螢幕上時,從 ChatAPI 來的friend prop 發生變化(也就是資料改變了、被 mutate),會發生什麼事? FriendStatus component 將繼續顯示不應該存在的好友的上線狀態。這是一個 bug。Unmount 時,由於取消訂閱的呼叫會使用錯誤的朋友 ID,因此也會導致 memory leak 或 crash。 React 官方文件 https://zh-hant.legacy.reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update ---- useEffect 對照 React class 生命週期 API | ![this_prop](https://hackmd.io/_uploads/BJkQwe17R.png) | ![hint](https://hackmd.io/_uploads/HJWCIDkmC.png) | | -------- | -------- | Note: 在 class component 中,我們需要加入 componentDidUpdate 來處理這種情況: 忘記正確處理 componentDidUpdate 是 React 應用程式中常見的 bug 來源。 ---- ## useEffect function component 版本:改搭配 useEffect Hook ![useEffect hook](https://hackmd.io/_uploads/ryqCKxyXC.png) 它沒有受這個 bug 的困擾。 (但我們也沒有對它進行任何更改。) Note: ---- <font size=4>因為<font color=#fc499a> useEffect 會預設處理更新</font>,所以沒有專門用於處理更新的程式碼。在應用下一個 effect 之前,它將清除之前的 effect。為了說明這一點,下面列出該 component 隨時間推移可能產生的一系列訂閱和取消訂閱的呼叫:</font> ![effect in details](https://hackmd.io/_uploads/H17RYx1m0.png =4000x) <font size=4>而又因為 function component 搭配採用 useState 跟 useEffect 等 hook ,更加貫徹了一律重繪的單向資料流策略,從根本上就確保了程式碼的一致性,避開了 class component 中常見的『由於缺少更新邏輯而導致的 bug』。</font> ---- ### 小結 >對於有提供 API 的 class component 來說,需要由 developers 有意識地主動撰寫應用多個 methods。而在漸趨複雜的專案中,useEffect 刻意地同步資料流設計處理副作用,相對更符合宣告式 (declaritive) 程式設計。 --- ## 4-3 每次 render 都有自己的 props、state 與 event handler 函式 ---- ## 4-3-1 每次 render 都有其自己版本的 props 與 state ---- #### <font color=red>誤解</font> #### React 會去監聽 state 改變進而觸發 re-render? ---- 再看一次[流程圖](https://www.figma.com/board/JcwpPwbCHjI86HrfySVp3N/life-cycle-of-function-component?node-id=0%3A1&t=NCmgdOLSy7lCQ9Kz-1) ![image](https://hackmd.io/_uploads/HytNhP1QC.png =500x) ``` const [imageCount, setImageCount] = useState(0) ``` <font size=5>透過 useState 這個 hook 取到當前的值 => 宣告 const imageCount = 0</font> Note: 帶流程圖, 觸發 > 呼叫 setState... 1. 利用 setState 儲存新的 state 值在 fiber node 裡 ---- 再看一次[程式碼](https://codesandbox.io/p/sandbox/4-1-1-life-cycle-example-ftsngf?file=%2Fsrc%2FApp.jsx) 每執行一次 component function,js 機制就會創建一個全新的作用域,因此其中的全新 state 變數 (imageCount) 、每一個相依而生的常數、event Handler 函式...都是該次專屬的,呼應前面各自獨立的實例及其存取的獨有資料。 Note: [demo hints] 2. 而再次執行 cp fn 的時候,會透過 useState 這個 hook 取到當前的值 3. => 宣告常數 const imageCount = 0,接著利用最新版的 state 變數與相對應有關的 props 重新執行一次 cp fn 。 ---- ## 4-3-2 每次 render 都有其自己版本的 event handler 函式 eventHandler[程式碼](https://codesandbox.io/p/sandbox/4-3-2-eh-qj2nqh?file=%2Fsrc%2FApp.jsx) Note: 1. 再次執行該 cp fn 的時候,宣告了一個新的 eH。而連帶宣告內部新的 setTimeout callback fn。 2. 因為 closure 特性,這個 sT cBack fn 可以「記住」該 Componenet function 執行時所對應的 state 跟 props。 3. 延伸結論,每次 render 也有自己版本的 eH、對應到該次的 props 跟 state 4. props 也是一樣的, ---- ## 4-3-3 Immutable 資料使得 closure 函式變得可靠而美好 Note: 那就帶領我們到最後一個小節,終於QQ ---- # 回顧 1. 在 fn cp 執行的時候,會捕捉當下的 props, state 等資料,快照形式儲存。 2. 以 event Handler 為例,會因為 closure 的特性,跟每次獨有的資料綁定、可隨時取得可靠的資料。 讓 eH 成為單向資料流的一部分。 ---- ## 快照 ![image](https://hackmd.io/_uploads/SkVGKqkmC.png) Note: (...前略)剛剛說過在 fn cp 執行的時候,會捕捉當下的 props, state 等資料,以類似快照形式儲存。 ---- 來點科普:Cyber Security -- Ransomware ![Ransomware](https://hackmd.io/_uploads/SJRWUs1XC.jpg) Note: 問問大家 Ransomware 是甚麼?有甚麼特性? 勒索 + 軟體 特性:專門竄改資料、加密資料;綁架你的寶貴資料、跟受害人勒索(資料的)贖金。 ---- 來點科普:Cyber Security -- Ransomware<br> <span>在眾多對抗勒索軟體的手段中,利用<font color=#1bf58f>快照復原</font>的解決方案是許多大廠皆有提到的重點。正是因為其<font color=#fc499a> immutable </font>的特性,讓 <font color=#fc499a>ransomware 無法竄改</font>過去的快照檔案。透過對 backup data 定期拍攝快照,IT 人員可以利用指定時間當下的快照、快速將系統修復還原。<!-- .element: class="fragment" data-fragment-index="1"--></span> ---- | ![image](https://hackmd.io/_uploads/SJRWUs1XC.jpg =350x) | key points: <br>1. 儲存多少張快照<br>2. 最短間隔時間<br>3. 恢復服務的所需時間<br>4. 設備的效能...<br> | -------- | -------- | Note: 太多可以講,有興趣的人歡迎多查詢了解 ---- # function component 的資料就像快照、真的都是 immutable 的? 設計理念是如此, 但很遺憾地偶爾會出錯, 因為 <span>事在人為<!-- .element: class="fragment" style="color: #fc499a" data-fragment-index="1"--></span> Note: 前面講到 function component 的特性是自動「捕捉」的資料類似快照。 但資料真的都像快照 immutable 的嗎? ---- If our data was stored in a snapshot #### mutable data is like... ![](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExanVuNHo5NW80ZjRheWgybXB0dmYwZHU0MjNxMnlpZXM0MmVubWFsNCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/RuQf0gFy8zsTYJ8Xlv/source.gif) Portraits in Hogwarts<!-- .element: class="fragment" data-fragment-index="1" --> Note: 。試想,如果資料會變動,那不是跟哈利波特裡面的畫像一樣了嗎?只能淡淡說一聲:唉,你不能期望照片裡的人物永遠在那裏啊! ---- 某些情況之下,props 還是可能被意外修改;例如在巢狀 object 的情況下,developers 不小心修改到下一層的資料。因此,即使有 closure 幫助我們可以取得外部資料,資料流也被破壞了。 這時就<font color=#fc499a>有賴 developers 有意識地避免</font>去 mutate 資料。 ---- ## 重點回顧 1. 設計理念 props 與 state 就像快照,該是 immutable 的 2. Closure 的精神 3. 延伸的內部函式也會是 immutable 4. 資料流的正確性與可靠性 => 開發者的責任是要確認資料是 immutable 的, 才可真正大聲說出快照的比喻 ---- # WHO? ---- ![you-im-pointing](https://hackmd.io/_uploads/Sy_tKiyQR.jpg) ---- # We are THE developers! Note: 這是我今天最後一張投影片,進入連環題目時間,測測看你的了解~ ---- #### 4-3 觀念檢測 1. React 會監聽資料的改變並自動觸發舊有畫面的修改嗎?為什麼? <!-- .element: class="fragment" data-fragment-index="1" --> 2. 為什麼每一次 render 中的 props 與 state 的值都是永遠不變的?<!-- .element: class="fragment" data-fragment-index="2" --> 3. 總結解釋「每次 render 都有其自己版本的 props 與 state」是什麼意思?<!-- .element: class="fragment" data-fragment-index="3" --> 4. 為什麼 component function 內所定義的 event handler 函式可以永遠記得那些存取到的 props 與 state 變數?<!-- .element: class="fragment" data-fragment-index="4" --> Note: 雖然很多面向重複,但鼓勵每個人用自己的方法精準回答~ ---- #### 4-3 觀念檢測 5. Component function 內所定義的 event handler 函式,在每次 render 之間是同一個函式個體嗎?為什麼?<!-- .element: class="fragment" data-fragment-index="5" --> 6. 總結解釋「每次 render 都有其自己版本的 event handler 函式」是什麼意思?<!-- .element: class="fragment" data-fragment-index="6" --> 7. 為什麼 immutable 資料以及 closure 是讓 component 裡的函式也變成單向資料流的一部分的重要關鍵?<!-- .element: class="fragment" data-fragment-index="7" --> Note: 自己寫個答案在這裡 --- # Thank you :) ---
{"title":"CH.4 Component 的生命週期與資料流","contributors":"[{\"id\":\"5573773d-4d0c-41fa-967d-56aefae1ce8a\",\"add\":30260,\"del\":15594}]","description":"2024/5/15 導讀人:LJBL","slideOptions":"{\"transition\":\"slide\",\"spotlight\":{\"enabled\":true}}"}
    248 views