React Router v6 在组件之外导航

kof*_*gna 54 javascript reactjs react-router react-router-dom

在react-router v5中我创建了这样的历史对象:

import { createBrowserHistory } from "history";
export const history = createBrowserHistory();
Run Code Online (Sandbox Code Playgroud)

然后将其传递给路由器:

import { Router, Switch, Route, Link } from "react-router-dom";
<Router history={history}>
 ... my routes
</Router>
Run Code Online (Sandbox Code Playgroud)

我这样做是为了有机会在组件之外使用历史记录:

   // store action
    logout() {
        this.user = null;
        history.push('/');
    }
Run Code Online (Sandbox Code Playgroud)

通过这种方式,我将逻辑移至商店,并且组件尽可能保持干净。但现在,在 React Router v6 中我不能做同样的事情。我仍然可以useNavigate()在我的组件内部使用它进行导航,但我无法创建一个navigate将其使用到我的商店中的组件。还有其他选择吗?

Dre*_*ese 45

事实证明,如果您实现一个自定义路由器,以与 RRDv6 路由器相同的方式实例化历史状态,则可以复制该行为。

检查BrowserRouter实现,例如:

export function BrowserRouter({
  basename,
  children,
  window
}: BrowserRouterProps) {
  let historyRef = React.useRef<BrowserHistory>();
  if (historyRef.current == null) {
    historyRef.current = createBrowserHistory({ window });
  }

  let history = historyRef.current;
  let [state, setState] = React.useState({
    action: history.action,
    location: history.location
  });

  React.useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      basename={basename}
      children={children}
      location={state.location}
      navigationType={state.action}
      navigator={history}
    />
  );
}
Run Code Online (Sandbox Code Playgroud)

创建一个CustomRouter使用自定义history对象并管理状态的对象:

const CustomRouter = ({ history, ...props }) => {
  const [state, setState] = useState({
    action: history.action,
    location: history.location
  });

  useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      {...props}
      location={state.location}
      navigationType={state.action}
      navigator={history}
    />
  );
};
Run Code Online (Sandbox Code Playgroud)

这有效地将自定义history对象代理到Router导航状态并管理导航状态。

CustomRouter从这里您可以用自定义对象替换history现有的Router导入自react-router-dom

export default function App() {
  return (
    <CustomRouter history={history}>
      <div className="App">
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/profile" element={<Profile />} />
        </Routes>
      </div>
    </CustomRouter>
  );
}
Run Code Online (Sandbox Code Playgroud)

你的codesandbox的分支:

编辑react-router-v6-navigate-outside-of-components

更新

react-router-dom@6显示历史路由器。

历史路由器

<unstable_HistoryRouter>将库的实例history作为 prop。这允许您在非 React 上下文中使用该实例或作为全局变量。

import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import { createBrowserHistory } from "history";

const history = createBrowserHistory({ window });

ReactDOM.render(
  <HistoryRouter history={history}>
    {/* The rest of your app goes here */}
  </HistoryRouter>,
  root
);
Run Code Online (Sandbox Code Playgroud)

有这样一条注释:

此 API 目前带有前缀,unstable_因为您可能会无意中向应用程序添加两个版本的history库,即添加到 package.json 中的版本和 React Router 内部使用的任何版本。如果您的工具允许,建议不要添加history为直接依赖项,而是依赖react-router包中的嵌套依赖项。一旦我们有了检测不匹配版本的机制,该 API 将删除其unstable_前缀。

RRDv6.4+ 的注释

如果您使用 RRDv6.4+ 并且使用数据路由器,那么好消息是至少仍通过 RRDv6.7.0unstable_HistoryRouter导出您可以在此处跟踪存储库中提交的问题。

如果您使用数据路由器,那么新的“不稳定”方法是navigate直接使用路由器对象的附加函数。

例子:

export function BrowserRouter({
  basename,
  children,
  window
}: BrowserRouterProps) {
  let historyRef = React.useRef<BrowserHistory>();
  if (historyRef.current == null) {
    historyRef.current = createBrowserHistory({ window });
  }

  let history = historyRef.current;
  let [state, setState] = React.useState({
    action: history.action,
    location: history.location
  });

  React.useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      basename={basename}
      children={children}
      location={state.location}
      navigationType={state.action}
      navigator={history}
    />
  );
}
Run Code Online (Sandbox Code Playgroud)

  • @ChambreNoire 是的,如果你问我的话,那就太无赖了。一方面,RRD 正在朝着在路由加载时获取数据的方向发展,但似乎他们正在进入“redux-toolkit/query”已经可以很好地处理和处理的领域......但代价是无法发出来自路由器/反应代码之外的应用程序中其他位置的命令式导航操作,即异步操作。我不完全理解 RRD 维护者的动机。 (2认同)

小智 7

更简单的代码(react-router-dom@6.15.0),我喜欢这样

1-创建一个浏览器路由器对象router.tsx

import { createBrowserRouter } from 'react-router-dom';
const router = createBrowserRouter([
  {
    path: '/',
    element: <HomePage />,
  },
  {
    path: 'page',
    element: <SomePage />,
  }
]);
export default router;
Run Code Online (Sandbox Code Playgroud)

2-然后在App.tsx(或您想要使用路由器的地方):

import {  RouterProvider } from 'react-router-dom';
import router from '@/router';

function App() {
  return (
    <main>
      <RouterProvider router={router} />
    </main>
  );
}

export default App;
Run Code Online (Sandbox Code Playgroud)

3-现在从组件外部导航

import router from '@/router';
...
router.navigate('/path-to-go');
Run Code Online (Sandbox Code Playgroud)


Oka*_*dag 6

已接受答案的 TypeScript 解决方案

历史对象:

import { createBrowserHistory } from "history";

const customHistory = createBrowserHistory();

export default customHistory;
Run Code Online (Sandbox Code Playgroud)

BrowserRouterProps 是一种反应路由器类型。

export interface BrowserRouterProps {
  basename?: string;
  children?: React.ReactNode;
  window?: Window;
}
Run Code Online (Sandbox Code Playgroud)

自定义路由器:

import { useLayoutEffect, useState } from "react";
import { BrowserRouterProps, Router } from "react-router-dom";
import { BrowserHistory } from "history";
import customHistory from "./history";
interface Props extends BrowserRouterProps {
  history: BrowserHistory;
}
export const CustomRouter = ({ basename, history, children }: Props) => {
  const [state, setState] = useState({
    action: history.action,
    location: history.location,
  });
  useLayoutEffect(() => history.listen(setState), [history]);
  return (
    <Router
      navigator={customHistory}
      location={state.location}
      navigationType={state.action}
      children={children}
      basename={basename}
    />
  );
};  
Run Code Online (Sandbox Code Playgroud)

使用 CustomRouter 代替 BrowserRouter

ReactDOM.render(
  <React.StrictMode>
    <CustomRouter history={customHistory}>
      <App />
    </CustomRouter>
  </React.StrictMode>,
  document.getElementById("root")
);
Run Code Online (Sandbox Code Playgroud)