69

简介

这是我学习react-router-dom@5.1.2时,为了加深自己对react-router-dom的理解和帮助一些英文不好的同学,对官方文档进行了翻译,本人水平有限,如有理解和翻译错误,欢迎大家指正。官网地址

快速入门

要在web应用中开始使用React Router,您将需要一个React Web应用程序.如果您需要创建一个,我们建议您尝试Create React App。这是一个非常流行的工具,可与React Router很好地配合使用。

首先,安装create-react-app并使用它创建一个新项目。

安装

您可以使用npm或yarn从公共npm注册表中安装React Router。由于我们构建的是web app,因此在本指南中将使用react-router-dom。

npm install -g create-react-app       // 全局安装 create-react-app
create-react-app demo-app             // 创建一个react项目
cd demo-app                           // 进入react项目目录
npm install react-router-dom          // 安装react-router-dom

接下来,将以下两个示例之一复制/粘贴到src/App.js中。

第一个示例:基本路由

在此示例中,路由器处理了3个“页面”:主页、关于页面和用户页面。当您点击不同的<Link>时,这个路由会渲染匹配的<Route>。

注意:其实<Link>最后渲染出来是一个有真实href的标签,因此使用键盘导航或屏幕阅读器的人也可以使用react-router-dom。

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom';

function Home(props) {
    console.log('Home=>', props);
    return <h2>Home</h2>
}

function About(props) {
    console.log('About=>', props);
    return <h2>About</h2>;
}

function Users(props) {
    console.log('Users=>', props);
    return <h2>Users</h2>;
}

function App() {
    return <BrowserRouter>
        <div>
            <nav>
                <ul>
                    <li>
                        <Link to={'/'}>Home</Link>
                    </li>
                    <li>
                        <Link to={'/about'}>About</Link>
                    </li>
                    <li>
                        <Link to={'/users'}>Users</Link>
                    </li>
                </ul>
            </nav>
            {/* <Switch>通过查找所有的子<Route>并渲染与当前URL匹配的第一个<Route>的内容 */}
            <Switch>
                <Route path={'/about'}>
                    <About />
                </Route>
                <Route path={'/users'} children={<Users />}/>
                <Route path={'/'}>
                    <Home />
                </Route>
            </Switch>
        </div>
    </BrowserRouter>
}

ReactDOM.render(<App />, document.querySelector('#root'));

第二个示例:嵌套路由

此示例显示了嵌套路由的工作方式。路线/topics会加载Topics组件,在这个组件上的path:id值上有条件地渲染任何其他<Route>。

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Route, Link, useRouteMatch, useParams } from 'react-router-dom';

function Home(props) {
    console.log('Home=>', props);
    return <h2>Home</h2>
}

function About(props) {
    console.log('About=>', props);
    return <h2>About</h2>;
}

function Topic() {
    let { topicId } = useParams();
    return <h3>Requested topic ID: {topicId}</h3>
}

function Topics() {
    const match = useRouteMatch();
    console.log('match=>', match);
    return (
        <div>
            <h2>Topics</h2>
            <ul>
                <li>
                    <Link to={`${match.url}/components`}>Components</Link>
                </li>
                <li>
                    <Link to={`${match.url}/props-v-state`}>Props v. State</Link>
                </li>
            </ul>
            
            {/*
                Topics页面有自己的<Switch>,其中包含更多的路线,建立在/topics路径之上
                您可以将第二个<Route>视为所有主题的“索引”页面,或者当未选择任何主题时显示的页面
            */}
            <Switch>
                <Route path={`${match.path}/:topicId`}>
                    <Topic />
                </Route>
                <Route path={match.path}>
                    <h3>Please select a topic.</h3>
                </Route>
            </Switch>
        </div>
    );
}

function App() {
    return <BrowserRouter>
        <ul>
            <li>
                <Link to={'/'}>Home</Link>
            </li>
            <li>
                <Link to={'/about'}>About</Link>
            </li>
            <li>
                <Link to={'/topics'}>Topics</Link>
            </li>
        </ul>
        
        <Switch>
            <Route path={'/about'}>
                <About />
            </Route>
            <Route path={'/topics'}>
                <Topics />
            </Route>
            <Route path={'/'}>
                <Home />
            </Route>
        </Switch>
    </BrowserRouter>
}

ReactDOM.render(<App />, document.querySelector('#root'));

继续

希望这些示例能让您对使用React Router创建web app有点感觉。继续阅读可以了解有关React Router中主要组件的更多信息!

主要组件

React Router中的组件主要分为三类:

  • 路由器,例如<BrowserRouter>和<HashRouter>
  • 路由匹配器,例如<Route>和<Switch>
  • 导航,例如<Link>,<NavLink>和<Redirect>
    在Web应用程序中使用的所有组件都应从react-router-dom导入。

路由器

每个React Router应用程序的核心应该是路由器组件。对于Web项目,react-router-dom提供<BrowserRouter>和<HashRouter>路由器。两者之间的主要区别在于它们存储URL和与Web服务器通信的方式。

  1. <BrowserRouter>使用常规URL路径。 这些通常是外观最好的网址,但它们要求您的服务器配置正确。 具体来说,您的Web服务器需要在所有由React Router客户端管理的URL上提供相同的页面。Create React App在开发中即开即用地支持此功能,并附带有关如何配置生产服务器的说明
  2. <HashRouter>将当前位置存储在URL的哈希部分中,因此URL看起来类似于http://example.com/#/your/page。 由于哈希从不发送到服务器,因此这意味着不需要特殊的服务器配置。

要使用路由器,只需确保将其渲染在元素层次结构的根目录下即可。 通常,您会将顶级<App>元素包装在路由器中,如下所示:

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";

function App() {
  return <h1>Hello React Router</h1>;
}

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

路线匹配器

有两个路线匹配组件:Switch和Route。渲染<Switch>时,它会搜索其子元素<Route>,以查找其路径与当前URL匹配的元素。当找到一个时,它将渲染该<Route>并忽略所有其他路由。这意味着您应该将<Route>包含更多特定路径(通常较长)的路径放在不那么特定路径之前。

如果没有<Route>匹配,则<Switch>不渲染任何内容(null)。

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Route } from 'react-router-dom';

function App() {
    return <div>
        <Switch>
            {/* 如果当前URL是/ about,那么将渲染此路由,而其余路由将被忽略 */}
            <Route path={'/about'}>
                <h2>About</h2>
            </Route>
            {/* 请注意这两个路由的顺序。 更具体的path="/contact/id"位于path="/contact"之前,因此在查看单个联系人时将显示这个<Route> */}
            <Route path={'/contact/:id'}>
                <h2>Contact</h2>
            </Route>
            <Route path={'/contact'}>
                <h2>AllContact</h2>
            </Route>
            {/*
                如果先前的路由均未呈现任何内容,则此路由将充当后备路径。
                重要提示:路径="/"的路线将始终匹配任何路径的URL,因为所有URL均以/开头。 所以这就是为什么我们把这这个<Route>放在最后
            */}
            <Route path={'/'}>
                <h2>Home</h2>
            </Route>
        </Switch>
    </div>
}

ReactDOM.render(<BrowserRouter>
    <App />
</BrowserRouter>, document.querySelector('#root'));

⚠️ 需要注意的重要一件事是<Route path>匹配URL的开头,而不是整个开头。所以,<Route path ="/">将始终与任意一个URL匹配。因此,我们通常将此<Route>放在<Switch>的最后。另一种可能的解决方案是使用确实与整个URL匹配的<Route exact path="">。exact属性表示精准匹配。

⚠️注意:尽管React Router确实支持在<Switch>之外渲染<Route>元素,从5.1版开始,我们建议您改用useRouteMatch钩子。此外,我们不建议您渲染不带路径的<Route>,而是建议您使用钩子来访问您所使用的任何变量。

导航(或路线更改器)

React Router提供了一个<Link>组件来在您的应用程序中创建链接。 无论在何处渲染<Link>,锚点都将渲染在HTML文档中。

<NavLink>是<Link>的一种特殊类型,当其prop与当前位置匹配时,可以将其自身设置为“active”。

任何时候要强制导航,都可以渲染<Redirect>。渲染<Redirect>时,它将会使用其props进行导航

<Link to="/">Home</Link>
// <a href="/">Home</a>

<NavLink to="/react" activeClassName="hurray">
  React
</NavLink>
// 当URL为/react的时候, 渲染出来的以下内容:
// <a href="/react" className="hurray">React</a>
// 如果是其他URL,则渲染为:
// <a href="/react">React</a>

// 重定向到/login
<Redirect to="/login" />

NavLink例子:

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Route, NavLink } from 'react-router-dom';

function NavigationApp() {
    return <BrowserRouter>
        <ul>
            <li>
                <NavLink to={'/react'}>React</NavLink>
            </li>
            <li>
                <NavLink to={'/redux'}>redux</NavLink>
            </li>
        </ul>
        <div>
            <Switch>
                <Route path={'/react'}>
                    <h1>React</h1>
                </Route>
                <Route path={'/redux'}>
                    <h1>Redux</h1>
                </Route>
            </Switch>
        </div>
    </BrowserRouter>
}

