是否可以在 React-Router V6 中的一个组件中使用多个插座

Is it possible to use multiple outlets in a component in React-Router V6

我在应用程序中使用 React Router v6。我有一个布局页面,它使用一个插座来显示主要内容。我还想包含一个标题部分,该部分会根据匹配的路径而变化,但我不确定该怎么做。

function MainContent() {
  return (
    <div>
      <div>{TITLE SHOULD GO HERE}</div>
      <div><Outlet /></div>
    </div>
  );
}

function MainApp() {
  return (
    <Router>
      <Routes>
        <Route path="/projects" element={<MainContent />} >
          <Route index element={<ProjectList />} title="Projects" />
          <Route path="create" element={<CreateProject />} title="Create Project" />
        </Route>
      <Routes/>
    </Router>
  );
}

这样的事情可能吗?理想情况下,我希望除了标题之外还有一些其他的道具,我可以通过这种方式控制,所以像这样的变化有一个好的组织系统会很棒。

最直接的方法是将 title 属性移动到 MainContent 布局包装器并单独包装每个路由,但您将丢失嵌套路由。

另一种方法是创建一个 React 上下文来保存 title 状态并使用包装器组件来设置标题。

const TitleContext = createContext({
  title: "",
  setTitle: () => {}
});

const useTitle = () => useContext(TitleContext);

const TitleProvider = ({ children }) => {
  const [title, setTitle] = useState("");
  return (
    <TitleContext.Provider value={{ title, setTitle }}>
      {children}
    </TitleContext.Provider>
  );
};

使用提供程序包装应用程序(或任何高于 Routes 组件 的祖先组件)。

<TitleProvider>
  <App />
</TitleProvider>

更新 MainContent 以访问 useTitle 挂钩以获取当前 title 值并呈现它。

function MainContent() {
  const { title } = useTitle();
  return (
    <div>
      <h1>{title}</h1>
      <div>
        <Outlet />
      </div>
    </div>
  );
}

TitleWrapper 组件。

const TitleWrapper = ({ children, title }) => {
  const { setTitle } = useTitle();

  useEffect(() => {
    setTitle(title);
  }, [setTitle, title]);

  return children;
};

并更新路由组件以包装在 TitleWrapper 组件中,在此处传递 title 属性。

<Route path="/projects" element={<MainContent />}>
  <Route
    index
    element={
      <TitleWrapper title="Projects">
        <ProjectList />
      </TitleWrapper>
    }
  />
  <Route
    path="create"
    element={
      <TitleWrapper title="Create Project">
        <CreateProject />
      </TitleWrapper>
    }
  />
</Route>

这样,MainContent可以被认为是UI一组路由共有的,而TitleWrapper你可以选择一个更合适的名字) 可以被认为是 UI 特定于一条路线。

更新

我忘记了 Outlet 组件提供自己的 React 上下文。这变得更微不足道了。谢谢@LIIT。

示例:

import { useOutletContext } from 'react-router-dom';

const useTitle = (title) => {
  const { setTitle } = useOutletContext();

  useEffect(() => {
    setTitle(title);
  }, [setTitle, title]);
};

...

function MainContent() {
  const [title, setTitle] = useState("");
  return (
    <div>
      <h1>{title}</h1>
      <div>
        <Outlet context={{ title, setTitle }} />
      </div>
    </div>
  );
}

...

const CreateProject = ({ title }) => {
  useTitle(title);

  return ...;
};

...

<Router>
  <Routes>
    <Route path="/projects" element={<MainContent />}>
      <Route index element={<ProjectList title="Projects" />} />
      <Route
        path="create"
        element={<CreateProject title="Create Project" />}
      />
    </Route>
  </Routes>
</Router>

对于 left-right 布局,我遇到了同样的问题:更改侧边栏内容和主要内容,而不重复样式、横幅等。

我发现的最简单的方法是删除嵌套路由,并创建一个布局组件,在其中我通过属性提供不断变化的内容。

布局组件(为此 post 剥离):

export function Layout(props) {

  return (
    <>
      <div class="left-sidebar">
        <img id="logo" src={Logo} alt="My logo" />
        {props.left}
      </div>

      <div className='right'>
        <header className="App-header">
          <h1>This is big text!</h1>
        </header>

        <nav>
          <NavLink to="/a">A</NavLink>
          &nbsp;|&nbsp;
          <NavLink to="/b">B</NavLink>
        </nav>

        <main>
          {props.right}
        </main>
      </div>
    </>
  );
}

在反应路由器中的用法:

<Route path="myPath" element={
  <Layout left={<p>I'm left</p>}
          right={<p>I'm right</p>} />
} />