render 在 useEffect 之前调用

mar*_*lle 5 javascript reactjs

我正在尝试使用 React 和 Leva js 创建一个简单的应用程序。

基本上有一个项目数组,每个项目都有一个名称和一个数字数组。Leva 面板包含两个选择,用户可以从项目数组中选择两个项目。

如果两个选定的项目具有相同的长度,则可以,否则应用程序应返回错误。

这里是主要代码,这里是一个工作演示

应用程序.jsx

export const App = () => {
  const [haveSameNumberOfValues, setHaveSameNumberOfValues] = useState(true);
  const [showResult, setShowResult] = useState(haveSameNumberOfValues);

  const { startValuesName, endValuesName } = useControls({
    startValuesName: {
      value: VALUES[0].name,
      options: VALUES.map((d) => d.name)
    },
    endValuesName: { value: VALUES[1].name, options: VALUES.map((d) => d.name) }
  });

  useEffect(() => {
    const startValuesItem = getValuesFromName(startValuesName);
    const endValuesItem = getValuesFromName(endValuesName);
    const startValues = startValuesItem.values;
    const endValues = endValuesItem.values;

    const values = [startValues, endValues];
    const valuesLenght = values.map((path) => path.length);
    const haveValuesTheSameNumberOfItems = valuesLenght.every(
      (pathLength) => pathLength === valuesLenght[0]
    );
    setHaveSameNumberOfValues(haveValuesTheSameNumberOfItems);
    setShowResult(haveValuesTheSameNumberOfItems);
  }, [startValuesName, endValuesName]);

  console.log("\n");
  console.log("haveSameNumberOfValues:", haveSameNumberOfValues);
  console.log("showResult:", showResult);

  return (
    <div className="w-screen h-screen flex flex-col justify-center items-center">
      {!haveSameNumberOfValues && <div>:( Error.</div>}

      {showResult && (
        <Result
          startValues={getValuesFromName(startValuesName)}
          endValues={getValuesFromName(endValuesName)}
        />
      )}
    </div>
  );
};
Run Code Online (Sandbox Code Playgroud)

结果.jsx

export const Result = ({ startValues, endValues }) => {
  console.log("startValues:", startValues.values.length);
  console.log("endValues:", endValues.values.length);

  return (
    <div className="border-4 border-green-400 px-5 py-3">
      <div>:)</div>
      <div>{startValues.name}</div>
      <div>{endValues.name}</div>
    </div>
  );
};
Run Code Online (Sandbox Code Playgroud)

数据.js

export const VALUES = [
  {
    name: "carrot (3)",
    values: [0, 4, 45]
  },
  {
    name: "apple (3)",
    values: [20, 20, 10]
  },
  {
    name: "salad (4)",
    values: [30, 0, 2, 1]
  },
  {
    name: "chicken (6)",
    values: [40, 1, 3, 20, 3, 1]
  }
];

export function getValuesFromName(name) {
  return VALUES.find((d) => d.name === name);
}
Run Code Online (Sandbox Code Playgroud)

问题是,当用户选择值长度不等于的两个项目(例如胡萝卜和鸡肉)时,代码设置showResult为 true,因此Result即使不应该渲染组件,也会渲染组件。您可以检查它阅读日志消息。我试图使用整个示例流程更好地解释自己。

  1. 刷新页面,选中的项目为carrot (3)apple (3)。这些值具有相同的长度,在控制台中您可以看到:
haveSameNumberOfValues:  true
showResult: true
startValues: 3
endValues: 3
Run Code Online (Sandbox Code Playgroud)

showResult为 true,因此Result组件被渲染。好的,可以了

  1. 用户选择chiken (6)endValuesName. 控制台打印:
haveSameNumberOfValues: true
showResult: true
startValues: 3
endValues: 6

haveSameNumberOfValues: false
showResult: false
Run Code Online (Sandbox Code Playgroud)

showResult第一次为 true,因此Result组件被渲染,然后它发生变化并变为 false。这很奇怪,因为我不想要那个,我想立即拥有showResult=false。这是因为在我的简单示例中,这不会造成大问题,但在我的实际应用程序中它会破坏应用程序。

我的代码有什么问题吗?

我重复一下我想要的:

用户使用 Leva 更改值 ->showResult应在调用之前第一次以正确的方式更新Result

Ahm*_*bai 4

render 在 useEffect 之前调用

这是 React 的本质,你无法改变它!了解有关使用useEffecthook 和 React 生命周期方法的更多信息。

