简体   繁体   English

React:useEffect “悖论”与 eventHandler 道具

[英]React: useEffect “paradox” with eventHandler props

Problem with this example . 这个例子有问题。

I've got an Input component.我有一个Input组件。 This input component has an onChange prop to handle any data changes from inside .这个输入组件有一个onChange属性来处理内部的任何数据更改。

As it should be, if I update the value from outside , the onChange prop is not fired, since nothing changed from inside the input component.应该是,如果我从外部更新value ,则不会触发onChange道具,因为输入组件内部没有任何更改。 If you start typing the onChange prop is fired and I can update the state from outside as well.如果你开始输入onChange道具就会被触发,我也可以从外部更新状态。

But if some condition is met (in my example just a boolean state), I want to prefill the input field from inside, therefore I'll have to call an onChange event so that the App can update the state from outside.但是如果满足某些条件(在我的示例中只是一个布尔状态),我想从内部预填充输入字段,因此我必须调用onChange事件,以便应用程序可以从外部更新状态。

The problem/paradox is that the useEffect checking for a condition now needs the onChange prop as a dependency, and since this prop is a method, it will change on every render.问题/悖论是useEffect检查条件现在需要onChange道具作为依赖项,并且由于此道具是一种方法,因此它会在每次渲染时更改。 This means now the input element always resets to the value from the useEffect when anything is changed because the useEffect is triggered again.这意味着现在输入元素总是在任何更改时重置为 useEffect 的值,因为 useEffect 再次触发。

My problem would not be there if I was allowed to remove the onChange prop from the useEffect dependency, but this is of course against the rules of the useEffect hook.如果允许我从 useEffect 依赖项中删除onChange属性,我的问题就不存在了,但这当然违反了 useEffect 钩子的规则。

The only "solutions" that came to my mind:我想到的唯一“解决方案”:

  • I could move the condition outside of the Input element into the App component.我可以将 Input 元素之外的条件移动到 App 组件中。 This makes sense for my example, but in the actual scenario the should be the same every time the component is used, and I don't want to repeat the condition every time from outside since the condition is also not just a boolean in the real scenario of course.这对我的示例来说是有道理的,但在实际场景中,每次使用组件时都应该相同,而且我不想每次从外部重复该条件,因为条件也不仅仅是真实的布尔值场景当然。
  • I could add an additional state, whether the condition has already be checked and fire the useEffect only if it has not been triggered yet.我可以添加一个额外的状态,条件是否已经被检查,并且只有在它尚未被触发时才触发 useEffect。 But this get really complicated really fast, if the condition needs to get checked again at some point, etc. etc.但这会很快变得非常复杂,如果条件需要在某个时候再次检查,等等。

I've come across this problem in different shapes scenarios, so I really hope to get an answer that can be applied to similar scenarios in general.我在不同的形状场景中遇到过这个问题,所以我真的希望得到一个可以应用于一般类似场景的答案。 Maybe I just have an anti-pattern in my code of how I work with event handlers?也许我的代码中有一个关于如何使用事件处理程序的反模式?

Thank you in advance for all answers ^^.预先感谢您的所有答案^^。

I would change the if statement to only run if prefillCondition has changed.我会将 if 语句更改为仅在prefillCondition已更改时运行。 That means you'll need to know what the value was on the last render.这意味着您需要知道上次渲染的值是多少。 Doing that inline would look like:这样做内联看起来像:

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]);

I find it common to want to remember a value from the previous render.我发现想要记住上一个渲染中的值是很常见的。 If you do too, you may want to consider extracting that logic to a custom hook:如果您也这样做,您可能需要考虑将该逻辑提取到自定义钩子中:

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]);

Consider this考虑这个

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

a == b // false

Even though the function has the same code it has a different memory reference so to React it always appears as a new (changed) prop, that's why it calls re-render and since you always pass new anonymous onChange callback it calls useEffect each time.即使该函数具有相同的代码,它也具有不同的内存引用,因此对于 React 而言,它始终显示为一个新的(已更改的)道具,这就是它调用重新渲染的原因,并且由于您总是传递新的匿名 onChange 回调,因此它每次都会调用 useEffect。

So you could either omit it from the dependencies array (note the ESlint rule)所以你可以从依赖项数组中省略它(注意 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

or useReact.useCallback() function which solves exactly your problem and prevents creating a new function on every render.或使用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>
  );
};

你不能只在输入上设置一个默认值

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM