在开发 sidebar 的 stack 结构时遇到一个问题,希望按照路由结构构建栈结构,但是得到的栈结构却是相反的。原因是React 的 useEffect 执行顺序是“自下而上”,即先执行子组件的 useEffect,再执行父组件的 useEffect。
代码结构如下:
// 路由结构
{
path: "test",
exact: true,
element: <TestLayout />,
children: [
{
path: "",
element: <TestMain />,
},
{
path: "settings",
element: <TestSettingsSidebar />,
children: [
{
path: "",
element: <TestSettings />,
},
],
},
],
},
TestLayout 页面
export const TestLayout = () => {
useSidebar(() => {
return (
<div key="test-layout-sidebar">
<a href="/test/settings">Go Setting</a>
</div>
);
}, []);
return <Outlet />;
};
TestSettingsSidebar
export const TestSettingsSidebar = () => {
useSidebar(() => {
return (
<div key="test-settings-sidebar">
<a href="/test">Back</a>
</div>
);
}, []);
return <Outlet />;
};
useSidebar Hook
export const useSidebar = (getContent: () => ReactNode | null, dependencies: DependencyList) => {
const [layout, setLayout] = useRecoilState(layoutAtom);
useEffect(() => {
const content = getContent();
if (content) {
setLayout((prev) => update(prev, { sidebarContentStack: { $push: [content] } }));
}
return () => {
setLayout((prev) => {
const newStack = prev.sidebarContentStack.slice(0, -1);
return {
...prev,
sidebarContentStack: newStack,
};
});
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setLayout, layout.isSidebarExpanded, ...dependencies]);
};
根据上述代码,当我访问路由 /test/setting 时,由于React Router 渲染嵌套路由,父组件先挂载,子组件后挂载。
1. 渲染顺序是:
TestLayout(父,useSidebar 执行,push TestLayoutSidebar)TestSettingsSidebar(子,useSidebar 执行,push TestSettingsSidebar)
2. stack 的 push 顺序
每个 useSidebar 都会把 sidebar 内容 push 到 stack 的末尾(栈顶):
- 先 push TestLayoutSidebar,stack: [TestLayoutSidebar]
- 再 push TestSettingsSidebar,stack: [TestLayoutSidebar, TestSettingsSidebar]
3. 为什么看到的是 [TestSettingsSidebar, TestLayoutSidebar]?
原因分析:React 的 useEffect 执行顺序
React 的 useEffect 执行顺序是“自下而上”,即先执行子组件的 useEffect,再执行父组件的 useEffect。
-
详细流程
- 访问 /test/settings/,React Router 渲染顺序:
- 先渲染父组件 TestLayout,再渲染子组件 TestSettingsSidebar。
- 但所有组件渲染完毕后,React 会先执行子组件的 useEffect,再执行父组件的 useEffect。
- 所以:
- TestSettingsSidebar 的 useSidebar 先执行,push 它的 sidebar 到 stack。
- TestLayout 的 useSidebar 后执行,再 push 它的 sidebar 到 stack。
- 访问 /test/settings/,React Router 渲染顺序:
-
结果
- 在断点看到的第一个 push,就是 TestSettingsSidebar 的 sidebar。
- 第二个 push,才是 TestLayout 的 sidebar。
- 所以 stack 顺序是 [TestSettingsSidebar, TestLayoutSidebar]。