反应:带有事件处理程序道具的useEffect“悖论”

时间:2020-09-11 11:54:45

标签: javascript reactjs react-hooks use-effect

this example的问题。

我有一个Input组件。此输入组件具有onChange道具,可以处理内部中的所有数据更改。

应该如此,如果我从外部更新了value,则不会触发onChange道具,因为输入组件内部没有任何变化。如果您开始输入onChange道具,就会触发,我也可以从外部更新状态。

但是,如果满足某些条件(在我的示例中只是布尔状态),我想从内部预填充输入字段,因此,我必须调用onChange事件,以便App可以更新从外部状态。

问题/矛盾之处在于,useEffect检查条件现在需要onChange道具作为依赖项,并且由于该道具是一种方法,因此它将在每次渲染时更改。这意味着现在,由于再次触发useEffect,因此发生任何更改时,输入元素始终会从useEffect重置为值。

如果允许我从useEffect依赖关系中删除onChange道具,我的问题就不会出现,但这当然违反了useEffect钩子的规则。

我想到的唯一“解决方案”:

  • 我可以将条件从Input元素之外移动到App组件中。这对于我的示例来说是有意义的,但是在实际情况下,每次使用组件时都应该相同,并且我不想每次都从外部重复该条件,因为该条件在实际中也不只是布尔值当然。
  • 我可以添加一个附加状态,以确定是否已经检查了条件,并且仅在尚未触发条件时才触发useEffect。但这很快就会变得非常复杂,如果需要在某些时候再次检查条件等,那么等等。

我已经在不同形状的场景中遇到了这个问题,所以我真的希望能得到一个可以普遍应用于类似场景的答案。也许我的代码中只是关于如何使用事件处理程序的反模式?

在此先感谢您提供所有答案^^。

3 个答案:

答案 0 :(得分:1)

我会将if语句更改为仅在prefillCondition更改后才运行。这意味着您需要知道上一次渲染的值。进行内联看起来像:

const [prefillCondition, setPrefillCondition] = React.useState(false);
const previous = useRef();
React.useEffect(() => {
  previous.current = prefillCondition;
})
React.useEffect(() => {
  // On the first render, previous.current will be undefined. On every render after that
  // it will tell you what value prefillCondition had on the previous render.
  if (prefillCondition && !previous.current) {
    onChange("value from useEeffff");
  }
}, [onChange, prefillCondition, previous]);

我发现想记住上一个渲染中的值是很常见的。如果您也这样做,则可能需要考虑将该逻辑提取到自定义钩子中:

const usePrevious = (value) => {
  const previous = useRef();
  React.useEffect(() => {
    previous.current = value;
  })
  return previous.current;
}

// used like:
const [prefillCondition, setPrefillCondition] = React.useState(false);
const prevPrefillCondition = usePrevious(prefillCondition);
React.useEffect(() => {
  if (prefillCondition && !prevPrefillCondition) {
    onChange("value from useEeffff");
  }
}, [onChange, prefillCondition, prevPrefillCondition]);

答案 1 :(得分:1)

考虑这个

const a = () => 'foo bar'
const b = () => 'foo bar'

a == b // false

即使函数具有相同的代码,但具有不同的内存引用,因此在React中,它始终显示为新的(更改的)prop,这就是为什么调用re-render的原因 而且由于您总是传递新的匿名onChange回调,因此每次都会调用useEffect。

因此您可以从依赖关系数组中忽略它(请注意ESlint规则)

React.useEffect(() => {
    // this would supposibly fetch some default value based on conditions and the call an onChange
    if (prefillCondition) {
      onChange("value from useEeffff");
    }
  }, [prefillCondition]); // eslint-disable-line react-hooks/exhaustive-deps

或使用React.useCallback()函数可以完全解决您的问题,并防止在每个渲染器上创建新函数。


const App = () => {
  const [myVal, setMyVal] = React.useState("");

  const handler = React.useCallback((a) => {
    console.log("onchange", a);
    setMyVal(a);
  }, []);

  return (
    <div>
      <Input value={myVal} onChange={handler} />
      <div>
        <button
          onClick={() => {
            setMyVal("smth");
          }}
        >
          set to smth from outside
        </button>
      </div>
    </div>
  );
};

答案 2 :(得分:0)

不能在输入中设置默认值

defaultValue={prefillCondition ? "text here" : ""}