2020/11/03

[React]Functional Component 製作上下層共用元件方法

 

源自:【學習筆記】React 边做边学(从零开始,包含Hooks)【25, 26. 彈出組件】一節。

課程中使用 Class 元件設計,CodeSandbox Demo1

看著 Panel 開開關關,心想使用 Functional 元件重新設計應該不是件難事,沒想到在做完之後跳出錯誤訊息:


Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: null.

為什麼會得到這樣的錯誤訊息?【Class Instance】

訊息內大致上是說:在應該有值、元件的位置卻得到 null。原因是 ES6 在建立 Class 時會帶有 Instance,典型語法就是【this】應用,this 在 Functional 元件是不受支援的。

題外話,Classes 在 MDN 中對物件導向迷思的解釋:

ECMAScript 6 中引入了類別 (class) 作為 JavaScript 現有原型程式(prototype-based)繼承的語法糖。類別語法並不是要引入新的物件導向繼承模型到 JavaScript 中,而是提供一個更簡潔的語法來建立物件和處理繼承。
實在是看不出 React 中用 Classes 來寫有簡潔的趨勢,不知你是否也和我有一樣想法?

使用 useRef 卻不得其解

useRef:回傳純 JavaScript Object,等同對 HTML DOM 直接操作,特色是不會觸發 Render,有需要時要用 useCallback,應用在 DOM 操作方法上,如 focus 等。

搭配 forwardRef 改寫老半天仍然無解,子元件也無法使用,難道這是 Functional 元件的限制?

一恍眼五天過去,還卡在原處動彈不得,只好向萬能的社群祈禱。

奇蹟!關鍵範例降臨

在社群發問後在很短的時間就獲得好多高手的回應,超感動!其中打通觀念的是這篇,獲得三項關鍵寶物:

  1. 思路
  2. createPortal 關鍵字
  3. 範例

原因在我想仿照原始課程使用 Class Instance,那是 Functional 元件所沒有的東西,正確的作法應該拆分 Context 和 createPortal 並行。

重構 (Code Refactoring)

思路有了,代表的是舊思維的改變,舊思維改變,架構也要跟著改變,很顯然的,重寫 Panel 元件是必然的過程,

課程裡關於彈出組件的設計架構

範例中給出 3 點思路:

  1. 使用 Context 由上而下傳遞控制 Panel 元件的變數。
  2. App 載入時利用 createPortal 渲染 Panel 到 DOM 中。
  3. 用 export default 打包 React.forwardRef 渲染到 ReactDOM 中。

Functional 元件需依賴 Context 的設計

 

Class 元件在渲染到 DOM 時就能全域存在,所以控制變數可以在 Class 裡就設定,然而考慮到 Functional 元件沒有 Instance,如果要控制 Panel,就必須將控制變數放在 Context 內讓其它元件操作 Context 而非 Panel,進而觸發渲染機制。

第一次測試

按此思路重新設計後,終於能看到 Panel 的出現,但動畫卻跑不出來,原來的卻可以,這究竟是怎麼一回事呢?

網頁載入後沒看到 Panel 元件

按下 Add 按鈕後 Panel 突然出現

從上圖可以看出,Panel 元件的建立時間和動畫元件是同時出現,這也是導致動畫效果沒動作的原因。然後關閉 Panel 元件時又從 DOM 裡消失,當然也就看不到動畫。

小提醒:createPortal 第二個參數為 DOM 容器,也可以是 document.body。

主因:程式利用 active Context 判斷是否要繪製 Panel 所產生的效果。

結論:所以只要能在 Panel 建立前把動畫元件擺好來讓動畫功能啟用。


第二次測試

這次把 CSSTransition 掛載在 export default Panel 中,因為不再依賴 active Context 而是直接渲染 CSSTransition 和其下的 Panel,此時在網頁載入後,DOM 就會主動掛載 Panel 元件:

實際操作效果和原課程範例相同,真是可喜可賀!

結語

原以為 Functional 元件開發可以很簡單的將 Class 元件轉換套用,但這兩者本質上的差別導致在功能上的實作也不完全也不應相同,在設計 Functional 元件時,善用 Hook 這件事不可少,Ref 在 Functional 元件開發上和 Class 元件相比多了好多變化,還好有萬能的 ReactJS 社群,這問題才得以解答。

 

最後,僅以此篇文章表示回敬,分享給大家!

 

謝謝各位收看,我們下次見。ByeBye!

See also

沒有留言:

張貼留言