ReactDOM.render(<NavigationApp />, document.querySelector('#root'));

服务器渲染

代码分割

网络应用的一个重要特色就是:我们无需让访问者下载整个应用程序即可使用,您可以将代码拆分视为增量下载应用程序。为此,我们将使用webpack,@babel/plugin-syntax-dynamic-import,和loadable-components做代码分割。

webpack内置了对动态导入的支持; 但是,如果您使用的是Babel(例如,将JSX编译为JavaScript),则需要使用@babel/plugin-syntax-dynamic-import插件。这是仅语法的插件,这意味着Babel不会进行任何其他转换。该插件仅允许Babel解析动态导入,因此webpack可以将它们捆绑为代码拆分。 您的.babelrc应该使用如下配置:

{
  "presets": ["@babel/preset-react"],
  "plugins": ["@babel/plugin-syntax-dynamic-import"]
}

loadable-components是用于通过动态导入加载组件的库。 它自动处理各种边缘情况,并使代码拆分变得简单! 这是有关如何使用loadable-components的示例:

import loadable from "@loadable/component";
import Loading from "./Loading.js";

const LoadableComponent = loadable(() => import("./Dashboard.js"), {
  fallback: <Loading />
});

export default class LoadableDashboard extends React.Component {
  render() {
    return <LoadableComponent />;
  }
}

这一切就是这么简单! 只需使用LoadableDashboard(或任何您命名的组件),当您在应用程序中使用它时,它将自动加载并渲染。fallback是一个占位符组件,用于在加载实际组件时显示。
完整的文档在这里

代码拆分和服务器端渲染

loadable-components包含服务器端渲染的指南

滚动还原

在早期版本的React Router中,我们提供了对滚动恢复的开箱即用的支持,从那以后人们一直在要求它。 希望本文档可以帮助您从滚动条和路由中获得所需的信息!
浏览器开始以自己的history.pushState处理滚动还原,其处理方式与使用普通浏览器导航时的处理方式相同。它已经可以在Chrome浏览器中使用,而且非常棒,这是滚动恢复规范
由于浏览器开始处理“默认情况”,并且应用具有不同的滚动需求(例如本网站!),因此我们不提供默认滚动管理功能。 本指南应帮助您实现任何滚动需求。

滚动到顶部

在大多数情况下,您所需要做的只是“滚动到顶部”,因为您有一个较长的内容页面,该页面在导航到该页面时始终保持向下滚动。 使用<ScrollToTop>组件可以轻松处理此问题,该组件将在每次导航时向上滚动窗口:
创建滚动到顶部组件:

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export default function ScrollToTop() {
    const { pathname } = useLocation();
    console.log('pathname=>', pathname);
    useEffect(() => {
        window.scrollTo(0, 0);
    }, [ pathname ]);
    return null;
}

如果您尚未运行React 16.8,则可以使用React.Component子类执行相同的操作:

import React from "react";
import { withRouter } from "react-router-dom";

class ScrollToTop extends React.Component {
  componentDidUpdate(prevProps) {
    if (
      this.props.location.pathname !== prevProps.location.pathname
    ) {
      window.scrollTo(0, 0);
    }
  }

  render() {
    return null;
  }
}

export default withRouter(ScrollToTop);

然后在您的应用程序的顶部渲染它,但是要把它路由器下面:

import React from 'react'
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import ScrollToTop from './ScrollToTop'

function App() {
    return <BrowserRouter>
        <ScrollToTop/>
        <h1>App</h1>
    </BrowserRouter>
}

ReactDOM.render(<App />, document.querySelector('#root'));

如果您将标签页接口连接到路由器,那么当他们切换标签页时,您可能不想滚动到顶部。 那么,您需要在的特定位置使用<ScrollToTopOnMount>?

import { useEffect } from "react";

function ScrollToTopOnMount() {
  useEffect(() => {
    window.scrollTo(0, 0);
  }, []);

  return null;
}

// 使用以下代码将此内容渲染到某处:
// <Route path="..." children={<LongContent />} />
function LongContent() {
  return (
    <div>
      <ScrollToTopOnMount />

      <h1>Here is my long content page</h1>
      <p>...</p>
    </div>
  );
}

再说一次,如果您运行的React小于16.8,则可以对React.Component子类做同样的事情:

with a React.Component subclass:import React from "react";

class ScrollToTopOnMount extends React.Component {
  componentDidMount() {
    window.scrollTo(0, 0);
  }

  render() {
    return null;
  }
}

// 使用以下代码将此内容渲染到某处:
// <Route path="..." children={<LongContent />} />
class LongContent extends React.Component {
  render() {
    return (
      <div>
        <ScrollToTopOnMount />

        <h1>Here is my long content page</h1>
        <p>...</p>
      </div>
    );
  }
}

通用解决方案

对于通用解决方案(以及哪些浏览器已开始在本机实现),我们谈论的是两件事:
1、向上滚动导航,这样就不会启动滚动到底部的新屏幕
2、恢复窗口的滚动位置和“后退”和“前进”单击上的溢出元素(但不单击“链接”单击!)
在某一时刻,我们希望提供一个通用的API。 这就是我们要研究的方向:

<Router>
  <ScrollRestoration>
    <div>
      <h1>App</h1>

      <RestoredScroll id="bunny">
        <div style={{ height: "200px", overflow: "auto" }}>
          I will overflow
        </div>
      </RestoredScroll>
    </div>
  </ScrollRestoration>
</Router>

首先,ScrollRestoration在导航中向上滚动窗口。其次,它将使用location.key将窗口滚动位置和RestoredScroll组件的滚动位置保存到sessionStorage。然后,在安装ScrollRestoration或RestoredScroll组件时,它们可以从sessionStorage查找其位置。

最棘手的部分是定义一个"opt-out"的API,当你不想滚动窗口时进行管理。例如,如果您在页面内容中浮动了一些选项卡导航,则可能不想滚动到顶部(选项卡可能会滚动到视图之外!)。当我们得知Chrome现在可以为我们管理滚动位置,并意识到不同的应用程序将具有不同的滚动需求时,我们有点迷失了我们需要提供某些东西的信念,尤其是当人们只想滚动到顶部时( 您可以直接将其直接添加到您的应用中)。基于此,我们不再有足够的力气自己完成工作(就像您一样,我们的时间有限!)。 但是,我们很乐意为有志于实施通用解决方案的任何人提供帮助。 一个可靠的解决方案甚至可以存在于项目中。 如果您开始使用它,请与我们联系:)

设计原理

本指南的目的是说明使用React Router时要具有的思维模型。 我们称之为“动态路由”,它与您可能更熟悉的“静态路由”完全不同。

静态路由

如果您使用过Rails,Express,Ember,Angular等,则使用了静态路由。 在这些框架中,您需要在进行任何渲染之前将路由声明为应用初始化的一部分。 React Router pre-v4也是静态的(大部分是静态的)。让我们看一下在express中如何配置路由:

Express路由配置模式:
app.get("/", handleIndex);
app.get("/invoices", handleInvoices);
app.get("/invoices/:id", handleInvoice);
app.get("/invoices/:id/edit", handleInvoiceEdit);

app.listen();

请注意在app监听之前如何声明路由。 我们使用的客户端路由器相似。 在Angular中,您先声明routes,然后在渲染之前将其导入顶级的AppModule中:

// Angular的路由配置样式:
const appRoutes: Routes = [
  {
    path: "crisis-center",
    component: CrisisListComponent
  },
  {
    path: "hero/:id",
    component: HeroDetailComponent
  },
  {
    path: "heroes",
    component: HeroListComponent,
    data: { title: "Heroes List" }
  },
  {
    path: "",
    redirectTo: "/heroes",
    pathMatch: "full"
  },
  {
    path: "**",
    component: PageNotFoundComponent
  }
];

@NgModule({
  imports: [RouterModule.forRoot(appRoutes)]
})
export class AppModule {}

Ember具有常规的route.js文件,该版本会为您读取并导入到应用程序中。 同样,这是在您的应用渲染之前发生的。

// Ember 路由配置样式:
Router.map(function() {
  this.route("about");
  this.route("contact");
  this.route("rentals", function() {
    this.route("show", { path: "/:rental_id" });
  });
});

export default Router;

虽然API是不同的,他们都有着“静态路由”的模式。 React Router也跟进了直到v4。
为了成功使用React Router,您需要忘记所有这些!

背后故事

坦率地说,我们对v2采取React Router的方向感到非常沮丧。 我们(Michael和Ryan)感到受到API的限制,认识到我们正在重新实现React的各个部分(生命周期等),而这与React为构建UI提供的思维模型不符。

我们走在一家酒店的走廊上,正在讨论如何解决这个问题。我们互相问:“如果我们使用我们在工作室里教的模式来建造路由器,那会是什么样子?”

