You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React简易计算器中使用useContext管理全局状态?

React计算器使用useContext的问题解答

1. 是否可以将所有useState状态移入Context?

完全可以。你可以把所有核心状态(value、prevValue、operator、display)以及对应的更新函数(setValue、setPrevValue等)全部放到Context中,这样组件树里任意层级的组件都能直接访问和修改这些状态,无需通过props层层传递。

2. 代码中哪些部分更适合用useContext?

  • 核心状态与状态更新逻辑:所有和计算器业务相关的状态(当前输入值、上一次计算值、运算符、显示内容),以及修改这些状态的函数(number、clear、invertSign、handleOperator、handleEqual)都应该移入Context。不管是显示输入框还是按钮组件,都能直接从Context获取所需的状态和方法,避免props drilling。
  • 按钮点击处理入口:handleButtonClick函数也适合放进Context,因为它是触发所有状态修改的统一入口,按钮组件只需要调用这个函数即可,不用关心内部逻辑细节。

3. 具体改造步骤示例

第一步:创建Context与Provider组件

新建CalculatorContext.tsx文件,封装状态与逻辑:

import { createContext, useContext, useState, useCallback, useEffect, ReactNode } from "react";

type CalculatorState = {
  value: string;
  prevValue: string;
  operator: string;
  display: string;
  handleButtonClick: (buttonValue: string) => void;
};

const CalculatorContext = createContext<CalculatorState | undefined>(undefined);

export function CalculatorProvider({ children }: { children: ReactNode }) {
  const [value, setValue] = useState("0");
  const [prevValue, setPrevValue] = useState("");
  const [operator, setOperator] = useState("");
  const [display, setDisplay] = useState("0");

  const number = useCallback((buttonValue: string) => {
    if (prevValue !== "" && operator === "") {
      setValue(buttonValue);
      setPrevValue("");
    } else {
      setValue(value === "0" ? buttonValue : value + buttonValue);
    }
  }, [prevValue, operator, value]);

  const clear = useCallback(() => {
    setValue("0");
    setPrevValue("");
    setOperator("");
  }, []);

  const invertSign = useCallback(() => {
    if (prevValue !== "" && value === "") {
      setValue((-1 * parseInt(prevValue)).toString());
      setPrevValue("");
    } else {
      setValue((-1 * parseInt(value)).toString());
    }
  }, [prevValue, value]);

  const handleEqual = useCallback(() => {
    let numPrevValue = parseFloat(prevValue);
    let numValue = parseFloat(value);
    let equal = 0;
    if (operator === "+") {
      equal = numPrevValue + numValue;
    } else if (operator === "-") {
      equal = numPrevValue - numValue;
    } else if (operator === "x") {
      equal = numPrevValue * numValue;
    } else if (operator === "/") {
      equal = numPrevValue / numValue;
    } else if (operator === "") {
      return;
    }

    setPrevValue(equal.toString());
    setOperator("");
    setValue("");
  }, [prevValue, value, operator]);

  const handleOperator = useCallback((buttonValue: string) => {
    setOperator(buttonValue);
    if (prevValue !== "") {
      handleEqual();
    } else {
      setPrevValue(value);
      setValue(" ");
    }
  }, [prevValue, value, handleEqual]);

  const handleButtonClick = useCallback(
    (buttonValue: string) => {
      const utility = parseInt(buttonValue, 10);
      if (!isNaN(utility)) {
        number(buttonValue);
      } else {
        switch (buttonValue) {
          case "C":
            clear();
            break;
          case "+-":
            invertSign();
            break;
          case "%":
            setValue((parseInt(value) / 100).toString());
            break;
          case "=":
            handleEqual();
            break;
          default:
            handleOperator(buttonValue);
            break;
        }
      }
    },
    [value, number, clear, invertSign, handleEqual, handleOperator]
  );

  useEffect(() => {
    setDisplay(value);
  }, [value]);

  return (
    <CalculatorContext.Provider
      value={{
        value,
        prevValue,
        operator,
        display,
        handleButtonClick,
      }}
    >
      {children}
    </CalculatorContext.Provider>
  );
}

export function useCalculator() {
  const context = useContext(CalculatorContext);
  if (!context) {
    throw new Error("useCalculator必须在CalculatorProvider内部使用");
  }
  return context;
}

第二步:改造App组件

简化App组件,仅负责UI渲染,通过钩子获取Context中的状态与方法:

import { useCalculator } from "./CalculatorContext";
import ButtonStyle from "./component/ButtonStyle";

export default function App() {
  const { display, prevValue, handleButtonClick } = useCalculator();

  return (
    <div className="flex items-center justify-center h-screen w-screen bg-yellow-300">
      <div className="flex flex-col justify-center items-center border-2 border-black bg-gray-800 rounded-md gap-1 p-1">
        <input
          type="text"
          value={display ? display : prevValue}
          disabled
          className="bg-gray-700 h-14 rounded-md w-52 text-right text-white text-4xl"
        />

        <div className="grid grid-cols-4 grid-rows-5 gap-1 w-52">
          {[
            ["C", "+-", "%", "/"],
            ["7", "8", "9", "x"],
            ["4", "5", "6", "-"],
            ["1", "2", "3", "+"],
            ["0", ".", "="],
          ].map((row, rowindex) =>
            row.map((button, buttonindex) => (
              <ButtonStyle
                key={`button-${rowindex}-${buttonindex}`}
                text={button}
                onClick={() => handleButtonClick(button)}
              />
            ))
          )}
        </div>
      </div>
    </div>
  );
}

第三步:在入口文件包裹Provider

在main.tsx中用Provider包裹App组件,确保所有子组件能访问Context:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { CalculatorProvider } from './CalculatorContext';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <CalculatorProvider>
      <App />
    </CalculatorProvider>
  </React.StrictMode>,
);

注意事项

  • 所有需要使用计算器状态或方法的组件,都要通过useCalculator钩子获取,且必须在CalculatorProvider的范围内。
  • 把状态和逻辑集中到Context后,App组件只负责UI渲染,核心业务逻辑统一维护,后续扩展或修改会更便捷。

内容的提问来源于stack exchange,提问作者Wildcard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 15:57:05