溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何優雅的使用react hooks來進行狀態管理

發布時間:2020-06-05 22:50:40 來源:網絡 閱讀:1416 作者:可樂程序員 欄目:大數據

在使用react和redux的過程中,一直有一個問題,哪些狀態需要放在redux中,狀態需要保存在組件內的local state中,此外不合理的使用redux可能會帶來狀態管理混亂的問題,此外對于local state局部狀態而言,react hooks提供了一個比class中的setState更好的一個替代方案。本文主要從狀態管理出發,講講如何優雅的使用hooks來進行狀態管理。

  • 如何使用redux

  • react hooks管理local state

  • react hooks如何解決組件間的通信

原文在我的博客中:github.com/fortheallli… 歡迎訂閱


一、如何使用redux

首先要明確為什么要使用redux,這一點很重要,如果不知道為什么使用redux,那么在開發的過程中肯定不能合理的使用redux.首先來看redux的本質:

redux做為一款狀態管理工具,主要是為了解決組件間通信的問題。

既然是組件間的通信問題,那么顯然將所有頁面的狀態都放入redux中,是不合理的,復雜度也很高。

(1)全量使用redux

筆者在早期也犯了這個問題,在應用中,不管什么狀態,按頁面級路由拆分,全部放在redux中,頁面任何狀態的更改,通過react-redux的mapState和mapDispatch來實現。

如何優雅的使用react hooks來進行狀態管理


redux中的狀態從狀態更新到反饋到視圖,是一個過程鏈太長,從dispatch一個action出發,然后走reducer等邏輯,一個完整的鏈路包含:

創建action,創建redux中間件,創建相應type的reducer函數,創建mapState和mapDispatch等。

如果將所有狀態都保存在redux中,那么每一個狀態必須走這幾步流程,及其繁瑣,毫無疑問增加了代碼量

(2)減少局部狀態和redux狀態的不合理混用

全量使用redux的復雜度很高,我們當然考慮將一部分狀態放在redux中,一部分狀態放在local state中,但是這種情況下,很容易產生一個問題,就是如果local State跟redux中的state存在狀態依賴。

舉例來說,在redux中的狀態中有10個學生

?//redux
?
?students?=?[{name:"小白",score:70},{name:"小紅",score:50}....]
復制代碼

在local state中我們保存了分數在60分以上的學生

?//?local?state
?
?state?=?[{name:"小白",score:70}]
復制代碼

如果redux中的學生改變了,我們需要從redux中動態的獲取students信息,然后改變局部的state.結合react-redux,我們需要在容器組件中使用componentWillReceivedProps或者getDerivedStateFromProps這個聲明周期,來根據props改變局部的local state.

componentWillReceivedProps這里不討論,為了更高的安全性,在react中用靜態的getDerivedStateFromProps代替了componentWillReceivedProps這里不討論,而getDerivedStateFromProps這個聲明周期函數在props和state變化的時候都會去執行,因此如果我們需要僅僅在props的改變而改變局部的local state,在這個聲明周期中會存在著很復雜的判斷邏輯。

redux中的狀態和local state中的狀態相關聯的越多,getDerivedStateFromProps這個聲明周期函數就越復雜

給我們的啟示就是盡可能的減少getDerivedStateFromProps的使用,如果實在是redux和local state有關聯性,用id會比直接用對象或者數組好,比如上述的例子,我們可以將學生分組,并給一個組號,每次在redux中的學生信息發生改變的時候會改變相應的組號。 這樣在getDerivedStateFromProps只需要判斷組號是否改變即可:

?class?Container?extends?React.Component{
?state?=?{
?group_id:number
?}
?
?static?getDerivedStateFromProps(props,state){?if(props.group_id!==state.group_id){
?
?...?更新及格的學生
?}else{?return?null
?}
?}
?}
復制代碼

這里推薦https://github.com/paularmstrong/normalizr,如果實在redux和local state關聯性強,可以先將數據范式化,范式化后的數據類似于給一個復雜結構一個id,這樣子會簡化getDerivedStateFromProps的邏輯.

(3)本節小結

如何使用redux,必須從redux的本質出發,redux的本質是為了解決組件間的通信問題,因此組件內部獨有的狀態不應該放在redux中,此外如果redux結合class類組件使用,可以將數據范式化,簡化復雜的判斷邏輯。

二、react hooks管理local state

前面將了應該如何使用redux,那么如何維護local state呢,React16.8中正式增加了hooks。通過hooks管理local state,簡單易用可擴展。

在hooks中的局部狀態常見的有3種,分別是useState、useRef和useReducer

(1) useState

useState是hooks中最常見的局部狀態,比如:

