ค้นหาเว็บไซต์

typescript - React router V6: วิธีการมีช่องทางการนำทางสองช่องที่ทำงานพร้อมกันและเป็นอิสระ


ฉันกำลังสร้างเว็บไซต์ที่มี 2 ส่วน (เรียกว่าซ้ายและขวา) และในแต่ละส่วนมีแท็บ

บางอย่างเช่นนี้:

สิ่งที่ฉันต้องการคือ URL แบบนี้:

"https://mywebsite?right=billing&left=stock"

โดยที่ 'left=' และ 'right=' ถูกใช้เพื่อหาคอนเทนต์ที่จะนำมาแสดง

โครงสร้างแอปแบบง่าย

ฉันมีคอมโพเนนต์ Split ซึ่งมีคอมโพเนนต์ลูก PanelLeft และ PanelRight:

แอป.tsx

<Split
  sizes={60, 40}
  direction="horizontal"
>
  <PanelLeft />
  <PanelRight />
</Split>

const PanelLeft = () => {
  const leftPart: string = extractQueryFromUrl('left') // give me 'stock'
  return <Outlet context={{ leftPart }} />;
};

const PanelRight= () => {
  const rightPart: string = extractQueryFromUrl('right') // give me 'billing'
  return <Outlet context={{ rightPart }} />;
};

main.tsx

const router = createBrowserRouter([
  {
    path: "/",
    element: <App/>,
    children: [
      {
        path: "",
        element: <PanelLeft />,
        children: generateChildRoutes(),
      },
      {
        path: "",
        element: <PanelRight />,
        children: generateChildRoutes(),
      },
    ],
  },
]);

const generateChildRoutes = () => [
  { path: "/", element: <Home /> },
  { path: "stock", element: <Stock /> },
  { path: "billing", element: <Billing /> },
];

createRoot(document.getElementById("root")!).render(
  <RouterProvider router={router}>
  </RouterProvider>
);

อย่างชัดเจน การแมปใน createBrowserRouter และ <Outlet context={{leftPart}}/> ไม่ทำงานเลย

ฉันสงสัยว่าฉันจะกำหนดค่าพวกมันอย่างไรให้สามารถมีแผงนำทางสองอันที่เป็นอิสระได้

