原文: 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
更好的地方?也许你能够尝试使用咱们这里介绍的想法来构建一些东西,下面是一些想法。
本文已经联系原文做者,并受权翻译,转载请保留原文连接