?const?[hide,?setHide]?=?React.useState(false);?const?[name,?setName]?=?React.useState('BI');
復制代碼

理解useState必須明確,在react hooks中:

每一次渲染都有它自己的 Props and State

一個經典的例子就是:

?function?Counter()?{?const?[count,?setCount]?=?useState(0);?
?function?handleAlertClick()?{
?setTimeout(()?=>?{
?alert('You?clicked?on:?'?+?count);
?},?3000);
?}?
?return?(?<div>
?<p>You?clicked?{count}?times</p>
?<button?onClick={()?=>?setCount(count?+?1)}>
?Click?me
?</button>
?<button?onClick={handleAlertClick}>
?Show?alert
?</button>
?</div>
?);
?}
復制代碼

如果我按照下面的步驟去操作:

  • 點擊增加counter到3

  • 點擊一下 “Show alert”

  • 點擊增加 counter到5并且在定時器回調觸發前完成

猜猜看會alert出什么?

如何優雅的使用react hooks來進行狀態管理


結果是彈出了3,alert會“捕獲”我點擊按鈕時候的狀態,也就是說每一次的渲染都會有獨立的props和state.

(2) useRef

在react hooks中,我們知道了每一次的渲染都會有獨立的props和state,那么如果我們需要跟類組件一樣,每次都能拿到最新的渲染值時,應該怎么做呢?此時我們可以用useRef

useRef提供了一個Mutable可變的數據

我們來修改上述的例子,來是的alert為5:

?function?Counter()?{?const?[count,?setCount]?=?useState(0)?const?late?=?useRef(0)?function?handleAlertClick()?{
?setTimeout(()?=>?{
?alert('You?clicked?on:?'?+?late.current)
?},?3000)
?}
?useEffect(()?=>?{
?late.current?=?count
?})?return?(?<div>
?<p>You?clicked?{count}?times</p>
?<button?onClick={()?=>?setCount(count?+?1)}>Click?me</button>
?<button?onClick={handleAlertClick}>Show?alert</button>
?</div>
?)
?}
復制代碼

如此修改以后就不是alert3 而是彈出5

(3) useReducer

react hooks中也提供了useReducer來管理局部狀態.

當你想更新一個狀態,并且這個狀態更新依賴于另一個狀態的值時,你可能需要用useReducer去替換它們。

同樣的用例子來說明:

?function?Counter()?{
?const?[state,?dispatch]?=?useReducer(reducer,?initialState);
?const?{?count,?step?}?=?state;
?
?useEffect(()?=>?{
?const?id?=?setInterval(()?=>?{
?dispatch({?type:?'tick'?});
?},?1000);
?return?()?=>?clearInterval(id);
?},?[dispatch]);
?
?return?(
?<>
?<h2>{count}</h2>
?<input?value={step}?onChange={e?=>?{
?dispatch({?type:?'step',?step:?Number(e.target.value)
?});
?}}?/>
?</>
?);
?}
?
?const?initialState?=?{?count:?0,?step:?1,
?};
?
?function?reducer(state,?action)?{
?const?{?count,?step?}?=?state;?if?(action.type?===?'tick')?{
?return?{?count:?count?+?step,?step?};
?}?else?if?(action.type?===?'step')?{
?return?{?count,?step:?action.step?};
?}?else?{?throw?new?Error();
?}
?}
復制代碼

解釋上面的結果主要來看useEffect部分:

?useEffect(()?=>?{?const?id?=?setInterval(()?=>?{
?dispatch({?type:?'tick'?});
?},?1000);?return?()?=>?clearInterval(id);
?},?[dispatch]);
復制代碼

在state中的count依賴與step,但是使用了useReducer后,我們不需要在useEffect的依賴變動數組中使用step,轉而用dispatch來替代,這樣的好處就是減少不必要的渲染行為.

此外:局部狀態不推薦使用 useReducer ,會導致函數內部狀態過于復雜,難以閱讀。 useReducer 建議在多組件間通信時,結合 useContext 一起使用。

三、react hooks如何解決組件間的通信

react hooks中的局部狀態管理相比于類組件而言更加簡介,那么如果我們組件采用react hooks,那么如何解決組件間的通信問題。

###(1) UseContext

最基礎的想法可能就是通過useContext來解決組件間的通信問題。

比如:

