React 用 useReducer 替换 useState 同时还使用自定义钩子
React replacing useState with useReducer while also using a custom hook
所以我正在制作一个预算跟踪应用程序,用户可以在其中将他们的收入来源添加到收入列表中,并将支出添加到支出列表中,我让它工作了,但我想看看我是否可以使用 useReducer 而不是多次使用 useState。这就是我被卡住的地方,因为我不确定在减速器中 return 是什么。
我正在使用 2 个状态对象,收入和支出。基本上现在我想使用减速器来允许用户向收入对象添加收入来源。我想看看我是否可以在 reducer 中设置 incomes 对象,当调用 dispatch 时将操作设置为 ADD_INCOME_ITEM,budgetObj.type 将设置为 + 和 setIncomes(incomes.concat( budgetObj)) 将被调用(收入来源将被添加到收入列表中)。我希望我说清楚了!
App.js:
import React, { useState, useReducer } from 'react';
import './App.css';
import BudgetInput from './components/input/BudgetInput';
import BudgetOutput from './components/output/BudgetOutput';
import IncomeOutputList from './components/output/IncomeOutputList';
import ExpenseOutputList from './components/output/ExpenseOutputList';
// custom hook
const useSemiPersistentState = (key, initialState) => {
const [value, setValue] = React.useState(
localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)) : initialState
);
React.useEffect(()=>{
localStorage.setItem(key, JSON.stringify(value));
}, [value, key])
return [value, setValue];
};
const App = () => {
// want to replace these 5 lines with useReducer
const [incomes, setIncomes] = useSemiPersistentState('income',[{}]);
const [expenses, setExpenses] = useSemiPersistentState('expense',[{}]);
const [description, setDescription] = useState('');
const [type, setType] = useState('+');
const [value, setValue] = useState('');
const budgetObj = {
desc: description,
budgetType: type,
incomeValue: value
}
const initialbudget = {
desc: '',
budgetType: '+',
incomeValue: ''
}
const budgetReducer = (state, action) => {
switch(action.type) {
case 'ADD_INCOME_ITEM': //want to set the incomes object here
return setIncomes(incomes.concat(budgetObj)); // not sure if this is correct??
// also set state here???
}
//will add more cases here
}
const [budget, dispatchBudget] = useReducer( //reducer, initial state
budgetReducer,
initialbudget
);
const handleBudgetObjArray = () => {
if(budgetObj.budgetType === '+') {
setIncomes(incomes.concat(budgetObj)); //want to move this to reducer
}
else if(budgetObj.budgetType === '-') {
setExpenses(expenses.concat(budgetObj)); //want to move this to reducer
}
}
const handleChange = (event) => {
setDescription(event.target.value);
}
const handleSelectChange = (event) => {
setType(event.target.value);
}
const handleValueChange = (event) => {
setValue(event.target.value);
console.log(budgetObj)
}
const removeInc = (index) => {
let items = JSON.parse(localStorage.getItem("income"));
items.splice(index, 1);
setIncomes(items);
}
const removeExp = (index) => {
let items = JSON.parse(localStorage.getItem("expense"));
items.splice(index, 1);
setExpenses(items);
}
return (
<div className="App">
<link href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css"></link>
<div className="top">
<BudgetOutput />
</div>
<div className="bottom">
<BudgetInput
descValue={description}
onDescChange={handleChange}
onSelectChange={handleSelectChange}
type={type}
onBudgetSubmit={handleBudgetObjArray}
budgetValue={value}
onValChange={handleValueChange}
/>
<div className="container clearfix">
<IncomeOutputList
list={incomes}
removeIncome={(index)=>removeInc(index)}
/>
<ExpenseOutputList
list={expenses}
removeExpense={(index)=>removeExp(index)}
/>
</div>
</div>
</div>
)
};
export default App;
此文件是设置 budgetObj 的地方:
import React from 'react';
import IncomeOutput from './IncomeOutput';
// list will be list of income objects
const IncomeOutputList = ({ list, removeIncome }) => {
return (
<div className="income__list">
<div className="income__list--title">INCOME</div>
{list.map((item, index, arr) => <IncomeOutput
id={item.id}
value={item.incomeValue}
type={item.budgetType}
desc={item.desc}
// handleButton={handler(index)}
handleButton={()=>removeIncome(index)}
/>
)}
</div>
)
}
export default IncomeOutputList;
useReducer
替换 useState
。它是你所在的州。所以这里没有意义。
case 'ADD_INCOME_ITEM': //want to set the incomes object here
return setIncomes(incomes.concat(budgetObj)); // not sure if this is correct??
包含 incomes
和 setIncomes
的那五 useState
行代码将被完全删除,因此您不能在 reducer 中使用它们。
您的减速器的 initialState
看起来只是一个预算对象。它需要是一个代表整个组件状态的对象。像这样:
const initialBudget = {
description: '',
type: '+',
value: '',
};
const initialState = {
incomes: [{}],
expenses: [{}],
budgetObj: initialBudget,
};
我单独定义 initialBudget
以便我们可以使用它轻松重置 budgetObj
。
您的减速器通过获取 state
和 action
并返回下一个状态来处理操作,如下所示:
const budgetReducer = (state, action) => {
switch(action.type) {
case 'SUBMIT_BUDGET':
// I am using spread to clone the object to be safe, might not be 100% neccessary
const budget = {...state.budget};
// figure out where to add the current budget object
const isIncome = budget.budgetType === '+';
return {
...state, // not actually necessary in this case since we are updating every property
incomes: isIncome ? state.incomes.concat(budget) : state.incomes, // maybe add to incomes
expenses: isIncome ? state.expenses : state.expenses.concat(budget), // maybe add to expenses
budgetObj: initialBudget, // reset budget object
}
default:
return state;
}
}
所以我正在制作一个预算跟踪应用程序,用户可以在其中将他们的收入来源添加到收入列表中,并将支出添加到支出列表中,我让它工作了,但我想看看我是否可以使用 useReducer 而不是多次使用 useState。这就是我被卡住的地方,因为我不确定在减速器中 return 是什么。
我正在使用 2 个状态对象,收入和支出。基本上现在我想使用减速器来允许用户向收入对象添加收入来源。我想看看我是否可以在 reducer 中设置 incomes 对象,当调用 dispatch 时将操作设置为 ADD_INCOME_ITEM,budgetObj.type 将设置为 + 和 setIncomes(incomes.concat( budgetObj)) 将被调用(收入来源将被添加到收入列表中)。我希望我说清楚了!
App.js:
import React, { useState, useReducer } from 'react';
import './App.css';
import BudgetInput from './components/input/BudgetInput';
import BudgetOutput from './components/output/BudgetOutput';
import IncomeOutputList from './components/output/IncomeOutputList';
import ExpenseOutputList from './components/output/ExpenseOutputList';
// custom hook
const useSemiPersistentState = (key, initialState) => {
const [value, setValue] = React.useState(
localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)) : initialState
);
React.useEffect(()=>{
localStorage.setItem(key, JSON.stringify(value));
}, [value, key])
return [value, setValue];
};
const App = () => {
// want to replace these 5 lines with useReducer
const [incomes, setIncomes] = useSemiPersistentState('income',[{}]);
const [expenses, setExpenses] = useSemiPersistentState('expense',[{}]);
const [description, setDescription] = useState('');
const [type, setType] = useState('+');
const [value, setValue] = useState('');
const budgetObj = {
desc: description,
budgetType: type,
incomeValue: value
}
const initialbudget = {
desc: '',
budgetType: '+',
incomeValue: ''
}
const budgetReducer = (state, action) => {
switch(action.type) {
case 'ADD_INCOME_ITEM': //want to set the incomes object here
return setIncomes(incomes.concat(budgetObj)); // not sure if this is correct??
// also set state here???
}
//will add more cases here
}
const [budget, dispatchBudget] = useReducer( //reducer, initial state
budgetReducer,
initialbudget
);
const handleBudgetObjArray = () => {
if(budgetObj.budgetType === '+') {
setIncomes(incomes.concat(budgetObj)); //want to move this to reducer
}
else if(budgetObj.budgetType === '-') {
setExpenses(expenses.concat(budgetObj)); //want to move this to reducer
}
}
const handleChange = (event) => {
setDescription(event.target.value);
}
const handleSelectChange = (event) => {
setType(event.target.value);
}
const handleValueChange = (event) => {
setValue(event.target.value);
console.log(budgetObj)
}
const removeInc = (index) => {
let items = JSON.parse(localStorage.getItem("income"));
items.splice(index, 1);
setIncomes(items);
}
const removeExp = (index) => {
let items = JSON.parse(localStorage.getItem("expense"));
items.splice(index, 1);
setExpenses(items);
}
return (
<div className="App">
<link href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css"></link>
<div className="top">
<BudgetOutput />
</div>
<div className="bottom">
<BudgetInput
descValue={description}
onDescChange={handleChange}
onSelectChange={handleSelectChange}
type={type}
onBudgetSubmit={handleBudgetObjArray}
budgetValue={value}
onValChange={handleValueChange}
/>
<div className="container clearfix">
<IncomeOutputList
list={incomes}
removeIncome={(index)=>removeInc(index)}
/>
<ExpenseOutputList
list={expenses}
removeExpense={(index)=>removeExp(index)}
/>
</div>
</div>
</div>
)
};
export default App;
此文件是设置 budgetObj 的地方:
import React from 'react';
import IncomeOutput from './IncomeOutput';
// list will be list of income objects
const IncomeOutputList = ({ list, removeIncome }) => {
return (
<div className="income__list">
<div className="income__list--title">INCOME</div>
{list.map((item, index, arr) => <IncomeOutput
id={item.id}
value={item.incomeValue}
type={item.budgetType}
desc={item.desc}
// handleButton={handler(index)}
handleButton={()=>removeIncome(index)}
/>
)}
</div>
)
}
export default IncomeOutputList;
useReducer
替换 useState
。它是你所在的州。所以这里没有意义。
case 'ADD_INCOME_ITEM': //want to set the incomes object here
return setIncomes(incomes.concat(budgetObj)); // not sure if this is correct??
包含 incomes
和 setIncomes
的那五 useState
行代码将被完全删除,因此您不能在 reducer 中使用它们。
您的减速器的 initialState
看起来只是一个预算对象。它需要是一个代表整个组件状态的对象。像这样:
const initialBudget = {
description: '',
type: '+',
value: '',
};
const initialState = {
incomes: [{}],
expenses: [{}],
budgetObj: initialBudget,
};
我单独定义 initialBudget
以便我们可以使用它轻松重置 budgetObj
。
您的减速器通过获取 state
和 action
并返回下一个状态来处理操作,如下所示:
const budgetReducer = (state, action) => {
switch(action.type) {
case 'SUBMIT_BUDGET':
// I am using spread to clone the object to be safe, might not be 100% neccessary
const budget = {...state.budget};
// figure out where to add the current budget object
const isIncome = budget.budgetType === '+';
return {
...state, // not actually necessary in this case since we are updating every property
incomes: isIncome ? state.incomes.concat(budget) : state.incomes, // maybe add to incomes
expenses: isIncome ? state.expenses : state.expenses.concat(budget), // maybe add to expenses
budgetObj: initialBudget, // reset budget object
}
default:
return state;
}
}