仅仅在开发的几个小时内,我们就有了一个概念证明,我们知道这就是我们想要的路由的未来。我们最终得到的API不是React的“外部”API,而是一个由React的其余部分组成的API,或者自然地与之匹配。我们想你会喜欢的。

动态路由

当说动态路由时,是指在您的应用渲染时发生的路由,而不是在运行的应用之外的配置或约定中进行。 这意味着几乎所有内容都是React Router中的一个组件。 这是对该API的60秒回顾,以了解其工作原理:

首先,为您要定位的环境获取一个Router组件,并将其呈现在应用程序的顶部。

// react-native
import { NativeRouter } from "react-router-native";

// react-dom (我们将在这里使用什么)
import { BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  el
);

接下来,获取链接组件以链接到新位置:

const App = () => (
  <div>
    <nav>
      <Link to="/dashboard">Dashboard</Link>
    </nav>
  </div>
);

最后,渲染一个Route在用户访问/dashboard时显示一些UI。

const App = () => (
  <div>
    <nav>
      <Link to="/dashboard">Dashboard</Link>
    </nav>
    <div>
      <Route path="/dashboard" component={Dashboard} />
    </div>
  </div>
);

Route将渲染<Dashboard {... props} />,其中props是路由器特定的东西,props对象以这三个关键对象{match,location, history}。 如果用户不在/dashboard上,则Route将渲染null。 差不多就够了。

嵌套路由

很多路由器都有“嵌套路由”的概念。如果您使用的是v4之前的React Router版本,那么您也会知道它是这么做的!当您从静态路由配置转移到动态渲染路由时,如何“嵌套路由”?如何嵌套div呢?

const App = () => (
  <BrowserRouter>
    {/* 这是一个 div */}
    <div>
      {/* 这是一个 Route */}
      <Route path="/tacos" component={Tacos} />
    </div>
  </BrowserRouter>
);

// 当网址与`/ tacos`相匹配时,渲染此组件
const Tacos = ({ match }) => (
  // 这是一个嵌套的div
  <div>
    {/* 这是一条嵌套路线match.url帮助我们建立相对路径 */}
    <Route path={match.url + "/carnitas"} component={Carnitas} />
  </div>
);

看到路由器没有“嵌套”API了吗?路由只是一个组件,就像div一样。要嵌套一个路由或div,你只需要...
让我们更加棘手。

响应式路由

考虑用户导航到/invoices。 您的应用程序适应不同的屏幕尺寸,它们的viewport狭窄,因此您只向他们显示发票清单和发票仪表板的链接。 他们可以从那里更深入地导航。

小屏幕
url: /invoices

+----------------------+
|                      |
|      Dashboard       |
|                      |
+----------------------+
|                      |
|      Invoice 01      |
|                      |
+----------------------+
|                      |
|      Invoice 02      |
|                      |
+----------------------+
|                      |
|      Invoice 03      |
|                      |
+----------------------+
|                      |
|      Invoice 04      |
|                      |
+----------------------+

在较大的屏幕上,我们想显示一个主从视图,其中导航在左侧,仪表板或特定发票在右侧。

大屏幕
url: /invoices/dashboard

+----------------------+---------------------------+
|                      |                           |
|      Dashboard       |                           |
|                      |   Unpaid:             5   |
+----------------------+                           |
|                      |   Balance:   $53,543.00   |
|      Invoice 01      |                           |
|                      |   Past Due:           2   |
+----------------------+                           |
|                      |                           |
|      Invoice 02      |                           |
|                      |   +-------------------+   |
+----------------------+   |                   |   |
|                      |   |  +    +     +     |   |
|      Invoice 03      |   |  | +  |     |     |   |
|                      |   |  | |  |  +  |  +  |   |
+----------------------+   |  | |  |  |  |  |  |   |
|                      |   +--+-+--+--+--+--+--+   |
|      Invoice 04      |                           |
|                      |                           |
+----------------------+---------------------------+

现在暂停一分钟,并考虑两种屏幕尺寸的/invoices网址。 它甚至是大屏幕的有效路线吗? 我们应该在右边放什么?

大屏幕
url: /invoices
+----------------------+---------------------------+
|                      |                           |
|      Dashboard       |                           |
|                      |                           |
+----------------------+                           |
|                      |                           |
|      Invoice 01      |                           |
|                      |                           |
+----------------------+                           |
|                      |                           |
|      Invoice 02      |             ???           |
|                      |                           |
+----------------------+                           |
|                      |                           |
|      Invoice 03      |                           |
|                      |                           |
+----------------------+                           |
|                      |                           |
|      Invoice 04      |                           |
|                      |                           |
+----------------------+---------------------------+

在大屏幕上,/invoices不是有效的路径,但在小屏幕上则是! 为了使事情变得更有趣,请考虑使用大型手机的人。 他们可能会纵向查看/invoices,然后将手机旋转至横向。 突然,我们有足够的空间来显示主从界面,因此您应该立即进行重定向!
React Router以前版本的静态路由并没有真正解决这个问题的方法。 但是,当路由是动态的时,您可以声明性地组合此功能。 如果您开始考虑将路由选择为UI,而不是静态配置,那么您的直觉将引导您进入以下代码:

const App = () => (
  <AppLayout>
    <Route path="/invoices" component={Invoices} />
  </AppLayout>
);

const Invoices = () => (
  <Layout>
    {/* 总是显示导航 */}
    <InvoicesNav />

    <Media query={PRETTY_SMALL}>
      {screenIsSmall =>
        screenIsSmall ? (
          // 小屏幕没有重定向
          <Switch>
            <Route
              exact
              path="/invoices/dashboard"
              component={Dashboard}
            />
            <Route path="/invoices/:id" component={Invoice} />
          </Switch>
        ) : (
          // 大屏幕呢!
          <Switch>
            <Route
              exact
              path="/invoices/dashboard"
              component={Dashboard}
            />
            <Route path="/invoices/:id" component={Invoice} />
            <Redirect from="/invoices" to="/invoices/dashboard" />
          </Switch>
        )
      }
    </Media>
  </Layout>
);

当用户将手机从纵向旋转到横向时,此代码将自动将其重定向到仪表板。 有效routes会根据用户手中移动设备的动态性质而变化。
这只是一个例子。 我们可以讨论许多其他内容,但我们将总结以下建议:为了使您的直觉与React Router的直觉相符,请考虑组件而不是静态路由。 考虑一下如何使用React的声明式可组合性解决问题,因为几乎每个“ React Router问题”都可能是“ React问题”。

测试

React Router依靠React上下文来工作。 这会影响您如何测试在你的组件里使用我们的组件。

Context

如果您尝试对渲染<Link>或<Route>的组件之一进行单元测试,等等。您会收到一些有关上下文的错误和警告。 尽管您可能会想自己亲自设置路由器上下文,我们建议您将单元测试包装在路由器组件之一中:具有history属性的路由或<StaticRouter>,<MemoryRouter>或<BrowserRouter>的基本路由器(如果window.history在测试环境中可作为全局变量使用)。建议使用MemoryRouter或自定义历史记录,以便能够在两次测试之间重置路由器。

