轉自:zouwowo
前言
本文不會有 React 具體應用的內容,只是一些對於 hooks 跟之前的類組件的比較,對於 hooks 本身的思考。
筆者在今年的8月份入職現在的公司,從原來的 vue 轉為 React 。因為公司還存在一些比較老的項目,所以前期並沒有完全投入到 React 的項目開發當中。從10月份開始,參與了一個公司層面的新項目從0到1的構建過程,這也是我第一個 React 項目。我們是全面擁抱 hooks 的,這個項目的開發過程中,我也寫了很多自定義的 hooks 方法,封裝了好幾個通用的功能組件,也算是熟練了 React 的具體應用。
項目開發初期,我一直有個疑惑,我在入職之前學習 React 的時候,其實很多的教程都是使用的類組件的方法,很少看到 hooks 的相關教程。自己邊學邊寫demo的時候使用的也都是類組件的寫法,但是現在越來越多的團隊開始全面擁抱 hooks , hooks 到底有什麼優點?我將在下面提出一些自己的思考和想法。
類組件和函數組件區別
這裡要注意, 類組件 和 hooks ,這兩個東西其實並不是一個概念。 hooks 只是一個工具集,用來增強函數組件的功能。真正要對比的應該是 類組件 和 函數組件 。
我們先來看看 類組件 和 函數組件 的區別。
代碼寫法上的區別
這是最直觀的區別,代碼就長的不一樣嘛。我隨便列幾個很常見的例子,這些特性在 函數組件 里都沒有。
類組件,顧名思義,它就是一個類,需要繼承 Class 。
類組件可以直接定義state
類組件有生命周期方法
類組件可以使用this獲取到組件實例
心智模型上的區別
這是兩個組件之間最大的區別,用 https://overreacted.io/zh-hans/how-are-function-components-different-from-classes/ 中的話來說。
函數式組件捕獲了渲染所用的值。
我們引用文章里的一個例子
功能個人資料頁( 道具) {
constshowMessage = => {
警報( ‘成功關注 ‘+ props.user);
};
consthandleClick = => {
setTimeout(showMessage, 3000);
};
返回(
< 鈕扣onClick= {handleClick}> 關注 鈕扣>
);
}
複製代碼
類個人資料頁延伸反應。 零件{
showMessage = => {
警報( ‘成功關注 ‘+ 這個.props.user);
};
handleClick = => {
setTimeout( 這個.showMessage, 3000);
};
渲染{
返回< 鈕扣onClick= {this.handleClick}> 關注 鈕扣> ;
}
}
複製代碼
我們用 類組件 以及 函數組件 實現了同一個邏輯。這兩個組件都會接收一個 props.user 的屬性,我們點擊按鈕,在3秒之後,會 alert 一條成功關注的信息。
有過經驗的同學肯定能輕鬆答出來。
// 函數組件會列印
‘成功關注 帥wowo’
// 類組件會列印
‘成功關注 丑wowo’
複製代碼
為什麼會這樣呢? (這裡註明一下,這個例子跟 React 框架無關,只是 js 的基本特性,你在任何用 js 編寫的代碼中都可以復現)
在 React 的類組件中, props 雖然是不變的, 但是 this 永遠是可變 。當有非同步的事件觸發,它獲取到的 props 或者 state 永遠都是最新的。當然我們也有辦法去解決。
比如我們可以重新定義一個數據來保存`props
handleClick = => {
const{user} = 這個。道具;
setTimeout( => 這個.showMessage(用戶), 3000);
};
複製代碼
但這種方式太過繁瑣,各種定義的數據非常不夠優雅。
或者把事件都寫到渲染函數 render 中
類個人資料頁延伸反應。 零件{
渲染{
const道具= 這個。道具;
constshowMessage = => {
警報( ‘成功關注 ‘+ props.user);
};
consthandleClick = => {
setTimeout(showMessage, 3000);
};
返回< 鈕扣onClick= {handleClick}> 關注 鈕扣> ;
}
}
複製代碼
這個方法其實函數組件的原理, props 變化之後,組件雖然重新渲染了,但是老的 props 通過閉包保存了下來,然後被列印出來。
寫了這麼多,只是為了論證那句話, 函數式組件捕獲了渲染所用的值。
為什麼我們要使用函數組件+hooks
這一點很多人可能覺得沒必要,覺得官方出的東西,跟著用就好,寫著也挺順手的,還管啥為什麼呢?
關於這一點,我覺得最重要的其實就是學習大佬們的思維,為什麼要做出一個 hooks 來?肯定是為了解決一些原來的開發過程中的問題。 React 團隊的設計層面的思路,能夠在一定程度上代表當前業界在框架設計領域裡最佳實踐。
接下來我會列出幾個我認為的 類組件 的幾個痛點。(好和壞都是比較出來的,這些痛點只是相比於 函數組件+hooks而言 ,技術在不斷發展,技術的迭代都是正常的趨勢)
1.函數組件的寫法更輕量,更加靈活
在函數組件中,我們不需要去繼承一個 class 對象,不需要記憶那些生命周期,不需要固定的把數據定義在 state 中。函數作為js中的一等公民,函數式編程方式可以讓我們可以更加靈活的去組織代碼。
2.類組件存在自身的缺陷
一個其實就是上面一節寫到,如果我們需要一個只跟著視圖走的數據,我們不能直接使用 props 或者 state 。
還有一個是最常見的,在 React 中,如果我們定義一個方法,我們必須使用 bind 或者箭頭函數去約束這個方法的 this 的作用域。
這兩個問題雖然我們都能解決,但是本質上,都是我們通過代碼實踐的方式去解決 類組件 自身的缺陷。
但是在函數組件中,我們不會有這種問題,通過閉包的方式, 在一次渲染中,組件的 props 和 state 是保持不變的 ,而且傳遞的方法本身就是已經被約束作用域了。
3.邏輯是分散的,而且難以復用
這個痛點其實跟 vue2 是一模一樣的, React 的類組件和 vue2 的開發模式都是類似。
我拿一張尤大來談論 vue2 和 vue3 區別時候的一張圖來舉例
這裡的不同顏色其實就是不同邏輯,這圖可以分成數據,事件方法,生命周期,模板四塊內容。
我們可以看到,一個邏輯在類組件里其實是分散的,拿 React 來說,數據需要定義在 state 里,然後需要編寫相關的事件方法,再在生命周期里進行邏輯的初始化,組件更新時候的處理,最後在模板里寫 jsx 。
我們如果是去維護這個代碼,會很痛苦,為了查看一個邏輯,我們要上下翻,找出各自的數據,方法,生命周期和模板。
而且這種方式的代碼, 很難被複用,抽離重複邏輯我們經歷過 mixin , HOC & render-props 。這兩種方式都有一個最大的問題,那就是 props 的來源不夠清晰。 mixin 就不說了,都是淚,只能一個個去找。 HOC 嵌套層級一多,也會很難確定來源,而且 HOC 的學習成本也相對比較高,對於新手不太友好。 (這塊是按我 vue2 開發用到的 mixin 和 HOC 的經驗寫的,我作為 React 的新手,並沒有經歷過 React 的 mixin 和 HOC 抽離邏輯的時代,所以如果寫的有問題,請各位大佬指出。最後感嘆一句, React 用 HOC 可太方便了!)
但是如果我們使用 hooks ,我還是用一張 vue3 中組合式API的圖來說明,跟使用 hooks 的結果是類似的。
每塊邏輯都是一個整體,非常清晰明了。而且你可以把這些邏輯都抽離出去,只是在這個組件當中引用即可。
邏輯復用就不用說了,現在誰家 React 項目里沒有好多自定義的 hooks 啊。
4.hooks更貼合React的基本理念
React的設計理念 里第一條
React的核心理念之一,相同的參數輸入應該產生相同的輸出。簡單說,它應當是一個簡單的純函數。
我們可以拿之前說到的心智模型上的區別中的例子來說明,我們傳入一個 props 參數 { user: ‘帥wowo’ } ,我們希望關於這個參數的所有事件都強依賴於這條數據。它不應該像 類組件 一樣,傳入的參數和我們得到的輸出不一致。
但是函數組件可以做到,重複引用一下上面的一句話, 在一次渲染中,組件的 props 和 state 是保持不變的 。
hooks的不足
談論一個技術,我們不能太過於片面,一定要保持辯證思維,理性分析。上面說了很多 hooks 的優點,但是它依然存在著不足。
1.比較大的心智負擔
同樣是因為上面那句話, 在一次渲染中,組件的 props 和 state 是保持不變的 。這個特性導致的 閉包陷阱 是我們現在開發中最常見的一個問題。我們需要時刻注意是否已經給 hooks 添加了必要的依賴項。在封裝一些功能相對複雜的組件時, useEffect 的重複渲染問題處理有時候會非常棘手,而且不易調試。
這個特性在對 函數組件 進行性能優化時也是會帶來很大的麻煩,因為每次 props 和 state 數據變化,都會導致 函數組件 中所有內容的重新渲染。我們需要通過 memo , useMemo , useCallback 這些方法手動去減少組件的 render 。當一個組件結構比較複雜,嵌套較多時,依賴項問題的處理也很讓人頭疼。具體性能優化的內容,可以看我之前的文章。
這些點給開發者帶來的學習 hooks 的成本相比較 類組件 來說會更大。
2.需要更嚴格的開發規範
剛才我們說了,函數式編程能夠給我們帶來很大的靈活度,這個靈活度在開發當中是一個雙刃劍。它能幫助我們更好的去解決一些複雜的需求,但是它在一個多人協作開發的項目中並不是一個好的事情。
一些大型的項目,最重要的一定是編程的規範, eslint 可以限制語法規範,但是限制不了我們實現需求的規範,一人一個風格的代碼對於一個項目來說就是個災難,對於後續的維護,公共方法的抽離來說將帶來很大的麻煩。
所以,需要開發者制定一個具體的開發規範,並在開發的時候嚴格遵守。
3.hooks並不能完全代替類組件
雖然我們有了很多 hooks 方法,用來增強 函數組件 的功能。比如 useState 可以讓 函數組件 維護自己的數據。有 useEffect 可以在一定程度上彌補 函數組件 沒有生命周期的缺點。
注意: useEffect 並不是用來代替生命周期,它只是提供了一個類似生命周期的方法。兩者其實本質上沒有可比性。
想了解更多,可以看Dan的這篇博客, https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/ ,它可以幫你更好的理解 hooks 中的 useEffect 。
但是,我們還是不能拿 hooks 來完全代替類組件。 (主要是部分生命周期還無法被代替)
比如 componentDidCatch 這個獲取組件錯誤的生命周期,在大部分的項目中,肯定會看到它的身影。還有其他的一些生命周期,可能你在一些特殊的場景下還是需要用到。
所以,在一段時間之內, hooks 還是會跟 類組件 共存。
總結
越來越多的公司和團隊早已經全面擁抱 hooks ,現在再談論使用 hooks 的收益跟付出相比是否值得已經毫無意義,大的潮流已經給你指明了方向,今年新發布的正式版 vue3 也是借鑒了 hooks 的方式,實現了讓開發者使用組合式api的方式組織邏輯代碼。大勢所趨,你也得趕緊跟上。
謝謝,購買
感謝您的閱讀,如果覺得本文對你有所幫助的話,請動手點個贊支持一下,謝謝。