为什么要重新渲染太多?

Why getting too many re-renders?

我有一个计算器组件,用于计算给定底数的指数。 我的代码如下:

//Exponential Calculator (Power/Squre-Root/Cube-Root)
  const Exponents=()=>{
    const [result,setResult]=useState(null);
    const [choice,setChoice]=useState("Power");
    const [x,setX]=useState(null);
    const [n,setN]=useState(null);
    useEffect(()=>{

    },[choice,x,n,result]);
    const calcResult=()=>{
      let res=1;
      if(choice=="Power")
      {
        for(var i=1;i<=n;i++)
        res*=x;
      }
      else if(choice=="SquareRoot")
      res=Math.sqrt(x);
      else
      res=Math.cbrt(x);

      setResult(res);
    }
    const handleChange=(e)=>{
      reset();
      setChoice(e.target.value);
    }
    function reset(){
      setResult(null);
      setX(null);
      setN(null);
    }
    const choiceData=()=>{
      if(choice==="Power"){
        return {
          name:"Power",
          quantities:["Base","Exponent"],
          disabled:false
        }
      }
      else if(choice==="SquareRoot")
      {
        setN(0.50);
        return{
          name:"Square-Root",
          quantities:["Base","Exponent"],
          disabled:true
        }
      }
      else if(choice==="CubeRoot")
      { 
        setN(0.34);
        return{
          name:"Cube-Root",
          quantities:["Base","Exponent"],
          disabled:true
        }
      }
    }
    return(
      <>
      <Form>
      <Form.Group className="mb-4" controlId="choice">
            <Form.Label>Select the type of calculation</Form.Label>
            <Form.Control
              as="select"
              className="select-custom-res"
              onChange={(e) => handleChange(e)}
            > 
              <option value="Power">Power</option>
              <option value="SquareRoot">Square Root</option>
              <option value="CubeRoot">Cube Root</option>
            </Form.Control>
          </Form.Group>
          <Form.Group className="mb-4" controlId="text">
            <Form.Text className="text">
              <strong>
                To find the {choiceData().name}, Enter the following values
              </strong>
              <br />
            </Form.Text>
          </Form.Group>
          <Form.Group className="mb-4">
            <Form.Label>{choiceData().quantities[0]}</Form.Label>
            <Form.Control
              onChange={(e) => setX(e.target.value)}
              type="number"
              placeholder={"Enter the Base"}
              value={x === null ? "" : x}
            />
          </Form.Group>
          <Form.Group className="mb-4">
            <Form.Label>{choiceData().quantities[1]}</Form.Label>
            <Form.Control
              onChange={(e) => setN(e.target.value)}
              type="number"
              placeholder={"Enter the Exponent"}
              value={n === null ? "" : n}
              disabled={choiceData().disabled}
            />
          </Form.Group>
          <Form.Group className="mb-4">
            <Form.Control
              readOnly
              type="number"
              placeholder={result === null ? "Result" : result + " "}
            />
          </Form.Group>
      </Form>
        <div className="button-custom-grp">
          <Button variant="primary" onClick={calcResult}>
            Calculate
          </Button>
          &nbsp;&nbsp;&nbsp;
          <Button variant="dark" onClick={() => reset()} type="reset">
            Reset
          </Button>
        </div>
      </>
    )
  }

choicePower 更改为 SquareRoot 时出现错误:Too many rerenders. 有趣的是,当我删除 choiceData 中的 setState 行时功能,错误消失。虽然我已经使用 useEffect 来防止重新渲染,但它不起作用。

问题

您在渲染 return 中调用 choiceData,它应该没有 side-effects,例如排队状态更新。当您在 choiceData 函数中调用 setN 时,它会触发重新渲染,它会再次调用 choiceData,这会触发重新渲染...重复广告令人作呕。

解决方案

我建议将 choiceData 转换为状态块,并使用 useEffect 挂钩更新它,并根据 choice 状态更新 n 状态。在渲染 return 中,不是调用函数来获取值,即 choiceData().quantities[0],而是访问新 choiceData 状态的 属性,即 choiceData.quantities[0].

const Exponents = () => {
  const [result, setResult] = useState(null);
  const [choice, setChoice] = useState("Power");
  const [choiceData, setChoiceData] = useState({});
  const [x, setX] = useState(null);
  const [n, setN] = useState(null);

  useEffect(() => {
    if (choice === "Power") {
      setChoiceData({
        name: "Power",
        quantities: ["Base", "Exponent"],
        disabled: false
      });
    } else if (choice === "SquareRoot") {
      setN(0.5);
      setChoiceData({
        name: "Square-Root",
        quantities: ["Base", "Exponent"],
        disabled: true
      });
    } else if (choice === "CubeRoot") {
      setN(0.34);
      setChoiceData({
        name: "Cube-Root",
        quantities: ["Base", "Exponent"],
        disabled: true
      });
    }
  }, [choice]);

  useEffect(() => {
    // is this effect used for anything?
  }, [choice, x, n, result]);

  const calcResult = () => {
    let res = 1;
    if (choice == "Power") {
      for (var i = 1; i <= n; i++) res *= x;
    } else if (choice == "SquareRoot") res = Math.sqrt(x);
    else res = Math.cbrt(x);

    setResult(res);
  };

  const handleChange = (e) => {
    reset();
    setChoice(e.target.value);
  };

  function reset() {
    setResult(null);
    setX(null);
    setN(null);
  }

  return (
    <>
      <Form>
        <Form.Group className="mb-4" controlId="choice">
          <Form.Label>Select the type of calculation</Form.Label>
          <Form.Control
            as="select"
            className="select-custom-res"
            onChange={(e) => handleChange(e)}
          >
            <option value="Power">Power</option>
            <option value="SquareRoot">Square Root</option>
            <option value="CubeRoot">Cube Root</option>
          </Form.Control>
        </Form.Group>
        <Form.Group className="mb-4" controlId="text">
          <Form.Text className="text">
            <strong>
              To find the {choiceData.name}, Enter the following values
            </strong>
            <br />
          </Form.Text>
        </Form.Group>
        <Form.Group className="mb-4">
          <Form.Label>{choiceData.quantities[0]}</Form.Label>
          <Form.Control
            onChange={(e) => setX(e.target.value)}
            type="number"
            placeholder={"Enter the Base"}
            value={x === null ? "" : x}
          />
        </Form.Group>
        <Form.Group className="mb-4">
          <Form.Label>{choiceData.quantities[1]}</Form.Label>
          <Form.Control
            onChange={(e) => setN(e.target.value)}
            type="number"
            placeholder={"Enter the Exponent"}
            value={n === null ? "" : n}
            disabled={choiceData.disabled}
          />
        </Form.Group>
        <Form.Group className="mb-4">
          <Form.Control
            readOnly
            type="number"
            placeholder={result === null ? "Result" : result + " "}
          />
        </Form.Group>
      </Form>
      <div className="button-custom-grp">
        <Button variant="primary" onClick={calcResult}>
          Calculate
        </Button>
        &nbsp;&nbsp;&nbsp;
        <Button variant="dark" onClick={() => reset()} type="reset">
          Reset
        </Button>
      </div>
    </>
  );
};