原文: Getting to Know the useReducer React Hook
做者:Kingsley Silas
譯者:博軒
useReducer
是 React 16.8.0 中爲數很少由官方提供的 React Hook 之一。它接受一個 reducer 函數 ,以及一個初始的應用程序狀態,而後返回當前應用程序的狀態,和一個調度函數(dispatch)。css
一個簡單的例子:html
const [state, dispatch] = useReducer(reducer, initialState);
這樣有什麼好處?一個好主意是讓咱們試着想象一下,一個應用初次加載屬性時的全部狀況。它多是可交互式的地圖上的一個起點。或許是容許用戶使用一個默認的模型來自定義選項,構建一個自定義汽車。這裏有一個很是簡潔的計算器,當計算器重置時,使用 useReducer
來使應用程序恢復默認狀態。react
https://codepen.io/dpgian/emb...redux
咱們將在這篇文章中深刻研究幾個例子,瞭解一下 useReducer Hook
自己,以及應該什麼時候使用。數組
提及 useState
,就不得不說起 JavaScript 的 reduce
方法。最開始,咱們很難將它們聯繫起來,可是 Sarah 的一篇關於 reducer
的文章 能夠幫助咱們更好的理解。函數
關於reducer
最重要的一點就是: 它每次只返回一個值。reducer
的工做就是減小。那個值能夠是數字,字符串,對象,數組或者對象,可是它老是一個值。reducer
在不少狀況下都頗有效,可是他對於處理輸入一組值,返回一個值的狀況很是有用。
假設咱們有一個數字數組,reduce
將依次累加每個值。這是數組:學習
const numbers = [1, 2, 3]
...以及一個函數,每次 reducer
中的計算都會在控制檯打印出來。這有助於咱們理解 reducer
將數組提取爲單個數字的過程。this
const reducer = function (tally, number) { console.log(`Tally: ${tally}, Next number: ${number}, New Total: ${tally + number}`) return tally + number }
如今,讓咱們運行一個 reducer
。正如咱們所看到的,reduce
接收一個調度函數,以及一個初始狀態。讓咱們傳入一個 reducer
函數,以及一個初始值:0。spa
const total = numbers.reduce(reducer, 0)
這是控制檯打印的內容:翻譯
"Tally: 0, Next number: 1, New Total: 1" "Tally: 1, Next number: 2, New Total: 3" "Tally: 3, Next number: 3, New Total: 6"
看 reduce
是如何將一個初始值累加,獲得咱們的最終結果的。在這個例子中,最終結果是 6。
我也十分喜歡 Dave Ceddia
的示例 ,他展現瞭如何使用 reduce
來拼寫一個單詞:
var letters = ['r', 'e', 'd', 'u', 'c', 'e']; // `reduce` takes 2 arguments: // - a function to do the reducing (you might say, a "reducer") // - an initial value for accumulatedResult var word = letters.reduce( function(accumulatedResult, arrayItem) { return accumulatedResult + arrayItem; }, ''); // <-- notice this empty string argument: it's the initial value console.log(word) // => "reduce"
好的,接下來到了這篇文章的重點: useReducer
。到了這裏的一切都很重要,由於使用 reduce
調用一個函數來處理初始值的方式,就是咱們接下來的目標。它是同一種概念,可是會返回一個數組包含兩個元素,當前的狀態和調度函數。
const [state, dispatch] = useReducer(reducer, initialArg, init);
第三個參數init
是什麼?它是一個可選值,能夠用來惰性提供初始狀態。這意味着咱們可使用使用一個init
函數來計算初始狀態/值,而不是顯式的提供值。若是初始值可能會不同,這會很方便,最後會用計算的值來代替初始值。
爲了使它工做,咱們須要作一些事情:
action
的函數來更新 state
useReducer
,基於初始值計算並更新 state
。計數器就是一個經典的例子。事實上這也是官方文檔使用這個例子的緣由:
https://codepen.io/kinsomicro...
這是一個很好的例子,由於它演示了每次經過單擊增長或減小按鈕觸發操做時如何使用初始狀態(零值)來計算新值。咱們甚至能夠在其中輸入一個「重置」按鈕,將總數恢復到初始狀態零。
https://codepen.io/geoffgraha...
在此示例中,咱們假設用戶已經選擇了本身要購買的汽車。可是,咱們但願用戶能夠爲汽車添加額外的選項。每一個選項的價格都會影響汽車的總價。
首先,咱們須要建立初始狀態,其中包括汽車,能夠跟蹤功能的空數組 features
,$26,395 的起始價格 price
,一個存放未選配件的列表 store
,用戶能夠選擇他們想要的東西。
const initialState = { additionalPrice: 0, car: { price: 26395, name: "2019 Ford Mustang", image: "https://cdn.motor1.com/images/mgl/0AN2V/s1/2019-ford-mustang-bullitt.jpg", features: [] }, store: [ { id: 1, name: "V-6 engine", price: 1500 }, { id: 2, name: "Racing detail package", price: 1500 }, { id: 3, name: "Premium sound system", price: 500 }, { id: 4, name: "Rear spoiler", price: 250 } ] };
咱們的 reducer
功能將處理兩件事:添加和刪除新項目。
const reducer = (state, action) => { switch (action.type) { case "REMOVE_ITEM": return { ...state, additionalPrice: state.additionalPrice - action.item.price, car: { ...state.car, features: state.car.features.filter((x) => x.id !== action.item.id)}, store: [...state.store, action.item] }; case "BUY_ITEM": return { ...state, additionalPrice: state.additionalPrice + action.item.price, car: { ...state.car, features: [...state.car.features, action.item] }, store: state.store.filter((x) => x.id !== action.item.id) } default: return state; } }
當用戶選擇他想要的項目時,咱們更新汽車的 features
,增長 additionalPrice
並從商店中刪除該項目。咱們確保 state
其餘部分會保持原樣。當用戶從功能列表中刪除項目時,咱們會執行相似操做 - 減小額外價格,將項目返回到商店。
如下是App組件的代碼。
const App = () => { const inputRef = useRef(); const [state, dispatch] = useReducer(reducer, initialState); const removeFeature = (item) => { dispatch({ type: 'REMOVE_ITEM', item }); } const buyItem = (item) => { dispatch({ type: 'BUY_ITEM', item }) } return ( <div> <div className="box"> <figure className="image is-128x128"> <img src={state.car.image} /> </figure> <h2>{state.car.name}</h2> <p>Amount: ${state.car.price}</p> <div className="content"> <h6>Extra items you bought:</h6> {state.car.features.length ? ( <ol type="1"> {state.car.features.map((item) => ( <li key={item.id}> <button onClick={() => removeFeature(item)} className="button">X </button> {item.name} </li> ))} </ol> ) : <p>You can purchase items from the store.</p> } </div> </div> <div className="box"> <div className="content"> <h4>Store:</h4> {state.store.length ? ( <ol type="1"> {state.store.map((item) => ( <li key={item.id}>\ <button onClick={() => buyItem(item)} className="button">Buy </button> {item.name} </li> ))} </ol> ) : <p>No features</p> } </div> <div className="content"> <h4> Total Amount: ${state.car.price + state.additionalPrice} </h4> </div> </div> </div> ); }
調度操做會包含所選項的詳細信息。咱們使用 action
的類型來肯定 reducer
函數如何處理狀態的更新。您能夠看到渲染視圖會根據您的操做而作出改變 - 從商店購買的商品會從商店中刪除,並添加到功能列表當中。此外,總金額也會更新。毫無疑問,咱們能夠對示例進行修改達到學習的目的。
聰明的讀者可能一直在想這個問題。個人意思是,setState
大體會作相同的事情,不是嗎?返回一個具有狀態的值,以及一個可使用新值從新渲染組件的函數。
const [state, setState] = useState(initialState);
咱們甚至可使用 useState
來實現官方文檔中的計數器的例子。可是 useReducer
在處理複雜狀態的時候是最優解。Kent C. Dodds 寫了一個二者之間的差別(雖然他常用 setState
)他提供了一個 useReducer
的最佳實踐:
當你一個元素中的狀態,依賴另外一個元素中的狀態,最好使用useReducer
例如,你正在完成一個井字遊戲。你的組件中的 狀態被稱爲
squares
,它包含了左右方格,以及其中的值。
個人經驗是使用 useReducer
來處理複雜的狀態,尤爲是初始狀態是基於其餘元素生成的狀況下。
若是你使用 Redux 工做,也會理解這裏所涉及的全部內容,由於它的設計理念是經過 Context API 來存儲,傳遞組件之間的狀態 - 沒必要經過其餘組件傳遞 props
來實現。
那麼, useReducer
取代 Redux 了嗎?不,個人意思是,你基本能夠經過 useContext hook
來實現你本身的 Redux,可是,這並不表明 Redux 沒有用了,它仍然有許多其餘的功能和優勢值得考慮。
你會在哪裏使用 useReducer
?他是否有比 setState
更好的地方?也許你能夠嘗試使用咱們這裏介紹的想法來構建一些東西,下面是一些想法。
本文已經聯繫原文做者,並受權翻譯,轉載請保留原文連接