连续修改两个不同的状态是否会引起多次渲染

46 阅读1分钟

需求

在公司的脚手架上应用主题,流程是

graph TD
获取主题索引 --> 从索引中获取主题文件位置 --> 获取指定主题

所以我写出了这样的代码

const ThemeContext = createContext<Theme|null>(null)
export function ThemeContextProvider(props:{children:ReactNode}){
	const [themeMap,setThemeMap]=useState<ThemeMap>()
	const [themeObj,setThemeObj]=useState<any>()
	useEffect(()=>{
		getThemeMap()
		.then(async res=>{
			const newThemeMap = res.data
			const newThemeObj = await getThemeObj(newThemeMap)
			setThemeMap(newThemeMap)
			setThemeObj(newThemeObj)
		})
	},[])
	const contextValue = useMemo(()=>{
		return {themeMap,themeObj}
	},[themeMap,themeObj])
	return (
		<ThemeContext.Provider value={contextValue}>
			{props.children}
		</ThemeContext.Provider>
	)
}

export function useTheme(){
	const theme = useContext(ThemeContext)
	return theme
}

目前看上去非常合理

问题

直到我开始写一个主题选择器

const ThemeSelect = memo((props) => {
	const { themeMap, themeObj } = useTheme()
	const options = useMemo(() => {
		return getOptions(themeMap)
	}, [themeMap])
	if (!themeMap) return null
	return <Select defaultValue={themeObj.themeName} options={options} onSelect={props.onChange}></Select>
})

系统的设计是url也可以提供主题,主题索引可能没有相应的主题,这种情况下不应当应用主题,themeObj保持空值,因此这里只检测了themeMap。后来调试的时候发现这个Select的默认值总是空。

解决

直到我在这个组件中输出themeMap和themeObj,才发现这个组件一共渲染了三次。第一次输出两个undefined,第二次themeMap有值,themeObj是undefined,第三次两个都有值。
这只是一个脚手架,所以问题肯定出在设置主题索引和对象上。但是为什么呢?据我(当时)所知,setState是异步运行的,合并多个状态更新统一执行。
查阅资料时看到这篇文章react17+18 中 setState是同步还是异步更新,我明白了原因所在:react18之前,setState仅在react可调度范围内是异步执行的,而then和setTimeout这种范围外的是同步执行的,总是为每次更新进行完整更新流程。而我的公司用的脚手架就属于“之前”。解决方法也很简单,把两个状态合并即可。