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



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

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 会在状态更改时重新呈现。

你必须使用useEffect并将空数组作为依赖项传递,所以这只会被超过一次,就像componentDidMount

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

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

溶液

您可以使用Effect,因此在useEffect中,如果您将第二个参数(数组为空(传递给空,则只会像componentDidMount一样调用一次

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

所以如果你传递第二个参数

  1. 什么都不传递,就像useEffect(() => {})一样 - 它每次都会调用。

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

  3. 传递数组 deps,每当数组依赖关系发生变化时,它都会在 usImpact 中执行代码块。

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

为什么需要使用 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(

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

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} />
);
}

相关内容

  • 没有找到相关文章

最新更新