我的代码有什么问题吗?

基本上什么都没有。你的代码很好,但是还可以更好


这里有一个简单的解释:

您应该记住的是,当您更新状态时,后者不会立即更新,而是当代码完成执行组件重新渲染时,并且只有当这种情况发生时,状态的值才会更新。
然后,当组件完成渲染时,如果有一个useEffect钩子,它将查看其依赖项数组并检查其中一个或多个值是否与上次渲染期间的值不同,以及它的情况是否useEffect运行(它将执行无论如何在第一次渲染期间)并且如果钩子内包含的代码useEffect正在更新状态,那么这将导致新的渲染,因此组件再次重​​新渲染,这就是您在控制台中得到以下输出的原因:

// component rerendered 
haveSameNumberOfValues: true
showResult: true
startValues: 3
endValues: 6
// useEffet is called (because endValues was 3 and become 6), and since inside useEffect you are updating the state (setShowResult(x) and setHaveSameNumberOfValues(y)) the component rerenders again
// new render
haveSameNumberOfValues: false
showResult: false
// <Result/> is not displayed because showResult is false
// useEffet is not called (because startValuesName and endValuesName are not updated)
Run Code Online (Sandbox Code Playgroud)

现在,话虽这么说,您明白了“问题”,您必须做的就是避免触发useEffect更新haveSameNumberOfValuesshowResult,没有必要,这也将避免这种额外的渲染。

由于useControlsLeva 与 React useStatehook “类似”,每次更新其值时,组件都会重新渲染,因此您将在这个新渲染中获得更新的值。然后,当涉及到 JSX 显示 ui 时,您调用此函数(它返回 或TRUEFALSE,并根据其结果决定显示<Result /><div>Error</div>

该函数包含与您相同的逻辑useEffect

function haveValuesTheSameNumberOfItems(first, second) {
  const startValuesItem = getValuesFromName(first);
  const endValuesItem = getValuesFromName(second);
  const startValues = startValuesItem.values;
  const endValues = endValuesItem.values;
  const values = [startValues, endValues];
  const valuesLenght = values.map((path) => path.length);
  return valuesLenght.every((pathLength) => pathLength === valuesLenght[0]);
}
Run Code Online (Sandbox Code Playgroud)

完整的工作示例:

import { useControls } from "leva";
const VALUES = [
  {
    name: "carrot (3)",
    values: [0, 4, 45],
  },
  {
    name: "apple (3)",
    values: [20, 20, 10],
  },
  {
    name: "salad (4)",
    values: [30, 0, 2, 1],
  },
  {
    name: "chicken (6)",
    values: [40, 1, 3, 20, 3, 1],
  },
];
const Result = ({ startValues, endValues }) => {
  console.log("startValues:", startValues.values.length);
  console.log("endValues:", endValues.values.length);

  return (
    <div className="border-4 border-green-400 px-5 py-3">
      <div>{startValues.name}</div>
      <div>{endValues.name}</div>
    </div>
  );
};

const App = () => {
  function getValuesFromName(name) {
    return VALUES.find((d) => d.name === name);
  }

  function haveValuesTheSameNumberOfItems(first, second) {
    const startValuesItem = getValuesFromName(first);
    const endValuesItem = getValuesFromName(second);
    const startValues = startValuesItem.values;
    const endValues = endValuesItem.values;
    const values = [startValues, endValues];
    const valuesLenght = values.map((path) => path.length);
    return valuesLenght.every((pathLength) => pathLength === valuesLenght[0]);
  }

  const { startValuesName, endValuesName } = useControls({
    startValuesName: {
      value: VALUES[0].name,
      options: VALUES.map((d) => d.name),
    },
    endValuesName: {
      value: VALUES[1].name,
      options: VALUES.map((d) => d.name),
    },
  });

  return (
    <div className="w-screen h-screen flex flex-col justify-center items-center">
      {haveValuesTheSameNumberOfItems(startValuesName, endValuesName) ? (
        <Result
          startValues={getValuesFromName(startValuesName)}
          endValues={getValuesFromName(endValuesName)}
        />
      ) : (
        <div>: Error.</div>
      )}
    </div>
  );
};
export default App;
Run Code Online (Sandbox Code Playgroud)

注意:你可以看看useMemo来保存函数结果的记忆版本,包括startValuesName和 ,endValuesName在它的依赖数组中,这样如果有另一个状态可以触发组件重新渲染,React 就不必在每次渲染时计算结果但仅当startValuesNameendValuesName更新时。