在React的函数组件中,我们可以通过useState()来创建state。这种创建state的方式会给我们返回两个东西state和setState()。state用来读取数据,而setState()用来设置修改数据。但是这种方式也存在着一些不足,因为所有的修改state的方式都必须通过setState()来进行,如果遇到一些复杂度比较高的state时,这种方式似乎就变得不是那么的优雅。
举个例子,之前的《汉堡到家》的练习中,App.js中有一个state叫做cartData用来存储购物车数据。但是这个数据本身是比较复杂的,它包括了多个属性:
const [cartData, setCartData] = useState({
items: [],
totalAmount: 0,
totalPrice: 0
});
同时购物车,也需要多个操作方法,像是添加食物、删除食物、清除购物车,而useState()只给我们提供了一个setCartData()方法,所以我们不得不在继续创建出三个不同的方法以实现出不同的功能:
const addItem = (meal) => {
const newCart = {...cartData};
if (newCart.items.indexOf(meal) === -1) {
newCart.items.push(meal);
meal.amount = 1;
} else {
meal.amount += 1;
}
newCart.totalAmount += 1;
newCart.totalPrice += meal.price;
setCartData(newCart);
};
const removeItem = (meal) => {
const newCart = {...cartData};
meal.amount -= 1;
if (meal.amount === 0) {
newCart.items.splice(newCart.items.indexOf(meal), 1);
}
newCart.totalAmount -= 1;
newCart.totalPrice -= meal.price;
setCartData(newCart);
};
const clearCart = () => {
const newCart = {...cartData};
newCart.items.forEach(item => delete item.amount);
newCart.items = [];
newCart.totalAmount = 0;
newCart.totalPrice = 0;
setCartData(newCart);
};
这三个函数定义在了App.js中,是操作cartData的三个函数。就这带来一些问题,首先,三个方法都是操作cartData的,但是它们被定义在App.js中和其他的函数混杂在了一起,维护起来并不方便。其次,三个方法并不是App.js自己调用,而是通过Context传递给其他组件调用,由于是三个函数所以我们不得不在Context中分别传递三个属性,也不方便。再有,如果后期我需要再添加新的功能,依然不可避免的要定义新的函数,并且修改Context。总之,就是各种不便利,这种不便还会随着项目复杂的提升而增加。
Reducer横空出世
为了解决复杂State带来的不便,React为我们提供了一个新的使用State的方式。Reducer横空出世,reduce单词中文意味减少,而reducer我觉得可以翻译为“当你的state的过于复杂时,你就可以使用的可以对state进行整合的工具”。当然这是个玩笑话,个人认为Reducer可以翻译为“整合器”,它的作用就是将那些和同一个state相关的所有函数都整合到一起,方便在组件中进行调用。
当然工具都有其使用场景,Reducer也不例外,它只适用于那些比较复杂的state,对于简单的state使用Reducer只能是徒增烦恼。但是由于初学,我们会先用一个简单的案例来对其进行演示,实际应用我们后边会以cartData作为演示。
和State相同Reducer也是一个钩子函数,语法如下:
const [state, dispatch] = useReducer(reducer, initialArg, init);
它的返回值和useState()类似,第一个参数是state用来读取state的值,第二个参数同样是一个函数,不同于setState()这个函数我们可以称它是一个“派发器”,通过它可以向reducer()发送不同的指令,控制reducer()做不同的操作。
它的参数有三个,第三个我们暂且忽略,只看前两个。reducer()是一个函数,也是我们所谓的“整合器”。它的返回值会成为新的state值。当我们调用dispatch()时,dispatch()会将消息发送给reducer(),reducer()可以根据不同的消息对state进行不同的处理。initialArg就是state的初始值,和useState()参数一样。
上代码:
import {useReducer, useState} from 'react';
const reducer = (state, action) => {
switch(action.type){
case 'add':
return state + 1;
case 'sub':
return state - 1;
}
};
function App() {
const [count, countDispath] = useReducer(reducer,1);
return (
<div className="App">
{count}
<div>
<button onClick={()=>countDispath({type:'sub'})}>-</button>
<button onClick={()=>countDispath({type:'add'})}>+</button>
</div>
</div>
);
}
export default App;
不知道代码你能不看懂,不过我也是不想分析了,不写了看视频吧。
修改购物车
修改App.js
...略...
const cartReducer = (state, action) => {
const newCart = {...state};
switch (action.type){
case 'ADD_ITEM':
if (newCart.items.indexOf(action.meal) === -1) {
newCart.items.push(action.meal);
action.meal.amount = 1;
} else {
action.meal.amount += 1;
}
newCart.totalAmount += 1;
newCart.totalPrice += action.meal.price;
return newCart;
case 'REMOVE_ITEM':
action.meal.amount -= 1;
if (action.meal.amount === 0) {
newCart.items.splice(newCart.items.indexOf(action.meal), 1);
}
newCart.totalAmount -= 1;
newCart.totalPrice -= action.meal.price;
return newCart;
case 'CLEAR_CART':
newCart.items.forEach(item => delete item.amount);
newCart.items = [];
newCart.totalAmount = 0;
newCart.totalPrice = 0;
return newCart;
default:
return state;
}
};
const App = () => {
...略...
const [cartData, cartDispatch] = useReducer(cartReducer, {
items: [],
totalAmount: 0,
totalPrice: 0
});
...略...
return (
<CartContext.Provider value={{...cartData, cartDispatch}}>
<div>
<FilterMeals onFilter={filterHandler}/>
<Meals
mealsData={mealsData}
/>
<Cart/>
</div>
</CartContext.Provider>
);
};
export default App;
在其他组件中,需要操作购物车时,只需先获取CartContext然后通过ctx.cartDispath操作购物车:
const ctx = useContext(CartContext); // 加载context
ctx.cartDispatch({type:'CLEAR_CART'}); // 清空购物车
ctx.cartDispatch({type:'ADD_ITEM', meal:props.meal}); // 添加食物
ctx.cartDispatch({type:'REMOVE_ITEM', meal:props.meal}); // 删除食物
打卡
超哥np,信超哥,得永生