做者:Dave Ceddiahtml
譯者:前端小智前端
來源:daveceddiareact
阿里雲最近在作活動,低至2折,有興趣能夠看看:promotion.aliyun.com/ntms/yunpar…git
爲了保證的可讀性,本文采用意譯而非直譯。github
看到「reducer」
這個詞,容易讓人聯想到Redux,可是在本文中,沒必要先理解Redux
才能閱讀這篇文章。我們將一塊兒討論「reducer
」其實是什麼,以及如何利用useReducer
來管理組件中的複雜狀態,以及這個新鉤子對Redux意味着什麼?編程
若是你熟悉Redux或數組上中的reduce
方法,你大概就知道「reducer」是什麼。 若是不熟悉,「reducer
」大概是一個帶有2
個值並返回1
個值的函數這麼個意思。redux
若是你有一系列的東西,而且想將這些東西組合成一個單獨的物體。「函數式編程」中就是使用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爲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。
原文:
www.robinwieruch.de/react-usere…
阿里雲最近在作活動,低至2折,有興趣能夠看看:promotion.aliyun.com/ntms/yunpar…
乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。
我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!
關注公衆號,後臺回覆福利,便可看到福利,你懂的。