【问题标题】:React hook useCallback to avoid multiple rendersReact hook useCallback 避免多次渲染
【发布时间】:2019-09-29 18:04:08
【问题描述】:

我正在尝试构建一个简单的Material UI Stepper 以允许用户单击NextBack,并且也在步骤中,但它会触发reducer 两次。

我已阅读 somewhere ,解决此问题的方法是 useCallbackuseMemo 钩子,它可以避免函数多次实例化,仅在函数发生更改时才返回函数或结果。

我的问题是仍然是一个清晰的例子,我不确定如何将它应用到我的代码中。我正要使用简单的状态管理,效果很好。但是我想学这个……

这是我的App 函数:

function App() {
  const [completed, setCompleted] = React.useState({});
  const [activeStep, dispatchActiveStep] = React.useReducer((step, action) => {
    let completedSteps = completed;
    let active = step;
    switch (action.type) {
      case "next":
        if (step < steps.length) {
          completedSteps[activeStep] = true;
          active = step + 1;
        }
        break;
      case "previous":
        if (step > 0) {
          delete completed[activeStep];
          active = step - 1;
        }
        break;
      case "set":
        if (!(action.step in Object.keys(completed))) {
          console.error("step not completed");
          return step;
        }
        if (action.step === 0) {
          completedSteps = {};
          active = 0;
        } else if (action.step === steps.length - 1) {
          completedSteps = {};
          for (let i = 0; i <= action.step; i++) {
            completedSteps[i] = true;
          }
          active = action.step;
        }
        break;
      default:
        console.error("action not available");
    }
    console.log("test");
    setCompleted(completedSteps);
    return active;
  }, 0);

  return (
    <Paper>
      <Stepper activeStep={activeStep}>
        {steps.map((step, i) => (
          <Step key={i}>
            <StepButton
              key={i}
              completed={completed[i]}
              onClick={() => dispatchActiveStep({ type: "set", step: i })}
            >
              <Typography>{step.label}</Typography>
            </StepButton>
          </Step>
        ))}
      </Stepper>
      {steps.map((step, i) => {
        if (activeStep === i) {
          return (
            <div key={i} style={styles.content}>
              {step.component}
            </div>
          );
        }
      })}
      <div style={styles.buttons}>
        <Button
          color="primary"
          variant="contained"
          onClick={() => dispatchActiveStep({ type: "previous" })}
          disabled={activeStep === 0}
        >
          Previous
        </Button>
        <Button
          color="secondary"
          variant="contained"
          style={{ marginLeft: "10px" }}
          onClick={() => dispatchActiveStep({ type: "next" })}
          disabled={activeStep === steps.length - 1}
        >
          Next
        </Button>
      </div>
    </Paper>
  );
}

我已经尝试过这段代码,但仍然无法正常工作,因为在调用 dispatchActiveStep() 时它仍然会重新渲染:

function App() {
  const [completed, setCompleted] = React.useState({});
  const [activeStep, setActiveStep] = React.useState(0);

  const handleBack = () => {
    let completedSteps = completed;
    if (activeStep === steps.length - 1) {
      delete completedSteps[activeStep - 1];
    } else {
      delete completedSteps[activeStep];
    }
    setCompleted(completedSteps);
    setActiveStep(activeStep - 1);
  };

  const handleNext = () => {
    let completedSteps = completed;
    completedSteps[activeStep] = true;
    setCompleted(completedSteps);
    setActiveStep(activeStep + 1);
  };

  const handleClick = step => {
    let completedSteps = completed;
    if (!(step in Object.keys(completedSteps))) {
      console.error("step not completed");
      return;
    }
    completedSteps = {};
    for (let i = 0; i < step; i++) {
      completedSteps[i] = true;
    }
    setActiveStep(step);
    setCompleted(completedSteps);
  };

  return (
    <Paper>
      <Stepper activeStep={activeStep}>
        {steps.map((step, i) => (
          <Step key={i}>
            <StepButton
              key={i}
              completed={completed[i]}
              onClick={() => {
                handleClick(i);
              }}
            >
              <Typography>{step.label}</Typography>
            </StepButton>
          </Step>
        ))}
      </Stepper>
      {steps.map((step, i) => {
        if (activeStep === i) {
          return (
            <div key={i} style={styles.content}>
              {step.component}
            </div>
          );
        }
      })}
      <div style={styles.buttons}>
        <Button
          color="primary"
          variant="contained"
          onClick={handleBack}
          disabled={activeStep === 0}
        >
          Previous
        </Button>
        <Button
          color="secondary"
          variant="contained"
          style={{ marginLeft: "10px" }}
          onClick={handleNext}
          disabled={activeStep === steps.length - 1}
        >
          Next
        </Button>
      </div>
    </Paper>
  );
}

【问题讨论】:

  • 为什么需要减速器?你完全可以用第二个 useState 和一些函数处理程序来做到这一点
  • @mikheevm,我以前使用过 state,但是,使用 reducer 来处理这样的事情不是一个好习惯吗?
  • 对于两个状态变量?我不会这么说的。我希望useReducer 可以在具有一些复杂逻辑或大量变量的组件中使用。在您的情况下,您只需要一个已完成步骤的数组和当前步骤的整数。
  • 好了,我回去用简单的状态管理。我仍然想学习如何使用useCallbackuseReducer 钩子。

标签: javascript reactjs react-hooks memoization


【解决方案1】:

这里是使用 useReducer 的解决方案:CodeSandbox

我不确定你的组件被重新渲染两次的确切原因,但 useStateuseReducer 的混合以及减速器中的副作用对我来说似乎是错误的,所以我决定使用重写它仅限useReducer。可能是因为dispatchActiveStepsetCompleted 而被渲染了两次,因为它们都触发了重新渲染。

编辑:实际上重新渲染的原因是您在组件内部定义了一个减速器,并且每次都会重新创建它:useReducer Action dispatched twice

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-09
  • 1970-01-01
  • 2020-05-09
  • 1970-01-01
  • 2023-03-08
  • 2018-12-21
  • 2021-08-04
相关资源
最近更新 更多