React hook useCallback 避免多次渲染
React hook useCallback to avoid multiple renders
我正在尝试构建一个简单的 Material UI Stepper 以允许用户点击 Next 和 Back,以及这一步,但它会触发 reducer 两次。
我读过 somewhere 解决这个问题的方法是 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>
);
}
我已经尝试过此代码,但仍然无法正常工作,因为它在调用 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>
);
}
下面是使用 useReducer 的解决方案:CodeSandbox
我不确定你的组件被重新渲染两次的确切原因,但 useState
和 useReducer
的混合以及减速器中的副作用对我来说似乎是错误的,所以我决定只使用 useReducer
重写它。可能是因为 dispatchActiveStep
和 setCompleted
而渲染了两次,因为它们都触发了重新渲染。
编辑:实际上重新渲染的原因是您在组件内部定义了一个减速器,并且每次都会重新创建它:
我正在尝试构建一个简单的 Material UI Stepper 以允许用户点击 Next 和 Back,以及这一步,但它会触发 reducer 两次。
我读过 somewhere 解决这个问题的方法是 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>
);
}
我已经尝试过此代码,但仍然无法正常工作,因为它在调用 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>
);
}
下面是使用 useReducer 的解决方案:CodeSandbox
我不确定你的组件被重新渲染两次的确切原因,但 useState
和 useReducer
的混合以及减速器中的副作用对我来说似乎是错误的,所以我决定只使用 useReducer
重写它。可能是因为 dispatchActiveStep
和 setCompleted
而渲染了两次,因为它们都触发了重新渲染。
编辑:实际上重新渲染的原因是您在组件内部定义了一个减速器,并且每次都会重新创建它: