第一個 React 項目做完了,談談自己對 hooks 的思考

第一個 React 項目做完了,談談自己對 hooks 的思考 1


轉自: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 項目做完了,談談自己對 hooks 的思考

這裡的不同顏色其實就是不同邏輯,這圖可以分成數據,事件方法,生命周期,模板四塊內容。

我們可以看到,一個邏輯在類組件里其實是分散的,拿 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 的思考

每塊邏輯都是一個整體,非常清晰明了。而且你可以把這些邏輯都抽離出去,只是在這個組件當中引用即可。

邏輯復用就不用說了,現在誰家 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的方式組織邏輯代碼。大勢所趨,你也得趕緊跟上。

謝謝,購買

感謝您的閱讀,如果覺得本文對你有所幫助的話,請動手點個贊支持一下,謝謝。