function?useCounter()?{?let?[count,?setCount]?=?useState(0)?let?decrement?=?()?=>?setCount(count?-?1)?let?increment?=?()?=>?setCount(count?+?1)?return?{?count,?decrement,?increment?}
}let?Counter?=?createContext(null)function?CounterDisplay()?{?let?counter?=?useContext(Counter)?return?(?<div>
?<button?onClick={counter.decrement}>-</button>
?<p>You?clicked?{counter.count}?times</p>
?<button?onClick={counter.increment}>+</button>
?</div>
?)
}function?App()?{?let?counter?=?useCounter()?return?(?<Counter.Provider?value={counter}>
?<CounterDisplay?/>
?<CounterDisplay?/>
?</Counter.Provider>
?)
}
復制代碼

在這個例子中通過createContext和useContext,可以在App的子組件CounterDisplay中使用context,從而實現一定意義上的組件通信。

此外,在useContext的基礎上,為了其整體性,業界也有幾個比較簡單的封裝:

github.com/jamiebuilds… github.com/diegohaz/co…

但是其本質都沒有解決一個問題:

如果context太多,那么如何維護這些context

也就是說在大量組件通信的場景下,用context進行組件通信代碼的可讀性很差。這個類組件的場景一致,context不是一個新的東西,雖然用了useContext減少了context的使用復雜度。

###(2) Redux結合hooks來實現組件間的通信

hooks組件間的通信,同樣可以使用redux來實現。也就是說:

在React hooks中,redux也有其存在的意義

在hooks中存在一個問題,因為不存在類似于react-redux中connect這個高階組件,來傳遞mapState和mapDispatch, 解決的方式是通過redux-react-hook或者react-redux的7.1 hooks版本來使用。

  • redux-react-hook

在redux-react-hook中提供了StoreContext、useDispatch和useMappedState來操作redux中的store,比如定義mapState和mapDispatch的方式為:

import?{StoreContext}?from?'redux-react-hook';
ReactDOM.render(?<StoreContext.Provider?value={store}>
?<App?/>
?</StoreContext.Provider>,
?document.getElementById('root'),
);
import?{useDispatch,?useMappedState}?from?'redux-react-hook';
export?function?DeleteButton({index})?{
?//?Declare?your?memoized?mapState?function
?const?mapState?=?useCallback(?state?=>?({
?canDelete:?state.todos[index].canDelete,
?name:?state.todos[index].name,
?}),
?[index],
?);
?//?Get?data?from?and?subscribe?to?the?store
?const?{canDelete,?name}?=?useMappedState(mapState);
?//?Create?actions
?const?dispatch?=?useDispatch();
?const?deleteTodo?=?useCallback(
?()?=>
?dispatch({
?type:?'delete?todo',
?index,
?}),
?[index],
?);
?return?(?<button?disabled={!canDelete}?onClick={deleteTodo}>
?Delete?{name}
?</button>
?);
}
復制代碼
  • react-redux 7.1的hooks版

這也是官方較為推薦的,react-redux 的hooks版本提供了useSelector()、useDispatch()、useStore()這3個主要方法,分別對應與mapState、mapDispatch以及直接拿到redux中store的實例.

簡單介紹一下useSelector,在useSelector中除了能從store中拿到state以外,還支持深度比較的功能,如果相應的state前后沒有改變,就不會去重新的計算.

舉例來說,最基礎的用法:

import?React?from?'react'import?{?useSelector?}?from?'react-redux'export?const?TodoListItem?=?props?=>?{?const?todo?=?useSelector(state?=>?state.todos[props.id])?return?<div>{todo.text}</div>}
復制代碼

實現緩存功能的用法:

import?React?from?'react'import?{?useSelector?}?from?'react-redux'import?{?createSelector?}?from?'reselect'const?selectNumOfDoneTodos?=?createSelector(?state?=>?state.todos,
?todos?=>?todos.filter(todo?=>?todo.isDone).length
)export?const?DoneTodosCounter?=?()?=>?{?const?NumOfDoneTodos?=?useSelector(selectNumOfDoneTodos)?return?<div>{NumOfDoneTodos}</div>}export?const?App?=?()?=>?{?return?(?<>
?<span>Number?of?done?todos:</span>
?<DoneTodosCounter?/>
?</>
?)
}
復制代碼

在上述的緩存用法中,只要todos.filter(todo => todo.isDone).length不改變,就不會去重新計算.

四、總結

react中完整的狀態管理分為全局狀態和局部狀態,而react hooks簡化了局部狀態,使得管理局部狀態以及控制局部渲染極其方便,但是react hooks本質上還是一個視圖組件層的,并沒有完美的解決組件間的通信問題,也就是說,redux等狀態管理機和react hooks本質上并不矛盾。

在我的實踐中,用redux實現組件間的通信而react hooks來實現局部的狀態管理,使得代碼簡單已讀的同時,也減少了很多不必要的redux樣板代碼.

如何優雅的使用react hooks來進行狀態管理


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女