class Sidebar extends Component {
  // ...
  render() {
    return (
      <div>
        <button onClick={this.toggleExpand}>expand</button>
        <ul>
          {users.map(user => (
            <li>
              <Link to={user.path}>{user.name}</Link>
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

// broken
test("it expands when the button is clicked", () => {
  render(<Sidebar />);
  click(theButton);
  expect(theThingToBeOpen);
});

// fixed!
test("it expands when the button is clicked", () => {
  render(
    <MemoryRouter>
      <Sidebar />
    </MemoryRouter>
  );
  click(theButton);
  expect(theThingToBeOpen);
});

从指定route开始

<MemoryRouter>支持initialEntries和initialIndex props,因此您可以在特定位置启动应用程序(或应用程序的任何较小部分)。

test("current user is active in sidebar", () => {
  render(
    <MemoryRouter initialEntries={["/users/2"]}>
      <Sidebar />
    </MemoryRouter>
  );
  expectUserToBeActive(2);
});

导航

我们进行了很多测试,以检查route在位置更改时是否有效,因此您可能不需要测试这些东西。 但是,如果您需要在应用程序中测试导航,则可以这样进行:

app.js (a component file)
import React from "react";
import { Route, Link } from "react-router-dom";

// 我们的主题,即应用,但您可以测试任何子项
// 您的应用程序部分
const App = () => (
  <div>
    <Route
      exact
      path="/"
      render={() => (
        <div>
          <h1>Welcome</h1>
        </div>
      )}
    />
    <Route
      path="/dashboard"
      render={() => (
        <div>
          <h1>Dashboard</h1>
          <Link to="/" id="click-me">
            Home
          </Link>
        </div>
      )}
    />
  </div>
);
// 您还可以在此处使用"@testing-library/react"或"enzyme/mount"之类的渲染器
import { render, unmountComponentAtNode } from "react-dom";
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from "react-router-dom";

// app.test.js
it("navigates home when you click the logo", async => {
  // 在真实测试中,渲染器如"@testing-library/react"
  // 将负责设置DOM元素
  const root = document.createElement('div');
  document.body.appendChild(root);

  // Render app
  render(
    <MemoryRouter initialEntries={['/my/initial/route']}>
      <App />
    <MemoryRouter>,
    root
  );

  // 与页面互动
  act(() => {
    // 查找链接(可能使用文本内容)
    const goHomeLink = document.querySelector('#nav-logo-home');
    // Click it
    goHomeLink.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });

  // 检查显示的页面内容是否正确
  expect(document.body.textContent).toBe('Home');
});

检查测试中的位置

在测试中,您不必经常访问location或history对象,但是如果你这样做了(比如验证在url栏中设置了新的查询参数),你可以在测试中添加一个更新变量的路由:

// app.test.js
test("clicking filter links updates product query params", () => {
  let history, location;
  render(
    <MemoryRouter initialEntries={["/my/initial/route"]}>
      <App />
      <Route
        path="*"
        render={({ history, location }) => {
          history = history;
          location = location;
          return null;
        }}
      />
    </MemoryRouter>,
    node
  );

  act(() => {
    // example: click a <Link> to /products?id=1234
  });

  // assert about url
  expect(location.pathname).toBe("/products");
  const searchParams = new URLSearchParams(location.search);
  expect(searchParams.has("id")).toBe(true);
  expect(searchParams.get("id")).toEqual("1234");
});

备选方案:
1、如果您的测试环境具有浏览器全局变量window.location和window.history(这是通过JSDOM在Jest中的默认设置,但您无法重置测试之间的历史记录),则也可以使用BrowserRouter。
2、您可以将基本路由器与history包中的history props一起使用,而不是将自定义路由传递给MemoryRouter:

// app.test.js
import { createMemoryHistory } from "history";
import { Router } from "react-router";

test("redirects to login page", () => {
  const history = createMemoryHistory();
  render(
    <Router history={history}>
      <App signedInUser={null} />
    </Router>,
    node
  );
  expect(history.location.pathname).toBe("/login");
});

React测试包

请参阅官方文档中的示例:Testing React Router with React Testing Library

Redux集成

Redux是React生态系统的重要组成部分。 对于想要同时使用React Router和Redux的人,我们希望使其无缝集成。

阻止更新

通常,React Router和Redux可以很好地协同工作。不过有时候,应用程序可以包含一个组件,该组件在位置更改时(子routes或活动的导航links不会更新)不会更新。

在以下情况下会发生这种情况:
1、该组件通过connect()(Comp)连接到redux。
2、该组件不是“路由组件”,这意味着它的渲染方式不是这样:<Route component = {SomeConnectedThing} />

问题在于Redux实现了shouldComponentUpdate,如果没有从路由器接收props,则没有任何迹象表明发生了任何变化。
这很容易解决,找到连接组件的位置,然后将组件使用withRouter包装在一起

深度集成

有些人想:
1、从store同步路由数据,并从store访问路由数据。
2、可以通过dispatch action操作导航
3、在Redux devtools中支持对路径更改进行时间行程调试。

所有这些都需要更深入的集成。

我们的建议是不要将routes完全保留在Redux store中。论证:
1、路由数据已经成为大多数关心它的组件的支持。 无论是来自store还是router,您组件的代码都基本相同。
2、在大多数情况下,您可以使用Link,NavLink和Redirect执行导航操作。有时您可能还需要以编程方式进行导航,有时您可能还需要以编程方式导航,在某个操作最初启动的异步任务之后。例如,您在用户提交登录表单时调度操作。然后,您的使用thunksaga或其他异步处理程序会对凭据进行身份验证,如果成功,则需要以某种方式导航到新页面。此处的解决方案只是将history对象(提供给所有路由组件)包括在操作的payload,并且异步处理程序可以在适当的时候使用此对象进行导航。
3、路线更改对于时间行程调试不太重要。唯一明显的情况是调试router/store同步中的问题,如果根本不同步它们,则该问题将消失。
但是,如果您强烈希望与store同步route,您可能需要尝试Connected React Router,这是React Router v4和Redux的第三方绑定。

静态Routes

以前版本的React Router使用静态路由来配置应用程序的路由。这样可以在渲染之前检查和匹配路线。由于v4转移到动态组件而不是路由配置,因此一些以前的用例变得不那么明显和棘手。我们正在开发一个可与静态路由配置和React Router配合使用的软件包,以继续满足这些用例。 现在正在开发中,但我们希望您能尝试一下并提供帮助。

React Router Config

API

Hooks

React Router附带了一些钩子,可让您访问路由器的状态并从组件内部执行导航。
请注意:您必须使用React> = 16.8才能使用这些钩子中的任何一个!

useHistory

useHistory钩子使您可以访问可用于导航的history实例。

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

function HomeButton() {
  let history = useHistory();

  function handleClick() {
    history.push("/home");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}

useLocation

useLocation钩子返回代表当前URL的location对象。您可以像useState一样考虑它,只要URL更改,它就会返回一个新位置。
这可能非常有用,例如 在您希望每次加载新页面时都使用Web分析工具触发新的"page view"事件的情况下,如以下示例所示:

import React from "react";
import ReactDOM from "react-dom";
import {
  BrowserRouter as Router,
  Switch,
  useLocation
} from "react-router-dom";

function usePageViews() {
  let location = useLocation();
  React.useEffect(() => {
    ga.send(["pageview", location.pathname]);
  }, [location]);
}

function App() {
  usePageViews();
  return <Switch>...</Switch>;
}

ReactDOM.render(
  <Router>
    <App />
  </Router>,
  node
);

useParams

useParams返回URL参数的key/value的对象。 使用它来访问当前<Route>的match.params。

import React from "react";
import ReactDOM from "react-dom";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  useParams
} from "react-router-dom";

function BlogPost() {
  let { slug } = useParams();
  return <div>Now showing post {slug}</div>;
}

ReactDOM.render(
  <Router>
    <Switch>
      <Route exact path="/">
        <HomePage />
      </Route>
      <Route path="/blog/:slug">
        <BlogPost />
      </Route>
    </Switch>
  </Router>,
  node
);

useRouteMatch

useRouteMatch钩子尝试以与<Route>相同的方式匹配当前URL。它主要用于在不实际渲染<Route>的情况下访问匹配数据。
不用useRouteMatch:

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

function BlogPost() {
  return (
    <Route
      path="/blog/:slug"
      render={({ match }) => {
        // 用match做你想做的一切...
        return <div />;
      }}
    />
  );
}

使用useRouteMatch:

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

function BlogPost() {
  let match = useRouteMatch("/blog/:slug");

  // 用match做你想做的一切...
  return <div />;
}

<BrowserRouter>

一个\<Router\>,它使用HTML5 history API (pushState、replaceState和popstate事件)来保持UI与URL同步。

<BrowserRouter
  basename={optionalString}
  forceRefresh={optionalBool}
  getUserConfirmation={optionalFunc}
  keyLength={optionalNumber}
>
  <App />
</BrowserRouter>

basename: string

所有location的基本URL。如果您的应用是通过服务器上的子目录提供的,则需要将其设置为子目录。格式正确的basename应以斜杠开头,但不能以斜杠结尾。

getUserConfirmation: func

用于确认导航的功能。 默认使用window.confirm

forceRefresh: bool

如果为true,则路由器将在页面导航中使用整页刷新。您可能希望使用它来模仿传统的服务器渲染应用程序在页面导航之间刷新整个页面的方式。

keyLength: number

location.key的长度。 默认为6。

children: node

要渲染的子元素。
注意:在React <16上,您必须使用单个子元素,因为render方法不能返回多个元素。 如果需要多个元素,则可以尝试将它们包装在额外的<div>中。

<HashRouter>

<Router>使用URL的哈希部分(即window.location.hash)使UI与URL保持同步。
重要说明:Hash history不支持location.key或location.state。在以前的版本中,我们试图纠正这种行为,但是有些边缘情况我们无法解决。任何需要此行为的代码或插件都将无法使用。 由于此技术仅旨在支持旧版浏览器,我们建议您将服务器配置为与<BrowserHistory>一起使用。

<HashRouter
  basename={optionalString}
  getUserConfirmation={optionalFunc}
  hashType={optionalString}
>
  <App />
</HashRouter>

basename: string

所有location的基本URL。 格式正确的basename应以斜杠开头,但不能以斜杠结尾。

<HashRouter basename="/calendar"/>
<Link to="/today"/> // 渲染出来的样子: <a href="#/calendar/today">

getUserConfirmation: func

用于confirm导航的功能。 默认使用window.confirm。

<HashRouter
  getUserConfirmation={(message, callback) => {
    // this is the default behavior
    const allowTransition = window.confirm(message);
    callback(allowTransition);
  }}
/>

hashType: string

用于window.location.hash的编码类型。 可用值为:

  • "slash" - 创建像#/#/sunshine/lollipops的hash
  • "noslash" - 创建像##sunshine/lollipops的hash
  • "hashbang" - 创建诸如#!/#!/sunshine/lollipops之类的"ajax crawlable"(Google弃用)hash

默认为 "/"

children: node

要渲染的单个子元素

import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, Switch, Route, Link, withRouter } from 'react-router-dom';

function INav() {
    let homeRef;
    let anchorRef = React.createRef();
    console.log('before - anchorRef=>', anchorRef);
    useEffect(props => {
        console.log('after - anchorRef=>', anchorRef);
        console.log('after- homeRef=>', homeRef);
    });
    return (
        <ul className={'nav'}>
            <li>
                <Link to={'/home'} replace innerRef={homeRef}>Home</Link>
            </li>
            <li>
                <Link to={'/rule'} innerRef={anchorRef}>Rule</Link>
            </li>
            <li>
                <Link to={'/form'} innerRef={node => {
                    // "node"指的是被挂载的DOM元素
                    // 组件被卸载时为null
                    console.log('node=>', node);
                }}>Form</Link>
            </li>
            <li>
                <Link to={location => `/table?sort=name`}>Table</Link>
            </li>
            <li>
            <Link to={location => {
                console.log('Charts - location=>', location);
                return { ...location, pathname: '/charts' }
            }}>Charts</Link>
            </li>
            <li>
                <Link to={{
                    pathname: '/example',
                    search: '?sort=name',
                    hash: '#the-hash',
                    state: {
                        fromDashboard: true,
                        name: 'Jameswain'
                    }
                }}>Example</Link>
            </li>
        </ul>
    )
}

function Home(props) {
    console.log('Home:', props);
    return <h1>
        Home
    </h1>
}

function Form(props) {
    console.log('Form:', props);
    return <h1>Form</h1>;
}

function Table(props) {
    console.log('Table:', props);
    return <h1>Table</h1>
}

function Rule(props) {
    console.log('rule:', props);
    return <h1>
        Rule
    </h1>
}

const Example = withRouter((props) => {
    console.log('Example:', props);
    return <h1>Example</h1>
});

const Charts = withRouter((props) => {
    console.log('Charts:', props);
    return <h1>Charts</h1>
});


function App() {
    return (
        <HashRouter hashType={'noslash'} basename={'/calendar'}>
            <div className={'app'}>
                <INav/>
                <Switch>
                    <Route path={'/home'} exact>
                        <Home />
                    </Route>
                    <Route path={'/rule'} children={props => <Rule {...props} />} />
                    <Route path={'/form'} render={props => <Form {...props} />} />
                    <Route path={'/table'} component={props => <Table {...props} />} />
                    <Route path={'/charts'} children={<Charts />} />
                    <Route path={'/example'}>
                        <Example />
                    </Route>
                </Switch>
            </div>
        </HashRouter>
    );
}

ReactDOM.render(<App />, document.querySelector('#root'));

<Link>

提供围绕应用程序的声明式、可访问的导航,其实渲染出来的就是一个标签,对标签的封装。

<Link to="/about">About</Link>

to: string

链接位置的字符串表示形式,是通过将location的pathname,search和hash属性连接起来而创建的。

<Link to="/courses?sort=name" />

to: object

可以具有以下任何属性的对象:

  • pathname: 表示要链接到的路径的字符串。
  • search: query参数的字符串表示形式。
  • hash: 网址中的hash值,例如#a-hash。
  • state: 状态保留到该属性中,这个属性设置的内容会被传递到location.state

    <Link
    to={{
      pathname: "/courses",
      search: "?sort=name",
      hash: "#the-hash",
      state: { fromDashboard: true }
    }}
    />

to: function

将当前位置作为参数传递给它的函数,该函数应该以字符串或对象的形式返回位置信息

<Link to={location => ({ ...location, pathname: "/courses" })} />
<Link to={location => `${location.pathname}?sort=name`} />

replace: bool

如果为true,则将单击链接替换为history记录堆栈中的当前条目,而不是添加一条新条目。
这样就没有回退功能了,因为它是把当前URL地址替换掉,不会产生历史记录。

<Link to="/courses" replace />

innerRef: function

从React Router 5.1开始,如果您使用的是React16,则不需要此props,因为我们会将ref转发到基础。允许访问组件的基础引用。

<Link
  to="/"
  innerRef={node => {
    // “node”指的是被挂载的DOM元素
    // 组件被卸载时为null
  }}
/>

innerRef: RefObject

从React Router 5.1开始,如果您使用的是React16,则不需要此props,因为我们会将ref转发到基础使用React.createRef获取组件的基础引用。

let anchorRef = React.createRef()
<Link to="/" innerRef={anchorRef} />

其他

您还可以传递想要在上显示的props,例如title,id,className等。

<NavLink>

<Link>的特殊版本,当它与当前URL匹配时,它将为渲染的元素添加样式属性。

<NavLink to="/about">About</NavLink>

activeClassName: string

当元素处于active时给该元素设置的class,默认给定的class是active的,这将与className属性连接在一起。

<NavLink to="/faq" activeClassName="selected">
  FAQs
</NavLink>

activeStyle: object

元素处于active状态时应用于该元素的样式。

<NavLink
  to="/faq"
  activeStyle={{
    fontWeight: "bold",
    color: "red"
  }}
>
  FAQs
</NavLink>

exact: bool

如果为true,则仅在locatiuon完全匹配时才应用active的class或style。

<NavLink exact to="/profile">
  Profile
</NavLink>

strict: bool

如果为true,则在确定位置是否与当前URL匹配时,将会考虑位置路径名上的斜杠,它需要和<Route>配合使用。有关更多信息,请参见\<Route strict\>文档。

// 严格模式,无法匹配,URL必须要一模一样才能匹配上
<NavLink strict to="/events">
  Events
</NavLink>
<Switch>
    <Route path={'/events/'} strict children={<Events />} />
</Switch>

isActive: func

一种添加额外逻辑以确定链接是否处于active状态的功能。如果您要做的事情不仅仅是验证链接的路径名是否与当前URL的路径名匹配,则可以使用此选项。

<NavLink
  to="/events/123"
  isActive={(match, location) => {
    if (!match) {
      return false;
    }

    // 仅当事件id为奇数时元素才为active状态
    const eventID = parseInt(match.params.eventID);
    return !isNaN(eventID) && eventID % 2 === 1;
  }}
>
  Event 123
</NavLink>

location: object

isActive比较当前历史记录位置(通常是当前浏览器URL)。如果要与其他location进行比较,可以传递一个位置。

aria-current: string

在active链接上使用的aria-current属性的值。可用值为:

  • "page"- 用于指示一组分页链接中的链接
  • "step"- 用于指示基于步骤的过程的步骤指示器中的链接
  • "location"- 用于指示视觉上突出显示的图像作为流程图的当前组成部分
  • "date"- 用于指示日历中的当前日期
  • "time"- 用于指示时间表中的当前时间
  • "true"- 用于指示NavLink是否处于活动状态
    默认值为 "page"
    基于WAI-ARIA 1.1规范

    import React from 'react';
    import ReactDOM from 'react-dom';
    import { HashRouter, Switch, Route, NavLink } from 'react-router-dom';
    
    function Home() {
    return <h1>Home</h1>
    }
    
    function About() {
    return <h1>About</h1>
    }
    
    const Charts = () => <h1>Charts</h1>;
    const Table = () => <h1>Table</h1>;
    const FAQ = () => <h1>FAQ</h1>;
    const Events = () => <h1>Events</h1>;
    
    function App() {
    return <div className={'app'}>
        <HashRouter hashType={'noslash'}>
            <ul>
                <li>
                    <NavLink to={'/home'} className={'home'}>Home</NavLink>
                </li>
                <li>
                    <NavLink to={'/about'} className={'about'}>About</NavLink>
                </li>
                <li>
                    <NavLink to={'/charts'} className={'charts'} activeClassName={'selected'}>Charts</NavLink>
                </li>
                <li>
                    <NavLink to={'/table'} className={'table'} activeClassName={'selected'}>Table</NavLink>
                </li>
                <li>
                    <NavLink to={'/faq'} activeStyle={{ fontWeight: 'bold', color: 'red' }}>FAQ</NavLink>
                </li>
                <li>
                    <NavLink strict to="/events">Events</NavLink>
                </li>
            </ul>
            <Switch>
                <Route path={'/home'} children={<Home/>} />
                <Route path={'/about'} children={<About/>} />
                <Route path={'/charts'} children={<Charts/>} />
                <Route path={'/table'} children={<Table/>} />
                <Route path={'/faq'} children={<FAQ />} />
                <Route path={'/events/'} strict children={<Events />} />
            </Switch>
        </HashRouter>
    </div>
    }
    
    ReactDOM.render(<App />, document.querySelector('#root'))

<Prompt>

用于在离开页面之前提示用户。当您的应用程序进入应阻止用户导航的状态时(例如,表单已被半填满),请渲染<Prompt>。

<Prompt
  when={formIsHalfFilledOut}
  message="您确定要离开吗?"
/>

message: string

当用户尝试离开时提示用户的消息。

<Prompt message="Are you sure you want to leave?" />

message: func

将与用户尝试导航到的下一个位置和操作一起调用。返回一个字符串以向用户显示提示,或者返回true以允许过渡。

<Prompt
  message={location =>
    location.pathname.startsWith("/app")
      ? true
      : `Are you sure you want to go to ${location.pathname}?`
  }
/>

when: bool

您可以始终渲染它,而可以通过when={true}或when={false}来阻止或允许进行相应的导航,而不是通过条件控制是否渲染<Prompt>。

<Prompt when={formIsHalfFilledOut} message="Are you sure?" />
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Route, Link, Prompt } from 'react-router-dom';

function Home() {
    return <h1>Home</h1>
}

function Table() {
    return <h1>Table</h1>;
}

function Charts() {
    return <h1>Charts</h1>
}

function About() {
    return <h1>About</h1>
}

function App() {
    return <BrowserRouter>
        <Prompt message={location => {
            if (location.pathname !== '/home') {
                return `您确定要前往${location.pathname}吗?`
            } else {
                return true;
            }
            return true;
        }} when={true} />
        <ul>
            <li>
                <Link to={'/home'}>Home</Link>
            </li>
            <li>
                <Link to={'/table'}>Table</Link>
            </li>
            <li>
                <Link to={'/charts'}>Charts</Link>
            </li>
            <li>
                <Link to={'/about'}>About</Link>
            </li>
        </ul>
        <Switch>
            <Route path={'/home'} children={props => <Home {...props} />} />
            <Route path={'/table'} render={props => <Table {...props} />} />
            <Route path={'/charts'} children={props => <Charts {...props} />} />
            <Route path={'/about'} render={props => <About {...props} />} />
        </Switch>
    </BrowserRouter>;
}

ReactDOM.render(<App />, document.querySelector('#root'));

<Redirect>

渲染<Redirect>将导航到新位置。新位置将覆盖历史记录堆栈中的当前位置,就像服务器端重定向(HTTP 3xx)一样。

<Route exact path="/">
  {loggedIn ? <Redirect to="/dashboard" /> : <PublicHomePage />}
</Route>

to: string

重定向到的URL。path-to-regexp@^1.7.0可以理解的任何有效URL路径。to中使用的所有URL参数必须由from覆盖。

<Redirect to="/somewhere/else" />

to: object

重定向到的位置。路径名可以是path-to-regexp@^1.7.0可以理解的任何有效URL路径。

<Redirect
  to={{
    pathname: "/login",
    search: "?utm=your+face",
    state: { referrer: currentLocation }
  }}
/>

可以通过重定向到组件中的this.props.location.state访问状态对象。然后,可以通过路径名"/login"指向的Login组件中的this.props.location.state.referrer访问此新的引用关键字(不是特殊名称)。

push: bool

<Redirect push to="/somewhere/else" />

设置为true时,重定向会将新条目推入历史记录,而不是替换当前条目。

from: string

要重定向的路径名。 path-to-regexp@^1.7.0可以理解的任何有效URL路径。所有匹配的URL参数都提供给模式中的to。必须包含用于to中的所有参数。to不使用的其他参数将被忽略。

<Switch>
  <Redirect from='/old-path' to='/new-path' />
  <Route path='/new-path'>
    <Place />
  </Route>
</Switch>

// 使用匹配的参数重定向
<Switch>
  <Redirect from='/users/:id' to='/users/profile/:id'/>
  <Route path='/users/profile/:id'>
    <Profile />
  </Route>
</Switch>

示例:

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Route, Redirect, Link, useParams } from 'react-router-dom';

function App() {
    return(
        <BrowserRouter>
            <ul>
                <li>
                    <Link to={'/home'}>Home</Link>
                </li>
                <li>
                    <Link to={'/charts/123123'}>Charts</Link>
                </li>
                <li>
                    <Link to={'/profile/111'}>Profile</Link>
                </li>
            </ul>
            <Switch>
                <Route path={'/home'} render={props => <Home {...props} />} />
                <Route path={'/profile/:id'} render={props => <Profile {...props} />} />
                <Redirect from={'/charts/:id'} to={'/profile/:id'} />
            </Switch>
        </BrowserRouter>
    )
}
function Home() {
    return <h1>Home</h1>
}
function Profile() {
    const params = useParams();
    console.log('params=>', params);
    return <>
        <h1>Profile</h1>
    </>
}
ReactDOM.render(<App />, document.querySelector('#root'));

exact: bool

完全匹配;等同于Route.exact
注意:只有在<Switch>内渲染<Redirect>时,才能与from结合使用,以完全匹配位置。有关更多详细信息,请参见\<Switch children\>

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Route, Link, Redirect } from 'react-router-dom';

const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
const App = () => <BrowserRouter>
    <ul>
        <li>
            <Link to={'/home'}>Home</Link>
        </li>
        <li>
            <Link to={'/about'}>About</Link>
        </li>
    </ul>
    <Switch>
        <Route path={'/home'} render={props => <Home {...props} />} />
        <Route path={'/about'} children={props => <About {...props} />} />
        {/*这个一定要放到Route后面,等Route渲染完了,才可以重定向*/}
        <Redirect exact from={'/'} to={'/home'} />
    </Switch>
</BrowserRouter>;

ReactDOM.render(<App />, document.querySelector('#root'));

strict: bool

严格匹配;等同于Route.strict
注意:只有在<Switch>内部渲染<Redirect>时,此选项只有与from一起使用才能以严格匹配位置。有关更多详细信息,请参见\<Switch children\>

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Route, Link, Redirect } from 'react-router-dom';

const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
const App = () => <BrowserRouter>
    <ul>
        <li>
            <Link to={'/home'}>Home</Link>
        </li>
        <li>
            <Link to={'/about'}>About</Link>
        </li>
        <li>
            <Link to={'/one'}>One</Link>
        </li>
    </ul>
    <Switch>
        <Route path={'/home'} render={props => <Home {...props} />} />
        <Route path={'/about'} children={props => <About {...props} />} />
        {/*这个一定要放到Route后面,等Route渲染完了,才可以重定向*/}
        <Redirect strict from="/one/" to="/home" />
    </Switch>
</BrowserRouter>;

ReactDOM.render(<App />, document.querySelector('#root'));

sensitive: bool

区分大小写匹配;等同于Route.sensitive

<Route sensitive path="/one">
  <About />
</Route>
pathlocation.pathnamesensitive是否匹配
/one/onetrueyes
/One/onetrueno
/One/onefalseyes

<Router>

Router组件可能是React Router中了解和学习使用的最重要组件。它的最基本职责是在其路径与当前URL匹配时显示一些UI。
研究以下代码:

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";

ReactDOM.render(
  <Router>
    <div>
      <Route exact path="/">
        <Home />
      </Route>
      <Route path="/news">
        <NewsFeed />
      </Route>
    </div>
  </Router>,
  node
);

如果应用程序的location是/,则UI层次结构将类似于:

<div>
  <Home />
  <!-- react-empty: 2 -->
</div>

如果应用程序的location是/news,则UI层次结构将是:

<div>
  <!-- react-empty: 1 -->
  <NewsFeed />
</div>

"react-empty"注释只是React空渲染的实现细节。但是出于我们的目的,这是有益的。从技术上讲,即使始终为空,也总是对其进行"渲染"。当<Route>的路径与当前URL匹配时,它将渲染其子级(您的组件)。

Route render methods

使用<Route>渲染某些内容的方法建议使用子元素,如上所示。 但是,还有一些其他方法可用于使用<Route>渲染内容。 提供这些主要是为了支持在引入钩子之前使用早期版本的路由器构建的应用程序。

Route props

所有这三种渲染方法将通过相同的三个路由props

component

一个仅在location匹配时才渲染的React组件。 它将与route props一起渲染。

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";

// 用户可以使用所有route props(match, location and history)
function User(props) {
  return <h1>Hello {props.match.params.username}!</h1>;
}

ReactDOM.render(
  <Router>
    <Route path="/user/:username" component={User} />
  </Router>,
  node
);

当您使用组件(而不是下面的渲染器或子组件)时,路由器会使用React.createElement从给定的组件中创建一个新的React元素。这意味着,如果您向组件prop提供内联函数,则将在每个渲染中创建一个新组件。这意味着,如果您向组件prop提供内联函数,则将在每个渲染中创建一个新组件。这将导致现有组件的卸载和新组件的安装,而不仅仅是更新现有组件。使用内联函数进行内联渲染时,请使用render或children属性(如下)。

render: func

这样可以方便地进行内联渲染和包装,而无需进行上述不必要的重新安装。
无需使用组件prop为您创建新的React元素,而是可以传递位置匹配时要调用的函数。render函数可以访问与组件渲染相同的所有route属性(match,location和history)。

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";

// 方便的内联渲染
ReactDOM.render(
  <Router>
    <Route path="/home" render={props => <div>Home</div>} />
  </Router>,
  node
);
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";
// wrapping/composing
// 您可以传播 route 属性 以使它们可用于渲染的组件
function FadingRoute({ component: Component, ...rest }) {
  return (
    <Route
      {...rest}
      render={routeProps => (
        <FadeIn>
          <Component {...routeProps} />
        </FadeIn>
      )}
    />
  );
}

ReactDOM.render(
  <Router>
    <FadingRoute path="/cool" component={Something} />
  </Router>,
  node
);

警告:<Route component>优先于<Route render>,因此请勿在同一<Route>中同时使用两者。

children: func

有时您需要渲染路径是否与位置匹配。 在这种情况下,您可以使用child道具功能。 它与render完全一样,除了是否存在匹配项而被调用。
子级渲染属性接收组件渲染函数相同的所有路由属性,除非路由未能与URL匹配,则match为null。 这样您可以根据路由是否匹配来动态调整UI。 如果路由匹配,我们在这里添加一个active class。

import React from "react";
import ReactDOM from "react-dom";
import {
  BrowserRouter as Router,
  Link,
  Route
} from "react-router-dom";

function ListItemLink({ to, ...rest }) {
  return (
    <Route
      path={to}
      children={({ match }) => (
        <li className={match ? "active" : ""}>
          <Link to={to} {...rest} />
        </li>
      )}
    />
  );
}

ReactDOM.render(
  <Router>
    <ul>
      <ListItemLink to="/somewhere" />
      <ListItemLink to="/somewhere-else" />
    </ul>
  </Router>,
  node
);

这对于动画也可能有用:

<Route
  children={({ match, ...rest }) => (
    {/* Animate将始终进行渲染,因此您可以使用生命周期来对其子对象进行动画制作 */}
    <Animate>
      {match && <Something {...rest}/>}
    </Animate>
  )}
/>

警告:<Route children>优先于<Route component>和<Route render>,因此请不要在同一<Route>中使用多个。

path: string | string[]

如果为true,则仅在路径与location.pathname完全匹配时才匹配。

<Route exact path="/one">
  <About />
</Route>
pathlocation.pathnameexact是否匹配
/one/one/twotrueno
/one/one/twofalseyes

strict: bool

设置为true时,带有斜杠的路径将只匹配带有斜杠的location.pathname。当location.pathname中有其他URL段时,这无效。

<Route strict path="/one/">
  <About />
</Route>
path    
pathlocation.pathname是否匹配
/one//oneno
/one//one/yes
/one//one/twoyes

警告:strict可以用于强制location.pathname不带斜杠,但是要做到这一点,strict和exact都必须为true。

<Route exact strict path="/one">
  <About />
</Route>
pathlocation.pathname是否匹配
/one/oneyes
/one/one/no
/one/one/twono

location: object

<Route>元素尝试将其路径与当前历史记录位置(通常是当前浏览器URL)匹配。但是,也可以传递路径名不同的位置进行匹配。

在需要将<Route>匹配到当前历史记录位置以外的位置时,这很有用,如Animated Transitions示例所示。

如果<Route>元素包装在<Switch>中并且与传递给<Switch>的位置(或当前历史记录位置)匹配,则传递给<Route>位置的prop将被<Switch>使用的那个props覆盖(此处给出)。

sensitive: bool

为true时,如果路径区分大小写,则将匹配。

<Route sensitive path="/one">
  <About />
</Route>
pathlocation.pathnamesensitive是否匹配
/one/onetrueyes
/One/onetrueno
/One/onefalseyes

<Router>

所有路由器组件的通用底层接口。通常,应用将使用高级路由器之一代替:

使用底层<Router>的最常见用例是将自定义历史记录与状态管理库(如Redux或Mobx)进行同步。 请注意,并不需要将状态管理库与React Router一起使用,它仅用于深度集成。

import React from "react";
import ReactDOM from "react-dom";
import { Router } from "react-router";
import { createBrowserHistory } from "history";

const history = createBrowserHistory();

ReactDOM.render(
  <Router history={history}>
    <App />
  </Router>,
  node
);

history: object

用于导航的history对象

import React from "react";
import ReactDOM from "react-dom";
import { createBrowserHistory } from "history";

const customHistory = createBrowserHistory();

ReactDOM.render(<Router history={customHistory} />, node);

children: node

要渲染的子元素。

<Router>
  <App />
</Router>

<StaticRouter>

永远不会更改位置的<Router>。

当用户实际上没有四处点击时,这在服务器端渲染方案中很有用,因此位置永远不会发生实际变化。 因此,名称为:static。它在简单测试中也很有用,您只需要插入一个位置并在渲染输出中进行断言时。

示例:这是一个node服务器,它为<Redirect>发送302状态代码,并为其他请求发送常规HTML:

requests:import http from "http";
import React from "react";
import ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router";

http
  .createServer((req, res) => {
    // This context object contains the results of the render
    const context = {};

    const html = ReactDOMServer.renderToString(
      <StaticRouter location={req.url} context={context}>
        <App />
      </StaticRouter>
    );

    // context.url will contain the URL to redirect to if a <Redirect> was used
    if (context.url) {
      res.writeHead(302, {
        Location: context.url
      });
      res.end();
    } else {
      res.write(html);
      res.end();
    }
  })
  .listen(3000);

basename: string

所有位置的基本URL。格式正确的基本名称应以斜杠开头,但不能以斜杠结尾。

<StaticRouter basename="/calendar">
  <Link to="/today"/> // renders <a href="/calendar/today">
</StaticRouter>

location: string

服务器收到的URL,可能是node服务器上的req.url.

<StaticRouter location={req.url}>
  <App />
</StaticRouter>

location: object

形状为{ pathname, search, hash, state }的location对象

<StaticRouter location={{ pathname: "/bubblegum" }}>
  <App />
</StaticRouter>

context: object

一个普通的JavaScript对象。在渲染期间,组件可以向对象添加属性以存储有关渲染的信息

const context = {}
<StaticRouter context={context}>
  <App />
</StaticRouter>

当<Route>匹配时,它将把上下文对象传递给它作为staticContext属性呈现的组件。请查看服务器渲染指南,以获取有关如何自行执行此操作的更多信息。

渲染后,这些属性可用于配置服务器的响应。

if (context.status === "404") {
  // ...
}

children: node

要渲染的子元素。

注意:在React <16上,您必须使用单个子元素,因为render方法不能返回多个元素。如果需要多个元素,则可以尝试将它们包装在额外的<div>

<Switch>

渲染与位置匹配的第一个子元素<Route>或<Redirect>。
这与仅使用一堆<Route>有什么不同?
<Switch>的独特之处在于它专门渲染一条路由。相反,每个与该位置匹配的<Route>都将进行包含性渲染。研究以下route:

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';

function SwitchExample() {
    return <BrowserRouter>
        <Route path="/about">
            <h1>About</h1>
        </Route>
        <Route path="/:user">
            <h1>User</h1>
        </Route>
        <Route>
            <h1>NoMatch</h1>
        </Route>
    </BrowserRouter>;
}

ReactDOM.render(<SwitchExample />, document.querySelector('#root'));

如果URL是/about,则渲染<About>,<User>和<NoMatch>将全部渲染,因为它们都与所有路径都匹配。这是设计使然,允许我们以多种方式将<Route>组合到我们的应用中,例如边栏和面包屑,引导程序标签等。

但是,有时我们只选择一个<Route>进行渲染。如果我们位于/about,我们不想同时匹配/:user(或显示"404"页面)。使用Switch的方法如下:

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Route } from 'react-router-dom';

