您好,登錄后才能下訂單哦!
看到“reducer”這個詞,容易讓人聯想到Redux,但是在本文中,不必先理解Redux才能閱讀這篇文章。咱們將一起討論“reducer”實際上是什么,以及如何利用useReducer來管理組件中的復雜狀態,以及這個新鉤子對Redux意味著什么?
Reducer 是什么鬼
如果你熟悉Redux或數組上中的reduce方法,你大概就知道“reducer”是什么。 如果不熟悉,“reducer”大概是一個帶有2個值并返回1個值的函數這么個意思。
如果你有一系列的東西,并且想將這些東西組合成一個單獨的物體。“函數式編程”中就是使用Array的reduce函數。 例如,如果你有一個數字數組并且想得到數組中所有數字的總和,咱們就可以寫一個reducer函數并將它傳遞給reduce,如下所示:
let?numbers?=?[1,?2,?3]; let?sum?=?numbers.reduce((total,?number)?=>?{ ?return?total?+?number; },0) 復制代碼
如果你以前沒用過這個,它可能看起來有點神秘。它所做的是為數組的每個元素調用函數,傳入前一個total和當前元素 number。無論你返回什么,都會成為新的total。reduce的第二個參數(在本例中為0)是total的初始值。在本例中,reduce函數會被調用3次:
調用 (0, 1) 返回 1
調用 (1, 2) 返回 3
調用 (3, 4) 返回 6
reduce返回6,它保存在sum中。
使用useReducer又會是什么樣的?
各位花了在半篇幅來解釋Array的reduce函數,因為 useReducer 參數與 reduce 相同,并且工作方式基本一樣。 useReducer接收 (state, action) => newState,并且返回了一個與當前state成對的dispatch的方法。 咱們使用 useReducer 來編寫上面的求和例子。
useReducer((state,?acton)?=>?{ ?return?state?+?action },?0) 復制代碼
useReducer返回一個包含2個元素的數組,類似于useState hook。 第一個是當前狀態,第二個是dispatch方法,如下所示:
const?[sum,?dispatch]?=?useReducer((state,?action)?=>?{ ?return?state?+?action },?0) 復制代碼
注意,state可以是任何值,它不一定是一個對象,可以是一個數字,一個數組,或者其他任何類型。
盡管 useReducer 是擴展的 hook, 而 useState 是基本的 hook,但 useState 實際上執行的也是一個 useReducer。這意味著 useReducer 是更原生的,你能在任何使用 useState 的地方都替換成使用 useReducer。
import?React,?{?useReducer?}?from?'react'; function?Counter()?{ ?//?First?render?will?create?the?state,?and?it?will ?//?persist?through?future?renders ?const?[sum,?dispatch]?=?useReducer((state,?action)?=>?{ ?return?state?+?action; ?},?0); ?return?( ?<> ?{sum} ?<button?onClick={()?=>?dispatch(1)}> ?Add?1 ?</button> ?</> ?); } 復制代碼
點擊按鈕dispatch一個值為1的action,該action將被添加到當前狀態,然后組件使用新的狀態重新渲染。
這里故意展示了,派發action沒有遵循Redux的典型模式{type: "INCREMENT BY"、value: 1}或其他類似的東西。hook 的世界是一個新的世界:值得考慮的是,你是否發現舊的模式有價值并希望保留它們,或者你是否愿意更改它們。
一些更復雜的例子
再來看看更接近典型Redux reducer 的例子。創建一個組件來管理購物列表,這里看還會使用另外一個 hook:useRef。
首先,導入兩個 hook
import?React,?{?useReducer,?useRef?}?from?'react'; 復制代碼
然后創建一個設置ref和reducer的組件。 ref保存對表單的引用,以便咱們可以提取其值。
function?ShoppingList()?{ ?const?inputRef?=?useRef(); ?const?[items,?dispatch]?=?useReducer((state,?action)?=>?{ ?switch?(action.type)?{ ?//?do?something?with?the?action ?} ?},?[]); ?return?( ?<> ?<form?onSubmit={handleSubmit}> ?<input?ref={inputRef}?/> ?</form> ?<ul> ?{items.map((item,?index)?=>?( ?<li?key={item.id}> ?{item.name} ?</li> ?))} ?</ul> ?</> ?); } 復制代碼
請注意,在這種情況下,咱們的“state”是一個數組。 咱們通過useReducer第二個參數將它初始化為一個空數組,并從reducer函數返回一個數組。
useRef Hook
useRef hook為DOM節點創建持久引用。 調用useRef會創建一個空的節點。它返回的對象具有current屬性,因此在上面的示例中,咱們可以使用inputRef.current訪問輸入的DOM節點。 如果你熟悉React.createRef(),則其工作原理非常相似。
但是,useRef返回的對象不僅僅是一種保存DOM引用的方法。 它可以保存特定于此組件實例的任何值,并且它在渲染之間保持不變。
useRef可用于創建通用實例變量,就像使用具有this.whatever = value的React類組件一樣。 唯一的問題是,寫入它會被視為“副作用”,因此咱們無法在渲染過程中更改它,需要在useEffect hook 中才能修改。
回到useReducer示例
我們用表單來處理用戶的輸入,按回車提交表彰。 現在來編寫handleSubmit函數,該函數主要做的是將一個項添加到列表中,以及處理reducer中的 action。
function?ShoppingList()?{ ?const?inputRef?=?useRef(); ?const?[items,?dispatch]?=?useReducer((state,?action)?=>?{ ?switch?(action.type)?{ ?case?'add': ?return?[ ?...state, ?{ ?id:?state.length, ?name:?action.name ?} ?]; ?default: ?return?state; ?} ?},?[]); ?function?handleSubmit(e)?{ ?e.preventDefault(); ?dispatch({ ?type:?'add', ?name:?inputRef.current.value ?}); ?inputRef.current.value?=?''; ?} ?return?( ?//?...?same?... ?); } 復制代碼
在reducer函數中主要判斷兩種情況:一種用于action.type==='add'的情況,還有就是默認下的情況。
當action.type為 add 時,它返回一個包含所有舊元素的新數組,以及最后的新元素。
這里有一點需要注意的是,咱們使用數組的length作為一種自動遞增的 ID 方便演示,但是對于一個真正的應用程序來說這是不可靠,因為它可能導致重復的ID和bug。(最好使用uuid這樣的庫,或者讓服務器生成一個惟一的ID!)
當用戶在輸入框中按Enter鍵時會調用handleSubmit函數,因此咱們需要調用preventDefault以避免在發生這種情況時重新加載整頁。 然后dispatch派發一個 action。
刪除項
現在來看看如何從列表中刪除項的。
在項目中添加一個刪除<button>,點擊該按鈕派發 它將發送一個 action type === "remove"的操作,以及要刪除的項的索引。
然后咱們只需要在reducer中處理該action
function?ShoppingList()?{ ?const?inputRef?=?useRef(); ?const?[items,?dispatch]?=?useReducer((state,?action)?=>?{ ?switch?(action.type)?{ ?case?'add': ?//?...?same?as?before?... ?case?'remove': ?//?keep?every?item?except?the?one?we?want?to?remove ?return?state.filter((_,?index)?=>?index?!=?action.index); ?default: ?return?state; ?} ?},?[]); ?function?handleSubmit(e)?{?/*...*/?} ?return?( ?<> ?<form?onSubmit={handleSubmit}> ?<input?ref={inputRef}?/> ?</form> ?<ul> ?{items.map((item,?index)?=>?( ?<li?key={item.id}> ?{item.name} ?<button ?onClick={()?=>?dispatch({?type:?'remove',?index?})} ?> ?X ?</button> ?</li> ?))} ?</ul> ?</> ?); } 復制代碼
練習:清空列表
試著添加一個功能:添加一個清空列表的按鈕。
在<ul>上方插入一個按鈕,并為其提供一個onClick prop,派發一個action ,type 為“clear”的動作,并在 reducer 方法執行清空列表的動作。
可以在前面 CodeSandbox的基礎上完成。
Redux 會死嗎
大部分人看到useReducer hook, React 現在已經內置了reducer ,它有Context傳遞數據,所以可能會想到 Redux 會不會因此就死了,以下是原作者給出的一些看法。
作者不認為useReducer會殺死Redux。React Hook 擴展了React在狀態管理方面的能力,所以會讓使用 Redux的情況減少。
Redux仍然比Context + useReducer的組合做得更多,它具有Redux DevTools 用于調試,可定制中間件、,以及整個相關庫生態系統,當然 Redu x在很多地方都被過度使用了,但它仍然具有強大的功能。
Redux提供了一個全局存儲,可以在其中集中保存應用程序數據。useReducer本地化到特定組件。但是,沒有什么可以阻止咱們使用useReducer和useContext構建自己的迷你redux 。如果你想這么做,而且符合你的需要,那就去做吧!
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。