繁体   English   中英

React:如何在第一次渲染时跳过 useEffect

[英]React: How to skip useEffect on first render

我正在尝试使用受控表单组件中的useEffect挂钩来在用户更改表单内容时通知父组件并返回表单内容的 DTO。 这是我目前的尝试

const useFormInput = initialValue => {
  const [value, setValue] = useState(initialValue)

  const onChange = ({target}) => {
    console.log("onChange")
    setValue(target.value)
  }

  return { value, setValue, binding: { value, onChange }}
}
useFormInput.propTypes = {
  initialValue: PropTypes.any
}

const DummyForm = ({dummy, onChange}) => {

  const {value: foo, binding: fooBinding} = useFormInput(dummy.value)
  const {value: bar, binding: barBinding} = useFormInput(dummy.value)

  // This should run only after the initial render when user edits inputs
  useEffect(() => {
    console.log("onChange callback")
    onChange({foo, bar})
  }, [foo, bar])

  return (
    <div>
      <input type="text" {...fooBinding} />
      <div>{foo}</div>

      <input type="text" {...barBinding} />
      <div>{bar}</div>
    </div>
  )
}


function App() {
  return (
    <div className="App">
      <header className="App-header">
        <DummyForm dummy={{value: "Initial"}} onChange={(dummy) => console.log(dummy)} />
      </header>
    </div>
  );
}

但是,现在效果是在第一次渲染时运行的,此时初始值是在挂载期间设置的。 我该如何避免呢?

以下是加载页面并随后编辑这两个字段的当前日志。 我也想知道为什么我会收到缺少依赖项的警告。

onChange callback
App.js:136 {foo: "Initial", bar: "Initial"}
backend.js:1 ./src/App.js
  Line 118:  React Hook useEffect has a missing dependency: 'onChange'. Either include it or remove the dependency array. If 'onChange' changes too often, find the parent component that defines it and wrap that definition in useCallback  react-hooks/exhaustive-deps
r @ backend.js:1
printWarnings @ webpackHotDevClient.js:120
handleWarnings @ webpackHotDevClient.js:125
push../node_modules/react-dev-utils/webpackHotDevClient.js.connection.onmessage @ webpackHotDevClient.js:190
push../node_modules/sockjs-client/lib/event/eventtarget.js.EventTarget.dispatchEvent @ eventtarget.js:56
(anonymous) @ main.js:282
push../node_modules/sockjs-client/lib/main.js.SockJS._transportMessage @ main.js:280
push../node_modules/sockjs-client/lib/event/emitter.js.EventEmitter.emit @ emitter.js:53
WebSocketTransport.ws.onmessage @ websocket.js:36
App.js:99 onChange
App.js:116 onChange callback
App.js:136 {foo: "Initial1", bar: "Initial"}
App.js:99 onChange
App.js:116 onChange callback
App.js:136 {foo: "Initial1", bar: "Initial2"}

您可以看到这个答案 ,了解如何忽略初始渲染。 此方法使用useRef来跟踪第一个渲染。

  const firstUpdate = useRef(true);
  useLayoutEffect(() => {
    if (firstUpdate.current) {
      firstUpdate.current = false;
    } else {
     // do things after first render
    }
  });

至于你得到的警告

React Hook useEffect缺少依赖:'onChange'

钩子调用中的尾随数组( useEffect(() => {}, [foo] )列出了钩子的依赖关系。这意味着如果你在钩子范围内使用一个变量,它可以根据对变化的变化而变化。组件(比如组件的属性)需要在那里列出。

我为此创建了一个简单的钩子

https://stackblitz.com/edit/react-skip-first-render?file=index.js

它基于paruchuri-p

const useSkipFirstRender = (fn, args) => {
  const isMounted = useRef(false);

  useEffect(() => {
    if (isMounted.current) {
      console.log('running')
      return fn();
    }
  }, args)

  useEffect(() => {
    isMounted.current = true
  }, [])
}

第一个效果是主要效果,就像您在组件中使用它一样。 它会运行,发现isMounted不是true ,只会跳过任何事情。

然后在运行底部useEffect之后,它会将isMounted更改为true - 因此当组件被强制重新渲染时。 它将允许第一个useEffect正常呈现。

它只是一个很好的自封装可重用钩子。 显然你可以更改名称,这取决于你。

如果您正在寻找像componentDidUpdate()这样的东西而不通过componentDidMount(),您可以编写一个类似于的挂钩:

export const useComponentDidMount = () => {
  const ref = useRef();
  useEffect(() => {
    ref.current = true;
  }, []);
  return ref.current;
};

在您的组件中,您可以使用它:

const isComponentMounted = useComponentDidMount();

useEffect(() => {
 if(isComponentMounted) {
  // Do something
 }
}, [someValue])

在你的情况下,它将是:

const DummyForm = ({dummy, onChange}) => {
  const isComponentMounted = useComponentDidMount();
  const {value: foo, binding: fooBinding} = useFormInput(dummy.value)
  const {value: bar, binding: barBinding} = useFormInput(dummy.value)

  // This should run only after the initial render when user edits inputs
  useEffect(() => {
    if(isComponentMounted) {
      console.log("onChange callback")
      onChange({foo, bar})
    }
  }, [foo, bar])

  return (
    // code
  )
}

如果有帮助,请告诉我。

我不明白为什么你首先需要一个useEffect 您的表单输入几乎肯定是受控输入组件 ,其中表单的当前值作为prop提供,表单只提供onChange处理程序。 表单的当前值应存储在<App> ,否则您将如何从应用程序中的其他位置访问表单的值?

const DummyForm = ({valueOne, updateOne, valueTwo, updateTwo}) => {
  return (
    <div>
      <input type="text" value={valueOne} onChange={updateOne} />
      <div>{valueOne}</div>

      <input type="text" value={valueTwo} onChange={updateTwo} />
      <div>{valueTwo}</div>
    </div>
  )
}


function App() {
  const [inputOne, setInputOne] = useState("");
  const [inputTwo, setInputTwo] = useState("");

  return (
    <div className="App">
      <header className="App-header">
        <DummyForm
          valueOne={inputOne}
          updateOne={(e) => {
            setInputOne(e.target.value);
          }}
          valueTwo={inputTwo}
          updateTwo={(e) => {
            setInputTwo(e.target.value);
          }}
        />
      </header>
    </div>
  );
}

更清洁,更简单,更灵活,使用标准的React模式,并且不需要使用useEffect

挂载后可以使用自定义钩子运行使用效果。

const useEffectAfterMount = (cb, dependencies) => {
  const mounted = useRef(true);

  useEffect(() => {
    if (!mounted.current) {
      return cb();
    }
    mounted.current = false;
  }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};

这是typescript版本:

const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
  const mounted = useRef(true);

  useEffect(() => {
    if (!mounted.current) {
      return cb();
    }
    mounted.current = false;
  }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};

例子:

useEffectAfterMount(() => {
  console.log("onChange callback")
  onChange({foo, bar})
}, [count])

暂无
暂无

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

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