React 用 useReducer 替换 useState,同时还使用自定义钩子

Posted

技术标签:

【中文标题】React 用 useReducer 替换 useState,同时还使用自定义钩子【英文标题】:React replacing useState with useReducer while also using a custom hook 【发布时间】:2021-05-10 04:07:59 【问题描述】:

所以我正在制作一个预算跟踪应用程序,用户可以在其中将收入来源添加到收入列表并将费用添加到费用列表中,并且我得到了它的工作,但我想看看我是否可以使用 useReducer 而不是使用 useState很多次。这就是我卡住的地方,因为我不确定在减速器中返回什么。

我正在使用 2 个状态对象,收入和支出。基本上现在我想使用减速器来允许用户向收入对象添加收入来源。我想看看我是否可以在reducer中设置收入对象,并且当使用设置为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;

【问题讨论】:

【参考方案1】:

useReducer 替换 useState。它你的状态。所以这里没有任何意义。

case 'ADD_INCOME_ITEM': //want to set the incomes object here
    return setIncomes(incomes.concat(budgetObj)); // not sure if this is correct??

代码中的那五行useState 包括incomessetIncomes 将被完全删除,因此您不能在reducer 中使用它们。

看起来你的减速器的initialState 只是一个预算对象。它需要是一个代表整个组件状态的对象。像这样的:

const initialBudget = 
  description: '',
  type: '+',
  value: '',
;

const initialState = 
  incomes: [],
  expenses: [],
  budgetObj: initialBudget,
;

我正在单独定义initialBudget,以便我们可以使用它轻松地重置budgetObj

您的 reducer 通过获取 stateaction 并返回下一个状态来处理操作,如下所示:

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;
  

【讨论】:

感谢您的详细回答,现在对我来说更有意义了!虽然我仍然感到困惑的是我将如何使用我的自定义挂钩 useSemiPersistentState 来获取收入和支出?因为我确实想将收入和支出存储在本地存储中。 制作一个名为 useSemiPersistantReducer 的副本,并将 useState 替换为 useReducer。而不是 [value, setValue] 使用 [value, dispatch]。效果仍将取决于 [value]。

以上是关于React 用 useReducer 替换 useState,同时还使用自定义钩子的主要内容,如果未能解决你的问题,请参考以下文章

[React] When to useReducer instead of useState

useReducer 和 useState

在 React 中使用 useReducer 钩子过滤列表?

[react] 请说说什么是useReducer?

React中useReducer的理解与使用

React中useReducer的理解与使用