const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
const User = () => <h1>User</h1>;
const NoMatch = () => <h1>NoMatch</h1>;

function SwitchExample() {
    return <BrowserRouter>
        <Switch>
            <Route exact path='/'>
                <Home />
            </Route>
            <Route path='/about'>
                <About />
            </Route>
            <Route path='/:user'>
                <User />
            </Route>
            <Route>
                <NoMatch />
            </Route>
        </Switch>
    </BrowserRouter>
}

ReactDOM.render(<SwitchExample />, document.querySelector('#root'));

现在,如果我们位于/about,<Switch>将开始寻找匹配的<Route>。<Route path ="/about" />将匹配,而<Switch>将停止寻找匹配并渲染<About>。同样,如果我们在/michael位置,则会显示<User>。

这对于动画过渡也很有用,因为匹配的<Route>呈现在与上一个相同的位置。

let routes = (
  <Fade>
    <Switch>
      {/* 这里只有一个子元素 */}
      <Route />
      <Route />
    </Switch>
  </Fade>
);

let routes = (
  <Fade>
    {/* 这里永远有两个子元素,但是可能会呈现null,进行转换,计算起来有点麻烦 */}
    <Route />
    <Route />
  </Fade>
);

location: object

用于匹配子元素的位置对象,而不是当前历史记录位置(通常是当前浏览器URL)。

