【问题标题】:React: useEffect "paradox" with eventHandler props反应:useEffect “悖论”与 eventHandler 道具
【发布时间】:2020-12-29 22:47:17
【问题描述】:

this example 的问题。

我有一个Input 组件。这个输入组件有一个onChange 属性来处理内部的任何数据变化。

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

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

问题/悖论是 useEffect 检查条件现在需要 onChange 属性作为依赖项,并且由于此属性是一种方法,因此每次渲染都会更改。这意味着现在输入元素总是在发生任何更改时重置为 useEffect 的值,因为 useEffect 再次被触发。

如果允许我从 useEffect 依赖项中删除 onChange 属性,我的问题就不会存在,但这当然违反了 useEffect 挂钩的规则。

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

  • 我可以将 Input 元素之外的条件移到 App 组件中。这对我的示例有意义,但在实际场景中,每次使用组件时都应该相同,并且我不想每次都从外部重复条件,因为条件也不仅仅是真实的布尔值当然是场景。
  • 我可以添加一个额外的状态,判断条件是否已经被检查,并且只有当它还没有被触发时才触发 useEffect。但这会很快变得非常复杂,如果条件需要在某个时候再次检查等等等等。

我在不同形状的场景中遇到过这个问题,所以我真的希望得到一个可以应用于一般类似场景的答案。也许我的代码中有一个反模式,说明我如何使用事件处理程序?

提前感谢您的所有回答^^。

【问题讨论】:

    标签: javascript reactjs react-hooks use-effect


    【解决方案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]);
    

    【讨论】:

      【解决方案2】:

      考虑一下

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

      即使函数具有相同的代码,但它具有不同的内存引用,因此对于 React,它始终显示为新的(更改的)道具,这就是它调用重新渲染的原因 并且由于您总是传递新的匿名 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>
        );
      };
      

      【讨论】:

      • 这个解决方案有效,我也想过,每次使用组件时都需要从外部将事件处理程序包装在 useCallback 中,这并不是一个很好的模式。但是在很多情况下这已经足够了,这实际上是 React linting 已经给你的警告^^
      【解决方案3】:

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

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

      【讨论】:

      • "default" 可能是错误的术语。目标是根据您在应用程序中单击的内容,有可能满足条件并从内部更新输入值。看我的例子,只有按下按钮才能满足条件
      猜你喜欢
      • 2019-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多