原文:https://darios.blog/posts/using-callbacks-in-react
作者:Dario Djuric
译者:ChatGPT 4 Turbo
想象你有一个入职组件,它根据当前步骤显示 WelcomeStep
、TermsOfServiceStep
或 CompleteStep
。一个初学者开发者可能会这样实现:
export type Step = 'welcome' | 'terms-of-service' | 'complete';
export default function OnboardingSection() {
const [step, setStep] = useState<Step>('welcome');
return (
<Box>
{step === 'welcome' && (
<WelcomeStep setStep={setStep} />
)}
{step === 'terms-of-service' && (
<TermsOfServiceStep setStep={setStep} />
)}
{step === 'complete' && <CompleteStep />}
</Box>
);
}
WelcomeStep
组件有一个按钮,点击时,会更新状态为 welcome
。
export function WelcomeStep(props: { setStep: (newStep: Step) => void }) {
return (
<Box>
<Box>欢迎来到我们的平台!</Box>
<Box>
<Button onClick={() => props.setStep('terms-of-service')}>下一步</Button>
</Box>
</Box>
);
}
这种方法没有什么特别的问题,并且能够正确工作。然而,我们引入了 WelcomeStep
组件和 OnboardingSection
组件之间的紧密耦合。
为什么这是一个问题?
这种方法有几个问题。首先,这打破了父组件的封装,因为它现在将组件的内部状态泄露给了其子组件。暴露了设置器可能会使问题难以排查,因为你无法控制设置器的使用位置。一个子组件可能以父组件没有预期或控制的方式更新状态。
这也可能导致不必要的重新渲染,因为子组件现在控制着父组件的重新渲染。当只有父组件控制状态变化时,它对状态更新的时间有更多的控制。
最后,子组件现在更难测试,因为由于两者之间的高度耦合,它总是需要与父组件配对使用。
解决方案
解决方案是让子组件提供一个回调函数给父组件。虽然在这个示例中流程保持不变,但现在你使得子组件更易于测试和在不同的上下文中重用。让子组件暴露类似 onClickNext
的东西意味着它可以很容易地在 OnboardingSection
以外的组件中使用,使得组件更易于测试。
以标准的 input
元素为例。你在该元素的接口上看不到任何设置器函数。相反,有一个 value
属性和一个 onChange
属性。你向下传递值,并向上通知改变。这使得接口更简单,使得这个组件在非常不同的用例中可重用。
这是我们组件的修订版本:
export default function OnboardingSection() {
const [step, setStep] = useState<Step>('welcome');
return (
<Box>
{step === 'welcome' && (
<WelcomeStep onClickNext={() => setStep('terms-of-service')} />
)}
{step === 'terms-of-service' && (
<TermsOfServiceStep onClickNext={() => setStep('complete')} />
)}
{step === 'complete' && <CompleteStep />}
</Box>
);
}
我们现在在拥有设置器的组件中调用设置器,减少了该组件与子组件之间的耦合。这种方法的另一个好处是,如果你需要的话,现在你可以在 onClickNext
处理函数中拥有更复杂的逻辑。
在设计组件时,总是试图在设计它们的接口时多加一些努力。试着把组件孤立地看待,好像你不知道它们将如何或在哪里被使用。你会发现,你将开始设计出更简单、更易于使用和测试的组件。