React 的 useEffect 执行顺序

122 阅读1分钟

在开发 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。
  • 结果

    • 在断点看到的第一个 push,就是 TestSettingsSidebar 的 sidebar。
    • 第二个 push,才是 TestLayout 的 sidebar。
    • 所以 stack 顺序是 [TestSettingsSidebar, TestLayoutSidebar]。