0

我有一个组件应该从组件中读取一个属性(它是字符串“fill”或字符串“stroke”)并从对象中提取相应的键来读取它的上下文。

一旦选择了一个对象,就会安装它,访问活动对象并将其颜色作为填充颜色或描边颜色拉出。

useEffect(() => {
    setButtonColor(context.objects.getActiveObject()[props.type]);
}, []); //on mount

像这样安装它:

<ColorPicker type="fill" />
<ColorPicker type="stroke" />

这应该只在安装时运行一次。我认为当 dep 数组为空时,它会在任何情况下运行一次。

那么如何使用道具和上下文在安装时运行一些东西呢?

当我希望它总是在挂载时只运行一次时,为什么它需要一个依赖项,无论如何?

4

2 回答 2

2

最好不要认为效果会在组件生命周期的某些点运行。虽然这是真的,但可以帮助您更好地掌握钩子的模型是,依赖数组是效果与之同步的事物的列表:也就是说,每次这些事物发生变化时都应该运行效果。

当你得到一个 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. 如果onSignedOutprop 发生更改,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);
  ...
}

这仍然会留下一个问题,即当您更改道具时,消费者可能会对为什么视图会更新感到困惑。在功能组件起飞之前(并且我仍然使用的)常见的约定是在没有被监控的 props 前面加上 word 的变化initial

function C({ initialType }) {
  const initialColor = context.objects.getActiveObject()[initialType];
  const [color, setButtonColor] = React.useState(initialColor);
}

但是,您仍然应该在这里小心:这确实意味着,在 的生命周期内C,它只会读取一次contextinitialType一次。如果上下文的值发生变化怎么办?您最终可能会在<C />. 这可能是你可以接受的,但值得一提。


React.useRef()通过仅捕获初始版本来稳定值确实是一个很好的解决方案,但对于这个用例来说不是必需的。

于 2020-02-05T13:58:20.177 回答
0

这是我解决此问题的方法:

将颜色设置为变量,然后使用该变量在组件安装时设置按钮颜色。

const oldColor = useRef(context.objects.getActiveObject()[props.type]);

useEffect(() => {
    setButtonColor(oldColor.current);
}, []); //on mount

useRef 返回一个可变 ref 对象,其 .current 属性初始化为传递的参数 (initialValue)。返回的对象将在组件的整个生命周期内持续存在。

于 2020-02-05T13:38:20.013 回答