Реагируйте на ловушку useCallback, чтобы избежать нескольких рендеров - PullRequest
1 голос
/ 29 сентября 2019

Я пытаюсь создать простой Материал пользовательского интерфейса Stepper , чтобы позволить пользователю нажимать Далее и Назад , а также на шаге, но это вызываетдва раза.

Я прочитал где-то , что решение этой проблемы - ловушка useCallback или useMemo, которая избегает реализации функции более одного раза и возвращает функцию или результат, только еслиэто меняется.

Моя проблема в том, что все еще ясный пример, я не уверен, как применить это к моему коду. Я собирался использовать простое управление состоянием, которое прекрасно работает. Но я хотел бы узнать это ...

Это моя 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>
  );
}

Edit intelligent-wu-geq3d

У меня естьпопробовал этот код, но он все еще не работает, так как он по-прежнему перерисовывается при вызове 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>
  );
}

1 Ответ

0 голосов
/ 29 сентября 2019

Вот решение, использующее useReducer: CodeSandbox

Я не уверен в точных причинах, почему ваш компонент был повторно визуализирован дважды, но сочетание useState и useReducer а также побочные эффекты в редукторе показались мне неправильными, поэтому я решил переписать его, используя только useReducer. Возможно, он был обработан дважды из-за dispatchActiveStep и setCompleted, так как они оба запускают повторную визуализацию.

Редактирование: на самом деле причина повторной визуализации состоит в том, что вы определяете редуктор внутри компонента, и онвоссоздается каждый раз: useReducer Действие отправляется дважды

...