爲了保證的可讀性,本文采用意譯而非直譯。前端
想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!react
這個禮拜《大遷世界》有抽獎活動,獎品:專欄 《左耳聽風》 x3, 技術書 x5,歡迎關注回覆:抽獎
看到「reducer」
這個詞,容易讓人聯想到Redux,可是在本文中,沒必要先理解Redux
才能閱讀這篇文章。我們將一塊兒討論「reducer
」其實是什麼,以及如何利用useReducer
來管理組件中的複雜狀態,以及這個新鉤子對Redux意味着什麼?git
若是你熟悉Redux或數組上中的reduce
方法,你大概就知道「reducer」是什麼。 若是不熟悉,「reducer
」大概是一個帶有2
個值並返回1
個值的函數這麼個意思。github
若是你有一系列的東西,而且想將這些東西組合成一個單獨的物體。「函數式編程」中就是使用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
次:redux
(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爲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的基礎上完成。
大部分人看到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。
乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。
https://github.com/qq44924588...
我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!
關注公衆號,後臺回覆福利,便可看到福利,你懂的。