源自:【學習筆記】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 元件的限制?
一恍眼五天過去,還卡在原處動彈不得,只好向萬能的社群祈禱。
奇蹟!關鍵範例降臨
在社群發問後在很短的時間就獲得好多高手的回應,超感動!其中打通觀念的是這篇,獲得三項關鍵寶物:
- 思路
- createPortal 關鍵字
- 範例
原因在我想仿照原始課程使用 Class Instance,那是 Functional 元件所沒有的東西,正確的作法應該拆分 Context 和 createPortal 並行。
重構 (Code Refactoring)
思路有了,代表的是舊思維的改變,舊思維改變,架構也要跟著改變,很顯然的,重寫 Panel 元件是必然的過程,
課程裡關於彈出組件的設計架構 |
範例中給出 3 點思路:
- 使用 Context 由上而下傳遞控制 Panel 元件的變數。
- App 載入時利用 createPortal 渲染 Panel 到 DOM 中。
- 用 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!
沒有留言:
張貼留言