我有一个功能组件,可将自定义视口值保持在其状态,因此它必须使用事件侦听器并测量窗口大小:
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才能在状态更改时重新呈现。
答案 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()
}, [])
因此,如果您传递第二个参数
不传递任何内容,例如useEffect(() => {})
-每次都会调用。
传递一个空数组useEffect(() => {}, [])
-它会调用一次。
传递数组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} />
);
}