如果你在撰写React专案时,有试着在第一次渲染後,透过useEffect
以state修改绑定给元件的资料,应该会发现一个特殊的现象:
嗯?为甚麽我的元件会闪一下?
以下方的程序为例
import React, {useState,useMemo,useEffect} from 'react';
import MenuItem from '../component/MenuItem';
import Menu from '../component/Menu';
import { OpenContext } from '../context/ControlContext';
let menuItemWording = new Array(20);
menuItemWording.fill("没有东西");
const MenuPage = () =>{
const [isOpen, setIsOpen] = useState(true);
const [menuItemData, setMenuItemData] = useState(menuItemWording);
let menuItemArr = useMemo(()=>menuItemData.map((wording) => <MenuItem text={wording} key={wording}/>),[menuItemData]);
useEffect(()=>{
setMenuItemData([
"Like的发问",
"Like的回答",
"Like的文章",
"Like的留言"
]);
},[])
return (
<OpenContext.Provider value={{
openContext: isOpen,
setOpenContext: setIsOpen
}} >
<Menu title={"Andy Chang的like"}>
{menuItemArr}
</Menu>
<button onClick={()=>{
let menuDataCopy = ["测试资料"].concat(menuItemData);
setMenuItemData(menuDataCopy);
}}>更改第一个menuItem</button>
</OpenContext.Provider>
);
}
export default MenuPage;
你会得到下列的结果:
这是为什麽呢?
这是因为我们先前说过,useEffect
的执行时间点是这样:
也就是在第一次渲染时,React还没有拿到你在useEffect
中修改的state
,所以画面上显示的会是你一开始拿来当初始值的资料。这也是我们的画面会闪一下的原因。
(2020/10/11) 但如果你是从铁人赛一开始就在跟我文章的人,我当初这里有不小心写错。现在已经修正了。虽然这种人应该不多就是(?)
虽然这种需求很少,但React提供了一个解决上述问题的hook-useLayoutEffect
。它和useEffect
的语法、使用上一模一样。唯一的差别是useLayoutEffect
被提升到了渲染画面前、更新DOM後执行。
useLayoutEffect
的执行时间点是这样:
接下来你可以试着把刚刚的useEffect
换成useLayoutEffect
。
import React, {useState,useMemo,useLayoutEffect} from 'react';
import MenuItem from '../component/MenuItem';
import Menu from '../component/Menu';
import { OpenContext } from '../context/ControlContext';
let menuItemWording = new Array(20);
menuItemWording.fill("没有东西");
const MenuPage = () =>{
const [isOpen, setIsOpen] = useState(true);
const [menuItemData, setMenuItemData] = useState(menuItemWording);
let menuItemArr = useMemo(()=>menuItemData.map((wording) => <MenuItem text={wording} key={wording}/>),[menuItemData]);
useLayoutEffect(()=>{
setMenuItemData([
"Like的发问",
"Like的回答",
"Like的文章",
"Like的留言"
]);
},[])
return (
<OpenContext.Provider value={{
openContext: isOpen,
setOpenContext: setIsOpen
}} >
<Menu title={"Andy Chang的like"}>
{menuItemArr}
</Menu>
<button onClick={()=>{
let menuDataCopy = ["测试资料"].concat(menuItemData);
setMenuItemData(menuDataCopy);
}}>更改第一个menuItem</button>
</OpenContext.Provider>
);
}
export default MenuPage;
你会发现画面不再会闪过一次初始的资料了。这是因为React在第一次渲染画面前已经执行了useLayoutEffect
中的setState
。
然而必须要注意的事情是,useLayoutEffect
本身是一个同步函式,也就是说UI会等useLayoutEffect
中做的事情结束才会渲染。所以不要在useLayoutEffect
做太多事情,否则使用者看到UI的间隔会拉长,导致UX变差。
这件事衍伸的问题是,当你要在React做SSR时,因为
useLayoutEffect
和usetEffect
都不会在server-side执行,有需要useLayoutEffect
的元件就可能会以不符你的预期的方式运作。
useEffect
都应该能够解决你的问题。如果你是从class component切换过来的人,实质上useLayoutEffect的执行时机点才是真正等於componentDidMount和componentDidUpdate。 但使用上官方还是希望你使用useEffect。
参考资料:
React Hooks 一些纪录
官方文件
经历了前两次的失败,决定还是第一天不要直接写文章! 换个心情,先写了参赛宣言。好好的展开一个挑战的开...
1.前言 今天来讲讲函式(不是韩式料理的韩式),而是Coding时会用到的程序方法(你到底在讲啥?)...
经过昨天的一番折腾,我想读者们都对基本的图片优化稍有概念了,今天要介绍的优化技巧其实严格来说也算是...
前言 这是之前 Huli 大在前端社团分享的 国外 XSS 挑战。 最近比较有时间来分享,当时 「从...
大家好,我想介绍一下自己为什麽会认识spring boot,因为写後端API的时候会用到的框架 然後...