在功能组件内进行状态初始化(避免无限循环)

huangapple go评论84阅读模式
英文:

State initialization inside a functional component (without infinite looping)

问题

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

const AppWrap = () => {

  // 自定义vw和vh变量
  const [vw, setvw] = useState();
  const [vh, setvh] = useState();

  // 获取内部高度/宽度以充当视口尺寸(跨平台好处)
  const setViewportVars = () => {

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

    // 可以在scss中作为vw(n),vh(n)访问,也可以在css中作为--vw * n,--vh * n访问
    document.documentElement.style.setProperty('--vw', `${viewportWidth / 100}px`);
    document.documentElement.style.setProperty('--vh', `${viewportHeight / 100}px`);

    // 子组件中可以作为vw * n或vh * n访问
    setvw(viewportWidth / 100);
    setvh(viewportHeight / 100);

  }

  // 我希望在组件初始化时仅运行此函数一次
  useEffect(() => {
    setViewportVars();
  }, []);

  // 添加监听器
  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 中,但我不明白为什么这是必要的。我对功能性组件的理解是,它们只在返回语句之外运行一次代码,只有在状态更改时 JSX 才会重新渲染。

英文:

I have a functional component that holds custom viewport values in its state, so it must use event listeners and measure the window size:

const AppWrap = () =&gt; {

  // 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 = () =&gt; {

    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(&#39;--vw&#39;, `${viewportWidth / 100}px`);
    document.documentElement.style.setProperty(&#39;--vh&#39;, `${viewportHeight / 100}px`);

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

  }

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

  // add listeners
  window.addEventListener(&#39;resize&#39;, setViewportVars);
  window.addEventListener(&#39;orientationchange&#39;, setViewportVars);
  window.addEventListener(&#39;fullscreenchange&#39;, setViewportVars);

  return (
    &lt;App vw={vw} vh={vh}/&gt;
  );

}

The above code produces an error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

I can wrap setViewportVars() in useEffect, but I don't see why this is necessary. My understanding of functional components is that they only run code outside of the return statement once, and that only the JSX would re-render on a state change.

答案1

得分: 3

你需要使用 useEffect 并传递空数组作为依赖项,这样它只会执行一次,就像 componentDidMount 一样:

useEffect(() => {
  setViewportVars();

  // 添加监听器
  window.addEventListener('resize', setViewportVars);
  window.addEventListener('orientationchange', setViewportVars);
  window.addEventListener('fullscreenchange', setViewportVars);
}, []);
英文:

You have to use useEffect and pass empty array as dependencies, so this will only be excecuted once just like componentDidMount:

useEffect(() =&gt; {
  setViewportVars();

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

答案2

得分: 1

在你的情况下,基本上是这样的:你调用函数,它会更新状态,所以组件会再次加载,函数会再次调用,因此基本上会陷入无限循环。

解决方法

你可以使用 useEffect,在 useEffect 中,如果传递一个空数组作为第二个参数,它将只被调用一次,就像 componentDidMount 一样。

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

所以,如果传递第二个参数:

  1. 什么都不传递,像 useEffect(() => {}) - 它会每次都被调用。
  2. 传递一个空数组 useEffect(() => {}, []) - 它只会被调用一次。
  3. 传递包含依赖项的数组,每当依赖项数组发生变化时,它将执行 useEffect 内部的代码块。
useEffect(() => {
  // 一些逻辑
}, [user])
英文:

So in your case what happens is basically you call the function it will update the state, so again component will load again function will call so basically that goes to infinite loop

Solution

you can useEffect, so in useEffect if you pass the second argument which is an array as empty it will called only one time like the componentDidMount

useEffect(() =&gt; {
  setViewportVars()
}, [])

So if you pass second argument

  1. Passing nothing, like useEffect(() =&gt; {}) - it will call every time.

  2. Passing an empty array useEffect(() =&gt; {}, []) - it will call one time.

  3. Passing array deps, whenever the array dependencies changes it will execute the code block inside the usEffect.

    useEffect(() =&gt; {
    // some logic
    }, [user])

答案3

得分: 1

为了防止无限重新渲染,需要使用useEffect()的原因是:

<AppWrap>具有状态{vw}{vh}。当<AppWrap>被触发时,setViewportVars()立即运行并更新该状态。因为您更新了状态,然后再次触发了setViewportVars()(为了与React的单向数据流保持一致,更新了{vw/vh}的状态,导致重新触发AppWrap...从而导致重新触发setViewportVars())。在这里的任何时候,我们都没有允许浏览器绘制DOM,我们只是不断重复以下循环:

初始化组件 > 获取高度/宽度 > 更新状态 > 重新渲染组件 > 获取高度/宽度 > ...

useEffect的行为与常规渲染不同。useEffect仅在浏览器绘制了DOM后才触发。这意味着第一个循环会完成(初始化组件 > 浏览器绘制DOM > useEffect(getHeight/Width) > |如果状态即视图大小发生了变化?| > 重新渲染

欲了解更多信息,请查看Dan Abramov的关于useEffect的博客。

英文:

The answer to why you need to useEffect() to prevent the infinite re-render:

&lt;AppWrap&gt; has state {vw} and {vh}. When &lt;AppWrap&gt;is fired, setViewportVars() immediately runs and updates that state. Because you updated the state, setViewportVars() is then fired again (to keep in line with the react one way data flow which updates the state of {vw/vh} and causes a re-firing of AppWrap ...which causes a re-firing of setViewportVars(). At no point here have we allowed the DOM to get painted by the browser, we are just repeating the loop of:

init component &gt; getHeight/Width &gt; updateState &gt; re-render component &gt; getHeight/Width &gt; ...

useEffect behaves differently than a regular render. useEffect fires only after a the DOM has been painted by the browser. Which means that the first cycle would finish (init component &gt; browser paints DOM &gt; useEffect(getHeight/Width) &gt; |if state aka viewsize changed?| &gt; re-render)

For more info, check out Dan Abramov's blog on useEffect

答案4

得分: 0

const AppWrap = () => {

    // 自定义 vw 和 vh 变量
    const [vw, setvw] = useState();
    const [vh, setvh] = useState();

    // 获取内部高度/宽度以充当视口尺寸(跨平台受益)
    const setViewportVars = useCallback(() => {

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

        // 可以在 scss 中作为 vw(n)、vh(n) 访问,或在 css 中作为 --vw * n、--vh * n 访问
        document.documentElement.style.setProperty('--vw', `${viewportWidth / 100}px`);
        document.documentElement.style.setProperty('--vh', `${viewportHeight / 100}px`);

        // 可以在子组件中作为 vw * n 或 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(() => {
        // 我想在组件初始化时运行此函数 *一次*
        setViewportVars();
    }, []);

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

}
英文:
const AppWrap = () =&gt; {

    // 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(() =&gt; {

        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(&#39;--vw&#39;, `${viewportWidth / 100}px`);
        document.documentElement.style.setProperty(&#39;--vh&#39;, `${viewportHeight / 100}px`);

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

    }, []);



    useEffect(() =&gt; {
        window.addEventListener(&#39;resize&#39;, setViewportVars);
        window.addEventListener(&#39;orientationchange&#39;, setViewportVars);
        window.addEventListener(&#39;fullscreenchange&#39;, setViewportVars);
        return () =&gt; {
            window.removeEventListener(&#39;resize&#39;, setViewportVars);
            window.removeEventListener(&#39;orientationchange&#39;, setViewportVars);
            window.removeEventListener(&#39;fullscreenchange&#39;, setViewportVars);
        }
    }, []);

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


    return (
        &lt;App vw={vw} vh={vh} /&gt;
    );

}

</details>



huangapple
  • 本文由 发表于 2020年1月4日 01:18:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/59582707.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定