หมายเหตุ: แอปจริงซับซ้อนกว่านี้มาก ใช้ redux เพื่อดึงสถานะตาม tabId ที่ไม่ซ้ำกัน ฯลฯ มันทำงานได้ดีมากกับ Outlet หนึ่งตัว และตัวอย่างเช่น สองเส้นทางย่อย (https://mywebsite/panelright/billing และ https://mywebsite/panelleft/billing) ดังนั้นปัญหาจริง ๆ คือการแสดงผลพร้อมกันอย่างอิสระ

แก้ไข (22/09/2024)

ขอขอบคุณทั้ง @Drew Reese และ @Matthew สำหรับเวลาของคุณและคำอธิบายของคุณ คุณพูดถูกที่การมีหลาย Outlet ไม่มีความหมายและจะไม่มีทางทำงานตามที่ตั้งใจไว้ สุดท้ายฉันได้แนวทางแก้ไขที่ใกล้กับคำแนะนำของ Matthew โดยมี outlet หนึ่งและใช้ตรง ๆ บนอันอื่น

อะไรประมาณนี้

export const tabsRoutesMap: Map<string, JSX.Element> = new Map([
    [Billing_Path, <Billing />],
    [Stock_Path, <Stock/>],
//... others possible tabs...
]);

const router = createBrowserRouter([
    {
        path: "/",
        element: <App/>,
        // generate children from tabs route
        children: Array.from(tabsRoutesMap, ([path, element]) => ({ path, element })),
    
    },
]);

แล้วก็ :

<>
    {tabsRoutesMap.get(extractedPathFromUrl)}
</>

คำตอบ #1

ปัญหาคือคุณมีเบราว์เซอร์และมีเพียงเส้นทาง URL หนึ่งเท่านั้นที่จะจับคู่และซิงโครไนซ์เส้นทาง ดังนั้นจึงไม่สมเหตุสมผลที่จะมี Outlet มากกว่าหนึ่งตัวในระดับความลึกใด ๆ เพราะมีเพียงเส้นทางเดียวที่สามารถจับคู่และแสดงผลได้

ถ้าคุณต้องการ/จำเป็นต้องมีเราเตอร์/ปลั๊กไฟ ฯลฯ แยกกัน คุณก็มีโอกาสสูงมากที่จะต้องใช้วิธีแก้ปัญหาแบบลงมือทำเอง/ด้วยมือ

นี่คือตัวอย่าง Proof of Concept ที่ใช้สองคอมโพเนนต์ MemoryRouter ที่เป็นพี่น้องกัน หนึ่งอันในแต่ละ "แผง" ซึ่งอ่าน window.location.search ปัจจุบันเพื่อซิงโครไนซ์ตัว router ของแต่ละแผงกับค่าของ แผงนั้น

ตัวอย่าง :

import { useNavigate } from "react-router-dom";

const PanelLayout = ({ panel }) => {
  const navigate = useNavigate();

  // Get current "global" URL search params
  const searchParams = new URLSearchParams(window.location.search);

  // Get current panel's search value
  const target = searchParams.get(panel) ?? "";
  
  // Synchronize the panel's MemoryRouter to current target panel path
  useEffect(() => {
    navigate(`/${target}`, { replace: true });
  }, [target]);

  return (
    <>
      ...
      <Outlet />
      ...
    </>
  );
};
const generateChildRoutes = (panel) => [
  {
    element: <PanelLayout panel={panel} />,
    children: [
      { path: "/", element: <Home /> },
      { path: "stock", element: <Stock /> },
      { path: "billing", element: <Billing /> },
    ],
  },
];
const leftRouter = useMemo(
  () => createMemoryRouter(generateChildRoutes("left")),
  []
);
const rightRouter = useMemo(
  () => createMemoryRouter(generateChildRoutes("right")),
  []
);

...

<Split
  sizes={ 60, 40 }
  direction="horizontal"
>
  <PanelLeft>
    <RouterProvider router={leftRouter} />
  </PanelLeft>

  <PanelRight>
    <RouterProvider router={rightRouter} />
  </PanelRight>
</Split>

หลัก - คุณไม่สามารถซ้อนตัวจัดเส้นทางได้ ดังนั้น BrowserRouter รากจะถูกลบออก

createRoot(document.getElementById("root")!).render(
  <App />
);

เดโม

ซ้ายขวาตัวอย่าง
บ้านบ้าน

หุ้นบ้าน

หุ้นการเรียกเก็บเงิน

บ้านการเรียกเก็บเงิน

คำตอบ #2

ถ้าคุณต้องการสร้างมันด้วย createBrowserRouter คุณสามารถทำได้โดยใช้ outlet เป็นคอมโพเนนต์ด้านขวาและคอมโพเนนต์ด้านซ้ายเป็นปกติ

const Layout = ({ Component }) => (
  <div style={{ display: "flex" }}>
    <div className="box">
      <h2>Left Section</h2>
      <Component />
    </div>
    <div className="box">
      <h2>Right Section</h2>
      <Outlet context="right" />
    </div>
  </div>
);

const children = () => {
  return [
    { path: "1", element: <Component1 /> },
    { path: "2", element: <Component2 /> },
    { path: "3", element: <Component3 /> },
  ];
};

const router = createBrowserRouter([
  {
    path: "/",
    element: <RoutesExample />,
    children: [
      {
        path: "1",
        element: <Layout Component={Component1} />,
        children: children(),
      },
      {
        path: "2",
        element: <Layout Component={Component2} />,
        children: children(),
      },
      {
        path: "3",
        element: <Layout Component={Component3} />,
        children: children(),
      },
    ],
  },
]);

เดโม

และถ้าคุณต้องการใช้พารามิเตอร์ URL นี่คือวิธีแก้ปัญหาง่ายๆ

const Table = () => {
  const [searchParams, setSearchParams] = useSearchParams();
  const left = searchParams.get("left");
  const right = searchParams.get("right");
  ComponentLeft = componentList[left];
  ComponentRight = componentList[right];
  return left && right ? (
    <div style={{ display: "flex" }}>
      <div className="box">
        <h2>Left Section</h2>
        <ComponentLeft />
      </div>
      <div className="box">
        <h2>Right Section</h2>
        <ComponentRight />
      </div>
    </div>
  ) : (
    <div></div>
  );
};

ที่ไหน

const Component1 = () => {
  return <div>1</div>;
};
const Component2 = () => {
  return <div>2</div>;
};
const Component3 = () => {
  return <div>3</div>;
};

const componentList = {
  1: Component1,
  2: Component2,
  3: Component3,
};

เดโม

บทความที่เกี่ยวข้อง