children: node

<Switch>的所有子代应为<Route>或<Redirect>元素。仅第一个与当前位置匹配的子元素会被渲染。
<Route>元素使用其path属性进行匹配,而<Redirect>元素使用其from属性进行匹配。没有path属性的<Route>或没有from属性的<Redirect>将始终与当前位置匹配。
在<Switch>中包含<Redirect>时,它可以使用<Route>的任何位置匹配属性:path,exact和strict。 from只是path属性的别名。
如果给<Switch>一个location属性,它将覆盖匹配的子元素上的location属性。

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';

const Home = () => <h1>Home</h1>;
const User = () => <h1>User</h1>;
const NoMatch = () => <h1>NoMatch</h1>;

function SwitchExample() {
    return <BrowserRouter>
        <Switch>
            <Route exact path="/">
                <Home />
            </Route>
            <Route path="/user">
                <User />
            </Route>
            <Redirect from="/account" to="/user" />
            <Route>
                <NoMatch /> 
            </Route>
        </Switch>
    </BrowserRouter>;
}

ReactDOM.render(<SwitchExample />, document.querySelector('#root'));

history

本文档中的"history"和"history对象"一词是指history包,它是React Router仅有的两个主要依赖项之一(除了React本身)并且提供了几种不同的实现,用于在各种环境中管理JavaScript中的会话历史记录。

