react-router-dom@5.x官方文档翻译

简介

这是我学习react-router-dom@5.1.2时,为了加深本身对react-router-dom的理解和帮助一些英文很差的同窗,对官方文档进行了翻译,本人水平有限,若有理解和翻译错误,欢迎你们指正。官网地址html

快速入门

要在web应用中开始使用React Router,您将须要一个React Web应用程序.若是您须要建立一个,咱们建议您尝试Create React App。这是一个很是流行的工具,可与React Router很好地配合使用。node

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

安装

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

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中。git

第一个示例:基本路由

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

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

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>。ajax

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中主要组件的更多信息!express

主要组件

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

  • 路由器,例如<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>
    );
  }
}

通用解决方案

对于通用解决方案(以及哪些浏览器已开始在本机实现),咱们谈论的是两件事:
一、向上滚动导航,这样就不会启动滚动到底部的新屏幕
二、恢复窗口的滚动位置和“后退”和“前进”单击上的溢出元素(但不单击“连接”单击!)
在某一时刻,咱们但愿提供一个通用的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");
});

备选方案:
一、若是您的测试环境具备浏览器全局变量window.location和window.history(这是经过JSDOM在Jest中的默认设置,但您没法重置测试之间的历史记录),则也可使用BrowserRouter。
二、您能够将基本路由器与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不会更新)不会更新。

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

问题在于Redux实现了shouldComponentUpdate,若是没有从路由器接收props,则没有任何迹象代表发生了任何变化。
这很容易姐姐,找到链接组件的位置,而后将组件使用withRouter包装在一块儿

深度集成

有些人想:
一、从store同步路由数据,并从store访问路由数据。
二、能够经过dispatch action操做导航
三、在Redux devtools中支持对路径更改进行时间行程调试。

全部这些都须要更深刻的集成。

咱们的建议是不要将routes彻底保留在Redux store中。论证:
一、路由数据已经成为大多数关心它的组件的支持。 不管是来自store仍是router,您组件的代码都基本相同。
二、在大多数状况下,您可使用Link,NavLink和Redirect执行导航操做。有时您可能还须要以编程方式进行导航,有时您可能还须要以编程方式导航,在某个操做最初启动的异步任务以后。例如,您在用户提交登陆表单时调度操做。而后,您的使用thunksaga或其余异步处理程序会对凭据进行身份验证,若是成功,则须要以某种方式导航到新页面。此处的解决方案只是将history对象(提供给全部路由组件)包括在操做的payload,而且异步处理程序能够在适当的时候使用此对象进行导航。
三、路线更改对于时间行程调试不过重要。惟一明显的状况是调试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>
path location.pathname sensitive 是否匹配
/one /one true yes
/One /one true no
/One /one false yes

<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。 请参阅下面的说明以了解它们之间的区别。

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>
path location.pathname exact 是否匹配
/one /one/two true no
/one /one/two false yes

strict: bool

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

<Route strict path="/one/">
  <About />
</Route>
path
path location.pathname 是否匹配
/one/ /one no
/one/ /one/ yes
/one/ /one/two yes

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

<Route exact strict path="/one">
  <About />
</Route>
path location.pathname 是否匹配
/one /one yes
/one /one/ no
/one /one/two no

location: object

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

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

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

sensitive: bool

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

<Route sensitive path="/one">
  <About />
</Route>
path location.pathname sensitive 是否匹配
/one /one true yes
/One /one true no
/One /one false yes

<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)} />
    );
  }
}
相关文章
相关标签/搜索