【React】668- React Hooks的優(yōu)缺點

下面我談一下我認為的
react hooks的優(yōu)缺點,優(yōu)缺點通過和傳統(tǒng)的React.Component進行對比得出。
優(yōu)點
一、更容易復用代碼
這點應該是react hooks最大的優(yōu)點,它通過自定義hooks來復用狀態(tài),從而解決了類組件有些時候難以復用邏輯的問題。hooks是怎么解決這個復用的問題呢,具體如下:
每調(diào)用useHook一次都會生成一份獨立的狀態(tài),這個沒有什么黑魔法,函數(shù)每次調(diào)用都會開辟一份獨立的內(nèi)存空間。雖然狀態(tài)(from useState)和副作用(useEffect)的存在依賴于組件,但它們可以在組件外部進行定義。這點是class component做不到的,你無法在外部聲明state和副作用(如componentDidMount)。上面這兩點,高階組件和renderProps也同樣能做到。但hooks實現(xiàn)起來的代碼量更少,以及更直觀(代碼可讀性)。
舉個例子,我們經(jīng)常使用的antd-table,我們經(jīng)常需要寫一些狀態(tài):pagination={current:xxx,pageSize:xxx,total:xxx}。但許多場景只是簡單的表格,我們希望封裝一個高階組件,自帶這些狀態(tài),并可以自動調(diào)用server去獲取remote data。
用高階組件來實現(xiàn)這樣它:
import?{?Table?}?from?'antd'
import?server?from?'./api'
@useTable(server)
class?App?extends?Component{
??render(){
????//?useTable和tableProps的代碼是分離的,但高階組件一多,代碼會變得較難閱讀,
???//?你難以區(qū)分這個props是來自哪個高階組件,或者還是來自業(yè)務的父組件。
????const?{?tableProps?}?=?this.props;
????return?(
??????<Table?
????????columns={[...]}
??????//?tableProps包含pagination,?onChange,?dataSource等屬性
????????{...tableProps}
??????/>
????)
??}
}
用hooks來實現(xiàn)的話,會是:
import?{?Table?}?from?'antd'
import?server?from?'./api'
function?App?{
????const?{?tableProps?}?=?useTable();
????return?(
??????<Table?
????????columns={[...]}
??????//?tableProps包含pagination,?onChange,?dataSource等屬性
????????{...tableProps}
??????/>
????)
}
/*
相對比高階組件“祖父=>父=>子”的層層嵌套,
hooks是這樣的:??
const?{?brother1?}?=?usehook1;?
const?{?brother2}?=?usehook2;
*/
實現(xiàn)有以下幾點優(yōu)勢:
把"
useTable"和輸出的值寫到一起,結(jié)構(gòu)更清晰,更容易閱讀和維護。對比高階組件,代碼量更少。
二、清爽的代碼風格
函數(shù)式編程風格,函數(shù)式組件、狀態(tài)保存在運行環(huán)境、每個功能都包裹在函數(shù)中,整體風格更清爽,更優(yōu)雅。另外,對比類組件,函數(shù)組件里面的unused狀態(tài)和unused-method更容易被發(fā)現(xiàn)。
三、代碼量更少
向props或狀態(tài)取值更加方便,函數(shù)組件的取值都從當前作用域直接獲取變量,而類組件需要先訪問實例引用this,再訪問其屬性或者方法,多了一步。更改狀態(tài)也變得更加簡單, this.setState({ count:xxx })變成 setCount(xxx)。因為減少了很多模板代碼,特別是小組件寫起來更加省事,人們更愿意去拆分組件。而組件粒度越細,則被復用的可能性越大。所以,hooks也在不知不覺中改變?nèi)藗兊拈_發(fā)習慣,提高項目的組件復用率。
缺點
一、響應式的useEffect
寫函數(shù)組件時,你不得不改變一些寫法習慣。你必須清楚代碼中useEffect和useCallback等api的第二個參數(shù)“依賴項數(shù)組”的改變時機,并且掌握上下文的useEffect的觸發(fā)時機。當邏輯較復雜的時候,useEffect觸發(fā)的次數(shù),可能會被你預想的多。對比componentDidmount和componentDidUpdate,useEffect帶來的心智負擔更大。
二、狀態(tài)不同步
這絕對是最大的缺點。函數(shù)的運行是獨立的,每個函數(shù)都有一份獨立的作用域。函數(shù)的變量是保存在運行時的作用域里面,當我們有異步操作的時候,經(jīng)常會碰到異步回調(diào)的變量引用是之前的,也就是舊的(這里也可以理解成閉包)。
import?React,?{?useState?}?from?"react";
const?Counter?=?()?=>?{
??const?[counter,?setCounter]?=?useState(0);
??const?onAlertButtonClick?=?()?=>?{
????setTimeout(()?=>?{
??????alert("Value:?"?+?counter);
????},?3000);
??};
??return?(
????<div>
??????<p>You?clicked?{counter}?times.p>
??????<button?onClick={()?=>?setCounter(counter?+?1)}>Click?mebutton>
??????<button?onClick={onAlertButtonClick}>
????????Show?me?the?value?in?3?seconds
??????button>
????div>
??);
};
export?default?Counter;
當你點擊Show me the value in 3 seconds的后,緊接著點擊Click me使得counter的值從0變成1。三秒后,定時器觸發(fā),但alert出來的是0(舊值),但我們希望的結(jié)果是當前的狀態(tài)1。
這個問題在class component不會出現(xiàn),因為class component的屬性和方法都存放在一個instance上,調(diào)用方式是:this.state.xxx和this.method()。因為每次都是從一個不變的instance上進行取值,所以不存在引用是舊的問題。
其實解決這個hooks的問題也可以參照類的instance。用useRef返回的immutable RefObject(current屬性是可變的)來保存state,然后取值方式從counter變成了:counterRef.current。如下:
import?React,?{?useState,?useRef,?useEffect?}?from?"react";
const?Counter?=?()?=>?{
??const?[counter,?setCounter]?=?useState(0);
??const?counterRef?=?useRef(counter);
??const?onAlertButtonClick?=?()?=>?{
????setTimeout(()?=>?{
??????alert("Value:?"?+?counterRef.current);
????},?3000);
??};
??useEffect(()?=>?{
????counterRef.current?=?counter;
??});
??return?(
????<div>
??????<p>You?clicked?{counter}?times.p>
??????<button?onClick={()?=>?setCounter(counter?+?1)}>Click?mebutton>
??????<button?onClick={onAlertButtonClick}>
????????Show?me?the?value?in?3?seconds
??????button>
????div>
??);
};
export?default?Counter;
結(jié)果如我們所期待,alert的是當前的值1。
我們可以把這個過程封裝成一個custom hook,如下:
import?{?useEffect,?useRef,?useState?}?from?"react";
const?useRefState?=?(initialValue:?T):
?[T,?React.MutableRefObject,?
???React.Dispatch>]?=>?{
??const?[state,?setState]?=?useState(initialValue);
??const?stateRef?=?useRef(state);
??useEffect(()?=>?{
????stateRef.current?=?state;
??},?[state]);
??return?[state,?stateRef,?setState];
};
export?default?useRefState;
盡管這個問題被巧妙地解決了,但它不優(yōu)雅、hack味道濃,且丟失了函數(shù)編程風格。
怎么避免react hooks的常見問題
不要在 useEffect里面寫太多的依賴項,劃分這些依賴項成多個單一功能的useEffect。其實這點是遵循了軟件設計的“單一職責模式”。如果你碰到狀態(tài)不同步的問題,可以考慮下手動傳遞參數(shù)到函數(shù)。如:
//?showCount的count來自父級作用域?
const?[count,setCount]?=?useState(xxx);?
function?showCount(){?
??console.log(count)?
}?
//?showCount的count來自參數(shù)?
const?[count,setCount]?=?useState(xxx);?
function?showCount(c){?
??console.log(c)?
}
但這個也只能解決一部分問題,很多時候你不得不使用上述的useRef方案。
重視
eslint-plugin-react-hooks插件的警告。復雜業(yè)務的時候,使用Component代替
hooks。
感想
目前,我通常更偏向于用hooks來寫組件,但在復雜業(yè)務中,我會更傾向于用class Component或者兩者結(jié)合的方式。hooks會是未來的主流組件編寫方式,但目前來說它還不成熟。

回復“加群”與大佬們一起交流學習~
點擊“閱讀原文”查看70+篇原創(chuàng)文章

