最好不要认为效果会在组件生命周期的特定时间点运行。虽然这是真的,但可以帮助您更好地掌握钩子的模型是,依赖数组是效果 同步 的事物列表:也就是说,每次都应该运行效果那些事情会改变。
当你得到一个 linter 错误表明你的依赖数组缺少 props 时,linter 试图告诉你的是你的效果(或回调,或记忆函数)依赖于不稳定的值。它这样做是因为通常这是一个错误。考虑以下几点:
function C({ onSignedOut }) {
const onSubmit = React.useCallback(() => {
const response = await fetch('/api/session', { method: 'DELETE' })
if (response.ok) {
onSignedOut()
}
}, [])
return <form onSubmit={onSubmit}>
<button type="submit">Sign Out</button>
</form>
}
linter 将对onSubmit 中的依赖数组发出警告,因为onSubmit 依赖于onSignedOut 的值。如果您保留此代码原样,则onSubmit 将只创建一次,第一个值为onSignedOut。如果 onSignedOut 属性发生变化,onSubmit 将不会反映这种变化,您最终会得到对 onSignedOut 的过时引用。这在此处得到了最好的证明:
import { render } from "@testing-library/react"
it("should respond to onSignedOut changes correctly", () => {
const onSignedOut1 = () => console.log("Hello, 1!")
const onSignedOut2 = () => console.log("Hello, 2!")
const { getByText, rerender } = render(<C onSignedOut={onSignedOut1} />)
getByText("Sign Out").click()
// stdout: Hello, 1!
rerender(<C onSignedOut={onSignedOut2} />)
getByText("Sign Out").click()
// stdout: Hello, 1!
})
console.log() 语句不会更新。对于这个可能会违反您作为组件使用者的期望的特定示例。
现在让我们看看你的代码。
如您所见,此警告实质上是在说明您的代码可能没有按照您的想法执行。如果您确定自己知道自己在做什么,那么消除警告的最简单方法是禁用该特定行的警告。
useEffect(() => {
setButtonColor(context.objects.getActiveObject()[props.type]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); //on mount
正确的做法是将依赖项放在数组中。
const { type } = props
useEffect(() => {
setButtonColor(context.objects.getActiveObject()[type]);
}, [context, type]);
但是,每次type 更改时,这都会更改按钮颜色。这里有一点需要注意:您正在设置状态以响应道具的变化。这称为派生状态。
您只希望在初始安装时设置该状态。由于您只想在初始安装时设置它,您可以简单地将您的值传递给React.useState(initialState),这将完全实现您想要的:
function C({ type }) {
const initialColor = context.objects.getActiveObject()[type];
const [color, setButtonColor] = React.useState(initialColor);
...
}
这仍然会留下一个问题,即当您更改道具时,消费者可能会对为什么视图会更新感到困惑。 在功能组件起飞之前很常见的约定(我仍然使用的一个)是使用 initial 一词为未监视更改的道具添加前缀:
function C({ initialType }) {
const initialColor = context.objects.getActiveObject()[initialType];
const [color, setButtonColor] = React.useState(initialColor);
}
您仍然应该在这里小心:这确实意味着,在C 的生命周期内,它只会从context 或initialType 读取一次。如果上下文的值发生变化怎么办?您最终可能会在<C /> 中得到陈旧的数据。您可能可以接受,但值得一提。
React.useRef() 确实是一个很好的解决方案,可以通过仅捕获初始版本来稳定值,但对于这个用例来说不是必需的。