依稀記得去年的時候,有面試官問我:怎麼理解React Hook?當時我尚未使用React開發項目的經驗,固然也沒有對Hook會有什麼深刻的瞭解,因此當時只是應付回答了一下,估計當時的面試官對於個人回答應該是挺失望的(由於此次面試完後就沒有啥消息了);好吧,直到如今,我想我已經有了一個還算不錯的答案,如今記錄下來,方便之後跟面試官吹牛逼。面試
Hook簡直就是天使與魔鬼混血兒,關於Hook的出如今某乎早就爭論不休,大致是有的人是激進派,以爲Hook是顛覆性的,全項目切換到Hook方式開發;有的人則是保守派,複雜的組件仍然使用傳統類組件,簡單的組件則使用hook開發;有的人是反對派,對於Hook徹底持否認態度。
總結本身最近的使用感覺,徹底理解爲何這些人的觀點有如此大的差距;首先Hook的出現有它實實在在的好處(後面再分析),可是得到這種好處也是有代價的:要開發者本身維護依賴列代表顯是一種心智負擔,你在處理複雜的業務的時候,還要當心翼翼檢查useCallback/useEffect的依賴列表,只要寫少一個依賴項你的代碼就有潛在的bug,有時候以爲要麼乾脆不用useCallback還好,大不了就性能低一點,至少不會引入潛在的bug;可是回頭優化性能的時候,useCallback又是徹底繞不過去的,真讓人糾結;固然有人提到使用一些檢查工具能夠提供檢查,不用人工檢查那麼麻煩,可是始終以爲怪怪的。另一個問題就是代碼邏輯的抽取,稍加不注意,很容易把全部邏輯都塞到一個function裏面,致使函數代碼動輒幾百上千行代碼,因此要用好Hook的前提條件是開發者對組件已經有一個瞭然於胸的規劃(對於習慣代碼一把梭的人真的好艱難)。
再說說Hook的天使的一面,從聲明組件方面來講,確實簡單了很多,也不用記那麼多煩人的生命週期函數;而後最核心的地方是,它提供一種新的抽象方式,在傳統的類組件,咱們抽取出一個新的組件目的通常都是:api
因此在第二點,通常處理一個複雜的組件會抽取出一個負責業務的Controller組件,一個負責渲染的Dumb組件;可是咱們很快就會發現這樣以組件爲粒度劃分邏輯,粒度太大了,當咱們另一個組件想重用Controller組件的一小部分邏輯,咱們應該怎麼辦,如果用繼承,會把一些無用的邏輯帶入,並且繼承也還有其餘的問題;因此組合纔是最優的選擇,或許咱們應該再劃分出一個公用的邏輯組件,讓他們組件間能夠自由組合,可是隨着你們熱火朝天的重構,愈來愈多的業務組件被創造出來,而整個組件樹也會慢慢跟着膨脹,這致使每次更新的時候都要diff一棵愈來愈大的組件樹,明顯類組件已經到了一個瓶頸;
而這個時候Hook開始閃亮登上了舞臺,那麼怎麼用它來破解這個問題尼。函數
(舒適提示,下面是我的理解,可能會有錯誤誤導的地方)
先拋開Hook,再來建立一個新的名詞Scope,那麼Scope的定義以下:工具
沒錯Scope好像類的定義同樣,是方法和狀態的集合體,只不過它是基於Hook;爲啥不乾脆直接定義一個普通的類,由於Hook還提供了setState,useEffect等能力,這是普通的類作不到的,或者要花費一些心思才能作到,既然官方提供了這種方式,確定是最好的一種方式。
做爲Scope,它是能夠保持邏輯的獨立而且粒度最小,固然還有可以直接介入到宿主組件的生命週期裏面。
其實若是加上Render Function,它也應該算是一個組件了,只是它的生命週期依賴宿主。
那麼爲啥再也不加上Render Function,由於表現形式是多變的,可是邏輯就不必定了,因此保持邏輯抽象便可。性能
那麼如今組件定義以下:測試
這裏就很好理解了,如今問題的關鍵點就是Scope之間的關係了,若是Scope之間互相獨立,沒啥依賴那就完美,可是這是不可能,不少時候它們之間的關係還特別強,在不少hook示例裏面它們之間的關係都是經過調用useXxx的時候把依賴當作參數傳入,可是很容易致使相互依賴的狀況,例如日常狀況:優化
TableScope須要可以點擊打開對話框,DialogScope須要confirm以後去刷新表格,很明顯這二者建立的時候都要依賴對方,究竟是useDialog先仍是useTable先尼;
個人建議是不要把依賴當作參數傳入,我的解法:spa
function Demo() { //組件公共狀態 const [stateA, setStateA] = useState(''); const [stateB, setStateB] = useState(''); //建立各類Scope const dialog = useDialog({sateA, setStateA}); //Scope只依賴公共的狀態和方法,不容許建立開始互相依賴 const table = useTable({stateB, setStateB}); //經過提供回調,處理Scope之間各類聯繫 dialog.onConfirm(()=> { table.reload(); }); table.onClick(()=> { dialog.open(); }); //組件生命回調 useEffect(()=> { .... }, []); return ... }
Scope之間不容許建立的時候相互依賴,它們之間的聯繫統一在後面一段代碼中處理,這樣有什麼好處尼?這樣的話實現的時候,能夠更加專一Scope自己狀態和業務,也不須要擔憂建立的時候會出現相互依賴狀況。最後組件代碼分段也十分清晰,大致能分紅5段:調試
嗯。。最終的目標就是創建一種規範,讓組件的結構更加清晰,不會出現一片混亂的狀況。code
前一節,更可能是從抽象出發,如何合理利用Hook去組織代碼,而且創建一種規範;後面就來想一想如何解決Hook不少吐槽的問題:依賴列表。
正如以前說的,例如useCallback的使用場景,若是依賴列表少寫一項,就有可能埋下隱藏的bug,並且這種bug極難會被發現,由於依賴列表有可能很長,後面的維護者或者幫你review代碼的人不可能會仔細看依賴列表有沒有缺漏;因此我對useCallback基本上都是抵觸的,寧願不用,也不想埋下bug,到時候調試得滿頭大汗,甚至我也開始懷疑是否要繼續使用Hook來開發組件。
直到有一天看到Vue3的Composition API(沒錯Vue又又又借鑑了一次React),感受Hook趕上Mutable其實也是很美妙。那麼咱們有沒有辦法經過依賴蒐集來解決依賴列表的問題,實現更加簡潔的api,若是能夠咱們就能夠像Vue3那樣使用useCallback和useEffect:
const refA = useRef(0); const refB = useRef(0); const refC = useRef(0); useCallbck(()=> { consoel.log(refA.value + refB.vlue) }); useEffect(()=> { refC.value = refA.value - refB.value; }) ...
代碼上基本消滅了依賴列表,只是在變量使用上就稍微那麻煩了一丁點,可是依賴列表不用再維護,心智負擔瞬間降低了許多。
問題怎麼去實現這種代碼效果?
下面是一個簡單的實現:
const ACTIVE_EFFECT = null; function useReactiveState(initial) { const [state, setState] = useState(initial); const ref = useRef(state); const effects = useRef(new Set()); return useMemo(()=> { const memoState = { get value() { if(ACTIVE_EFFECT) { ACTIVE_EFFECT.deps.add(memoState); effects.current.add(ACTIVE_EFFECT); } return ref.current; }, set value(newValue) { ref.current = newValue; setState(newValue); effects.current.forEach((effect)=> { effect.update(); }) }, removeEffect(effect) { effects.current.remove(effect) } } return memoState; }, []) } function useReactiveEffect(call) { const [flag, setFlag] = useState(0); const effect = useRef({ deps: new Set(), update: ()=> { setFlag((flag)=> flag + 1); } }); useEffect(()=> { const prevEffect = ACTIVE_EFFECT; effect.current.deps.forEach((dep)=> { dep.removeEffect(effect); }); effect.current.deps = []; const result = call(); ACTIVE_EFFECT = prevEffect; return reuslt; }, [flag]) }
useCallback的實現同理,雖然沒有怎麼深刻測試,大致功能仍是可以實現的。
好吧,對React Hook的思考暫時到此爲止,等之後再有新的感悟再繼續寫React Hook的相關的文章吧,最後仍是那句,若有錯漏,請你們可以及時指出,謝謝你們的捧場。