功能组件内部的状态初始化(无无限循环)

时间:2020-01-03 17:18:19

标签: javascript reactjs react-hooks

我有一个功能组件,可将自定义视口值保持在其状态,因此它必须使用事件侦听器并测量窗口大小:

const AppWrap = () => {

  // custom vw and vh vars
  const [vw, setvw] = useState();
  const [vh, setvh] = useState();

  // gets the inner height/width to act as viewport dimensions (cross-platform benefits)
  const setViewportVars = () => {

    const viewportWidth = window.innerWidth;
    const viewportHeight = window.innerHeight;

    // can be accessed in scss as vw(n), vh(n) OR in css as --vw * n, --vh * n
    document.documentElement.style.setProperty('--vw', `${viewportWidth / 100}px`);
    document.documentElement.style.setProperty('--vh', `${viewportHeight / 100}px`);

    // can be accessed in child components as vw * n or vh * n
    setvw(viewportWidth / 100);
    setvh(viewportHeight / 100);

  }

  // I'd like to run this function *once* when the component is initialized
  setViewportVars();

  // add listeners
  window.addEventListener('resize', setViewportVars);
  window.addEventListener('orientationchange', setViewportVars);
  window.addEventListener('fullscreenchange', setViewportVars);

  return (
    <App vw={vw} vh={vh}/>
  );

}

上面的代码产生错误:Too many re-renders. React limits the number of renders to prevent an infinite loop.

我可以将setViewportVars()包装在useEffect中,但是我不明白为什么这样做是必要的。我对功能组件的理解是,它们只能在return语句之外运行一次代码,并且只有JSX才能在状态更改时重新呈现。

4 个答案:

答案 0 :(得分:2)

您必须使用useEffect并将空数组作为依赖项进行传递,因此将仅像componentDidMount一样执行一次:

useEffect(() => {
  setViewportVars();

  // add listeners
  window.addEventListener('resize', setViewportVars);
  window.addEventListener('orientationchange', setViewportVars);
  window.addEventListener('fullscreenchange', setViewportVars);
}, []);

答案 1 :(得分:1)

为什么的答案,您需要使用Effect()来防止无限重渲染:

<AppWrap>的状态为{vw}{vh}。触发<AppWrap>时,setViewportVars()立即运行并更新该状态。因为您更新了状态,所以setViewportVars()然后再次被触发(以与反应一种方式反应,即数据流更新{vw/vh}的状态并导致AppWrap的重新触发。 ..这会导致setViewportVars()的重新触发。在这里,我们绝对不允许DOM被浏览器绘制,我们只是在重复以下循环:

init component > getHeight/Width > updateState > re-render component > getHeight/Width > ...

useEffect的行为与常规渲染不同。 useEffect仅在浏览器绘制DOM之后触发 。这意味着第一个周期将结束(init component > browser paints DOM > useEffect(getHeight/Width) > |if state aka viewsize changed?| > re-render

有关更多信息,请访问useEffect

上的Dan Abramov博客。

答案 2 :(得分:0)

所以在您的情况下,基本上是您调用了将更新状态的函数,因此再次加载组件将再次调用函数,因此基本上会进入无限循环

解决方案

您可以useEffect,因此在useEffect中,如果您传递的第二个参数是一个空数组,它将像componentDidMount一样仅调用一次

useEffect(() => {
  setViewportVars()
}, [])

因此,如果您传递第二个参数

  1. 不传递任何内容,例如useEffect(() => {})-每次都会调用。

  2. 传递一个空数组useEffect(() => {}, [])-它会调用一次。

  3. 传递数组deps,只要数组依赖项发生更改,它将在usEffect中执行代码块。

    useEffect(() => { // some logic }, [user])

答案 3 :(得分:0)

const AppWrap = () => {

    // custom vw and vh vars
    const [vw, setvw] = useState();
    const [vh, setvh] = useState();

    // gets the inner height/width to act as viewport dimensions (cross-platform benefits)
    const setViewportVars = useCallback(() => {

        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;

        // can be accessed in scss as vw(n), vh(n) OR in css as --vw * n, --vh * n
        document.documentElement.style.setProperty('--vw', `${viewportWidth / 100}px`);
        document.documentElement.style.setProperty('--vh', `${viewportHeight / 100}px`);

        // can be accessed in child components as vw * n or vh * n
        setvw(viewportWidth / 100);
        setvh(viewportHeight / 100);

    }, []);



    useEffect(() => {
        window.addEventListener('resize', setViewportVars);
        window.addEventListener('orientationchange', setViewportVars);
        window.addEventListener('fullscreenchange', setViewportVars);
        return () => {
            window.removeEventListener('resize', setViewportVars);
            window.removeEventListener('orientationchange', setViewportVars);
            window.removeEventListener('fullscreenchange', setViewportVars);
        }
    }, []);

    useEffect(() => {
        // I'd like to run this function *once* when the component is initialized
        setViewportVars();
    }, []);


    return (
        <App vw={vw} vh={vh} />
    );

}