也使用以下术语:

  • "browser history" - 特定于DOM的实现,在支持HTML5历史记录API的Web浏览器中很有用.
  • "hash history" - 遗留Web浏览器的DOM特定实现.
  • "memory history" - 内存历史记录实现,可用于测试和像React Native这样的非DOM环境.

history对象通常具有以下属性和方法:

  • length -(number)历史记录堆栈中的条目数
  • action - (string)当前操作(PUSH,REPLACE或POP)
  • location - (object)当前位置。可能具有以下属性:

    • pathname - (string)URL的路径
    • search - (string)URL查询字符串
    • hash - (string)URL哈希片段
    • state - (object)提供给例如当此位置被压入堆栈时,push(path,state)。仅在browser和memory history中可用。
  • push(path, [state]) - (function)将新条目推入历史记录堆栈
  • replace(path, [state]) - (function)替换历史记录堆栈上的当前条目
  • go(n) - (function)将历史记录堆栈中的指针移动n个条目
  • goBack() - (function)相当于go(-1)
  • goForward() - (function)相当于go(1)
  • block(prompt) - (function)防止导航(请参阅history文档

history是可变的

history对象是可变的,因此,建议从\<Route\>的渲染属性中访问location,而不是从history.location中访问。这确保了您对React的假设在生命周期钩子中是正确的。例如:

class Comp extends React.Component {
  componentDidUpdate(prevProps) {
    // 将为 true
    const locationChanged =
      this.props.location !== prevProps.location;

    // 不正确,由于history是可变的,因此*总是*为假。
    const locationChanged =
      this.props.history.location !== prevProps.history.location;
  }
}

<Route component={Comp} />;

根据您所使用的实现方式,可能还会显示其他属性。请参阅history文档以获取更多详细信息。

location

location表示该应用程序现在的位置,您希望其运行的位置,甚至是以前的位置。看起来像这样:

{
  key: 'ac3df4', // not with HashHistory!
  pathname: '/somewhere',
  search: '?some=search-string',
  hash: '#howdy',
  state: {
    [userDefined]: true
  }
}

router将在几个地方为您提供location对象:

也可以在history.location上找到它,但是您不应使用它,因为它是可变的。您可以在history文档
中阅读有关此内容的更多信息.

location对象永远不会发生变化,因此您可以在生命周期钩子中使用它来确定何时进行导航,这对于数据获取和动画处理非常有用。

componentWillReceiveProps(nextProps) {
  if (nextProps.location !== this.props.location) {
    // navigated!
  }
}

你可以提供位置,而不是字符串导航到不同的地方:

通常您只需要使用一个字符串,但是,如果您需要添加一些“位置状态”,只要应用返回到该特定位置即可使用,则可以使用location对象代替。如果您要基于导航历史而不是仅基于路径(如 modals)来分支UI,这将非常有用。

// 通常你所需要的
<Link to="/somewhere"/>

// 但是你可以使用location来代替
const location = {
  pathname: '/somewhere',
  state: { fromDashboard: true }
}

<Link to={location}/>
<Redirect to={location}/>
history.push(location)
history.replace(location)

最后,您可以将location传递给以下组件:

这样可以防止他们在路由器状态下使用实际位置。这对于动画和待处理的导航很有用,或者在您想要诱使组件在与真实位置不同的位置进行渲染时,这很有用。

match

match对象包含有关<Route path>如何与URL匹配的信息。匹配对象包含以下属性:

  • params- (object)从与路径的动态段相对应的URL解析的键/值对
  • isExact- (boolean)如果整个网址都匹配,则为“ true”(不包含结尾字符)
  • path- (string) 用于匹配的路径模式。对于构建嵌套的<Route>有用
  • url- (string) URL的匹配部分。对于构建嵌套的<Link>有用

您将可以在各个地方使用match对象:

如果Route没有path,因此会始终匹配,它将获取最接近的父项匹配项。和withRouter一样。

null matches

即使子路径的路径与当前位置不匹配,使用子项道具的<Route>也会调用其子函数。 在这种情况下,匹配将为空。 能够在匹配时呈现<Route>的内容可能会很有用,但是这种情况会带来一些挑战。

"解析"URL的默认方法是将match.url字符串连接到"相对"路径。

let path = `${match.url}/relative-path`;

如果在匹配为null时尝试执行此操作,则最终将出现TypeError。这意味着在使用children prop时尝试在<Route>内部加入"relative"路径是不安全的。

当在生成空匹配对象的<Route>中使用无路径<Route>时,会发生类似但更微妙的情况。

// location.pathname = '/matches'
<Route path="/does-not-match"
  children={({ match }) => (
    // match === null
    <Route
      render={({ match: pathlessMatch }) => (
        // pathlessMatch === ???
      )}
    />
  )}
/>

无路径<Route>从其父级继承其match对象。如果其父匹配项为null,则其匹配项也将为null。这意味着任何子级路由/链接都必须是绝对的,因为没有父级可以解析,并且父级匹配可以为null的无路径路由将需要使用子级prop进行渲染。

matchPath

这允许您使用与<Route>相同的匹配代码(除了正常的渲染周期之外),比如在服务器上呈现之前收集数据依赖关系。

import { matchPath } from "react-router";

const match = matchPath("/users/123", {
  path: "/users/:id",
  exact: true,
  strict: false
});

pathname

第一个参数是您要匹配的路径名。如果您是在Node.js的服务器上使用它,则为req.path。

props

第二个参数是要匹配的props,它们与Route接受的匹配props相同。它也可以是字符串或字符串数​​组,作为{path}的快捷方式:

{
  path, // 像/users/:id;单个字符串或字符串数​​组
  strict, // 可选,默认为false
  exact, // 可选,默认为false
}

returns

当提供的路径名与路径属性匹配时,它将返回一个对象。

matchPath("/users/2", {
  path: "/users/:id",
  exact: true,
  strict: true
});

//  {
//    isExact: true
//    params: {
//        id: "2"
//    }
//    path: "/users/:id"
//    url: "/users/2"
//  }

如果提供的路径名与路径属性不匹配,则返回null。

matchPath("/users", {
  path: "/users/:id",
  exact: true,
  strict: true
});

//  null

withRouter

您可以通过withRouter高阶组件访问history对象的属性和最接近的\<Route\>匹配项。每当渲染时,withRouter都会将更新的match,location和history属性传递给包装的组件。

import React from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";

// 一个简单的组件,显示当前位置的路径名
class ShowTheLocation extends React.Component {
  static propTypes = {
    match: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired
  };

  render() {
    const { match, location, history } = this.props;

    return <div>You are now at {location.pathname}</div>;
  }
}

// 创建一个“连接”到路由器的新组件(借用redux术语)。
const ShowTheLocationWithRouter = withRouter(ShowTheLocation);

重要的提示:
withRouter不像React Redux的connect那样订阅位置更改以进行状态更改。而是在位置更改后从<Router>组件传播出去后重新渲染。这意味着withRouter不会在路由转换时重新渲染,除非其父组件重新渲染。
静态方法和属性
包装组件的所有非特定于反应的静态方法和属性将自动复制到“connected”组件。

Component.WrappedComponent

包装的组件在返回的组件上作为静态属性WrappedComponent公开,它可以用于隔离测试组件等。

// MyComponent.js
export default withRouter(MyComponent)

// MyComponent.test.js
import MyComponent from './MyComponent'
render(<MyComponent.WrappedComponent location={{...}} ... />)

wrappedComponentRef: func

该函数将作为ref prop传递给包装的组件。

class Container extends React.Component {
  componentDidMount() {
    this.component.doSomething();
  }

  render() {
    return (
      <MyComponent wrappedComponentRef={c => (this.component = c)} />
    );
  }
}

Jameswain
332 声望12 粉丝