React Router 官方文檔翻譯

版本:最新版 5.2.0html

React Router是React的官方路由庫,能夠用於web端,node.js服務端,和React Native。React Router是一個React組件、hooks和工具函數的集合。node

React Router包由三部分組成:react

  • react-router:包含了React Router的大多數核心功能,包括路由匹配算法和大多數核心組件以及hooks。
  • react-router-dom:除了react-router的內容以外,還添加了一些DOM相關的API,如<BrowserRouter>, <HashRouter>, <Link>等。
  • react-router-native:除了react-router的內容以外,還添加了一些React Native相關的API,如<NativeRouter>和native版的<Link>

當安裝react-router-dom 和 react-router-native時,會自動包含react-router做爲依賴,而且還會自動導出react-router裏的全部東西。因此使用時只須要安裝react-router-dom或react-router-native便可,不要直接使用react-router。webpack

總之,對於web瀏覽器(包括服務端渲染),使用react-router-dom;對於React Native app,使用react-router-native。git

如下文檔主要針對瀏覽器端,即react-router-dom。本文是以實用爲導向的意譯爲主,對於一些可有可無的部分可能會一語帶過。github

指南

快速開始

咱們推薦使用Create React App來建立React應用,而後再使用React Router。web

首先,安裝create-react-app,而後建立一個新項目:ajax

npx create-react-app demo-app
cd demo-app
複製代碼

安裝

你可使用npm或yarn安裝React Router,因爲咱們要建立web app,所以須要使用react-router-dom: yarn add react-router-dom正則表達式

下面是兩個示例,能夠複製到src/App.js算法

示例一:基礎路由

這個例子中,有三個頁面須要路由器處理,當你點擊不一樣的<Link>,路由器會渲染匹配的<Route>。一個<Link>最終會被渲染成一個帶有href的<a>標籤,以便讓那些使用鍵盤導航和屏幕閱讀器的人也能使用這個應用。

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

export default function App() {
  return (
    <Router>
      <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的 */}
        <Switch>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/users">
            <Users />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

function Home() {
  return <h2>Home</h2>;
}

function About() {
  return <h2>About</h2>;
}

function Users() {
  return <h2>Users</h2>;
}
複製代碼

示例二:嵌套路由

這個示例展現瞭如何嵌套路由,/topics路由會加載Topics組件,Topics組件會根據當前路徑的:id的值來決定進一步渲染哪一個<Route>

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

export default function App() {
  return (
    <Router> <div> <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> </div> </Router>
  );
}

function Home() {
  return <h2>Home</h2>;
}

function About() {
  return <h2>About</h2>;
}

function Topics() {
  let match = useRouteMatch();

  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 URL路徑上的路由。
      	  你能夠將這裏的第二個<Route>視爲全部topics的索引頁,或者也能夠看作沒有topic被選中時展現的頁面。 */}
      <Switch>
        <Route path={`${match.path}/:topicId`}>
          <Topic />
        </Route>
        <Route path={match.path}>
          <h3>Please select a topic.</h3>
        </Route>
      </Switch>
    </div>
  );
}

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

基礎組件

React Router中包含三種基礎類型的組件:

  • 路由器(routers), 如 <BrowserRouter><HashRouter>
  • 路由匹配組件(route matchers), 如 <Route><Switch>
  • 路由導航組件(navigation/route changers), 如 <Link>, <NavLink>, 和 <Redirect>

全部這些組件在使用時都從react-router-dom中引入:

import { BrowserRouter, Route, Link } from "react-router-dom";
複製代碼

Routers

router是React Router的核心組件。web項目可使用<BrowserRouter><HashRouter>。二者主要區別在於保存URL和與服務器通訊的方式不一樣:

  • <BrowserRouter>使用常規的URL路徑,看起來比較美觀,可是須要服務器正確設置。具體來說,就是須要你的web服務器對客戶端全部URL都提供同一個頁面。Create React App在開發模式下已經設置好了,生產環境服務端的設置能夠查看這篇文檔
  • <HashRouter>將當前的位置(location)存儲在URL的hash部分,因此URL看起來會像這樣:http://example.com/#/your/page。因爲hash不會發送到服務器,因此服務器不須要作額外的配置。

使用一個Router時,須要將其渲染在根元素,一般會將頂級的<APP>組件包裹在Router中,形如:

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>組件,找到第一個path匹配當前URL的組件就會中止查找,而後渲染這個<Route>組件,忽略其餘全部的。所以你應該把路徑更具體的路由組件放在前面。

若是沒有<Route>成功匹配,<Switch>就什麼都不渲染(null)。

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

function App() {
  return (
    <div> <Switch> {/* 若是當前URL是/about, 這個路由會被渲染,剩下的都會被忽略 */} <Route path="/about"> <About /> </Route> {/* 注意這兩個路由的順序,更具體的path="/contact/:id"排在了path="/contact"的前面 */} <Route path="/contact/:id"> <Contact /> </Route> <Route path="/contact"> <AllContacts /> </Route> {/* 若是以上任何路由都不匹配,這個路由就來兜底。 重要提示:path="/"的路由老是會匹配當前URL,由於全部URL都是以一個/開始 */} <Route path="/"> <Home /> </Route> </Switch> </div>
  );
}

ReactDOM.render(
  <Router> <App /> </Router>,
  document.getElementById("root")
);
複製代碼

很重要的一點是,一個<Route path>老是匹配URL的開頭,而不是整個URL,所以<Route path="/">老是會匹配URL。鑑於此,一般會將<Route path="/">放在<Switch>的最後。可使用<Route exact path="/">來匹配完整URL。

注意:儘管React Router支持在<Switch>以外渲染<Route>組件,可是5.1版本開始咱們推薦你使用useRouteMatch這個hook做爲替代。此外,咱們也不推薦渲染一個沒有path的<Route>,而是使用hook來訪問你須要的變量。

導航組件(改變路由的組件)

React Router提供一個<Link>組件來建立導航連接。當渲染一個<Link>時,實際上會在HTML中渲染一個<a>標籤。

<Link to="/">Home</Link>
// <a href="/">Home</a>
複製代碼

<NavLink>是一個特殊的<Link>,只不過能夠定義激活時的樣式。

<NavLink to="/react" activeClassName="hurray">
  React
</NavLink>

// 當 URL 是 /react時, 渲染結果:
// <a href="/react" className="hurray">React</a>

// 當 URL 是別的時:
// <a href="/react">React</a>
複製代碼

任什麼時候候你想使用強制導航,均可以渲染一個<Redirect>。當一個<Redirect>被渲染時,將會導航到它的to屬性定義的位置:

<Redirect to="/login" />
複製代碼

服務端渲染

在服務端渲染稍微有些不一樣,由於它是無狀態的。最基本的思路是將app包裹在一個無狀態的<StaticRouter>組件中。傳遞來自服務端的請求url好讓路由能夠匹配,再傳遞一個context。

<StaticRouter
  location={req.url}
  context={context}
>
  <App/>
</StaticRouter>
複製代碼

當你在客戶端渲染一個<Redirect>,瀏覽器中的history經過改變state來刷新頁面。在一個靜態的服務端環境咱們沒法改變app的 state。相反,咱們使用context prop來找出渲染的結果是什麼。若是發現context.url存在,就說明app須要重定向,而後咱們能夠從服務端發送一個合適的重定向。

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

if (context.url) {
  // 某個地方渲染了一個 `<Redirect>`
  redirect(301, context.url);
} else {
  // we're good, send the response
}
複製代碼

添加app中具體的context信息

StaticRouter只會添加context.url。但你也許但願一些重定向爲301,一些爲302。或者,若是一些特定分支的UI被渲染,你會想發送一個404響應。又或者他們沒有權限時發送一個401。context prop 是你的,你能夠修改。這裏提供了一種方法來區分301和302重定向:

function RedirectWithStatus({ from, to, status }) {
  return (
    <Route render={({ staticContext }) => { // 客戶端可沒有 `staticContext`,因此要判斷一下 if (staticContext) staticContext.status = status; return <Redirect from={from} to={to} />; }} />
  );
}

// somewhere in your app
function App() {
  return (
    <Switch> {/* some other routes */} <RedirectWithStatus status={301} from="/users" to="/profiles" /> <RedirectWithStatus status={302} from="/courses" to="/dashboard" /> </Switch>
  );
}

// on the server
const context = {};

const markup = ReactDOMServer.renderToString(
  <StaticRouter context={context}> <App /> </StaticRouter>
);

if (context.url) {
  // 可使用在 RedirectWithStatus 中添加的 `context.status`
  redirect(context.status, context.url);
}
複製代碼

404, 401, 或任何其餘狀態

咱們能夠繼續像上面那樣作。建立一個組件,添加一些context,並把這個組件渲染到app中的任何地方來獲取不一樣的狀態碼。

function Status({ code, children }) {
  return (
    <Route render={({ staticContext }) => { if (staticContext) staticContext.status = code; return children; }} />
  );
}
複製代碼

如今你能夠在app中任何你但願添加code到staticContext的地方渲染這個Status組件。

function NotFound() {
  return (
    <Status code={404}> <div> <h1>Sorry, can’t find that.</h1> </div> </Status>
  );
}

function App() {
  return (
    <Switch> <Route path="/about" component={About} /> <Route path="/dashboard" component={Dashboard} /> <Route component={NotFound} /> </Switch>
  );
}
複製代碼

把他們放一塊兒

這不是一個真實的app,但它展現了全部通用的須要放在一塊兒的部分。

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

import App from "./App.js";

http
  .createServer((req, res) => {
    const context = {};

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

    if (context.url) {
      res.writeHead(301, {
        Location: context.url
      });
      res.end();
    } else {
      res.write(` <!doctype html> <div id="app">${html}</div> `);
      res.end();
    }
  })
  .listen(3000);
複製代碼

瀏覽器端:

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

import App from "./App.js";

ReactDOM.render(
  <BrowserRouter> <App /> </BrowserRouter>,
  document.getElementById("app")
);
複製代碼

數據加載

有許多加載數據的方法,但目前沒有明確的最佳實踐,因此咱們力求使用通用的方法,而不會限定在某個具體的方法上。咱們相信React Router能夠很好地適應你的項目中用到的方法。

最基本的要求是須要在渲染前加載數據。React Router導出了matchPath這個靜態方法,這個方法用來在內部匹配location到路由組件。你能夠在服務端使用這個方法來明確在渲染前你的數據依賴將會是什麼。

這個方法的要點是依賴一個靜態路由配置用來渲染路由組件以及經過匹配來肯定渲染前的數據依賴。

const routes = [
  {
    path: "/",
    component: Root,
    loadData: () => getSomeData()
  }
  // 省略
];
複製代碼

而後使用這個配置來渲染路由:

import { routes } from "./routes.js";

function App() {
  return (
    <Switch> {routes.map(route => ( <Route {...route} /> ))} </Switch>
  );
}
複製代碼

而後在服務端,你可能會須要這麼作:

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

// 請求內部
const promises = [];
// 使用 `some` 來模仿 `<Switch>` 只會選擇第一個匹配成功的路由的行爲
routes.some(route => {
  // 這裏使用 `matchPath` 
  const match = matchPath(req.path, route);
  if (match) promises.push(route.loadData(match));
  return match;
});

Promise.all(promises).then(data => {
  // 提供一些數據來讓瀏覽器端在渲染的時候能獲取
});
複製代碼

最後,瀏覽器端將須要選出數據。以上都是你須要實現的要點。

你也可使用 React Router Config 包來協助加載數據以及服務端使用靜態路由配置來渲染。

代碼分割

代碼分割使得用戶不須要一次性下載整個應用,能夠增量漸進下載。爲了實現代碼分割,咱們使用webpack, @babel/plugin-syntax-dynamic-import, 以及 loadable-components

webpack內置支持動態的import();可是,若是你在使用Babel,你就須要使用@babel/plugin-syntax-dynamic-import插件。這是一個只使用語法的插件,這意味這Babel不會作任何額外的轉換。這個插件只是簡單地容許Babel解析動態import,好讓webpack能夠用代碼分割的方式打包他們。你的.babelrc文件可能看起來像這樣:

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

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是一個佔位的組件,在真正的組件顯示前會一直顯示。查看完整文檔

滾動恢復

在早期版本的React Router中,咱們就對滾動恢復提供了開箱即用的支持。 瀏覽器開始使用本身的history.pushState處理滾動恢復,其處理方式和普通瀏覽器導航時的處理方式相同。chrome早就支持了,並且支持的很好。 因爲瀏覽器開始處理「默認狀況」,應用也有不一樣的滾動需求,因此咱們沒有提供默認的滾動管理。

滾到頂部

當導航到一個很長的網頁而且停留在頁面底部時,須要回到頂部:

import { useEffect } from "react";
import { useLocation } from "react-router-dom";

export default function ScrollToTop() {
  const { pathname } = useLocation();

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

  return null;
}
複製代碼

類的寫法:

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);
複製代碼

而後在app上面渲染:

function App() {
  return (
    <Router> <ScrollToTop /> <App /> </Router>
  );
}
複製代碼

若是頁面是經過tab切換路由的,當切換tab時但願頁面可以停留在原先的位置,而不是滾到頂部:

import { useEffect } from "react";

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

  return null;
}

// Render this somewhere using:
// <Route path="..." children={<LongContent />} />
function LongContent() {
  return (
    <div> <ScrollToTopOnMount /> <h1>Here is my long content page</h1> <p>...</p> </div>
  );
}
複製代碼

類的寫法:

import React from "react";

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

  render() {
    return null;
  }
}

// Render this somewhere using:
// <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組件mount時,他們能夠在sessionsStorage中查找滾動位置。

棘手的部分是須要爲不但願管理窗口滾動的狀況定義一個「退出」 API。例如,若是你有一些導航的tab懸浮在頁面內容中,你或許不想滾到頂部,不然tab就看不到了。

當咱們知道了如今Chrome爲咱們管理了滾動位置,明白了不一樣app有着不一樣的滾動需求,咱們有點失去了咱們須要提供一些東西的信念——特別是當人們只是想滾動到頂部時(你能夠直接自行添加)。

此外,咱們不想再作這部分工做了。

哲學

這部分解釋了咱們爲何使用React Router,咱們稱它爲「動態路由」,這不一樣於你也許更爲熟悉的「靜態路由」。

靜態路由(Static Routing)

若是你用過Rails, Express, Ember, Angular等,那你就用過靜態路由了。在這些框架中,你將你的路由聲明爲app初始化的一部分,在任何渲染髮生以前。React Router v4以前大部分都是靜態的。讓咱們看看express是怎麼配置路由的:

// Express 風格的 routing:
app.get("/", handleIndex);
app.get("/invoices", handleInvoices);
app.get("/invoices/:id", handleInvoice);
app.get("/invoices/:id/edit", handleInvoiceEdit);

app.listen();
複製代碼

留意在app listen前路由是怎麼被聲明的。客戶端使用的路由器也相似,在Angular中你先聲明你的路由,而後在渲染前import到頂級的AppModule中。

// Angular 風格的 routing:
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有一個約定的routes.js文件供構建工具讀取和import進應用中。這些也是在渲染前發生的:

// Ember 風格的 Router:
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版本以前都是這麼作的,v4後就不這麼作了。

背後的故事

坦白說,咱們對React Router從v2版本後採起的方向感到很是沮喪,咱們被API限制了,意識到咱們正在從新實現React(例如生命週期等),這和React提供給咱們的組合UI的思惟模型背道而馳。

咱們僅用幾個小時的開發時間證明了咱們將來想要發展的方向。咱們最終獲得的API並非脫離於React的,而是React的一部分或者說天然地融入其中的。我想大家應該會喜歡的。

動態路由

當咱們在說動態路由的時候,咱們實際上在說的是在渲染階段的路由,不是脫離於一個運行中app的配置。這意味着React Router中幾乎全部東西都是一個組件。這裏是一個API的快速預覽來看看它是怎麼工做的:

首先,獲取一個目標環境的Router組件,渲染在app外層:

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

// react-dom (what we'll use here)
import { BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <BrowserRouter> <App /> </BrowserRouter>,
  el
);
複製代碼

而後使用link組件連接到一個新位置:

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

最後渲染一個Route組件來展現UI:

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

Route組件將會渲染 <Dashboard {...props}/>,這裏的props是一些Router相關的例如{ match, location, history }。若是當前路徑不在/dashboard,那麼Route將什麼都不渲染。

嵌套路由

許多路由器都有一些「嵌套路由」的概念。React Router直到v4版本也有這個概念。當你從一個靜態路由配置遷移到動態路由,你怎麼「嵌套路由」?很簡單,因爲Route僅僅是一個組件,因此你能夠像嵌套div同樣作就能夠了:

const App = () => (
  <BrowserRouter> {/* here's a div */} <div> {/* here's a Route */} <Route path="/tacos" component={Tacos} /> </div> </BrowserRouter>
);

// 當url 匹配 `/tacos` 時這個組件將會渲染
const Tacos = ({ match }) => (
  // 這裏是一個嵌套的div
  <div> {/* 這裏是一個嵌套的 Route, match.url 幫助咱們生成一個相對路徑 */} <Route path={match.url + "/carnitas"} component={Carnitas} /> </div>
);
複製代碼

響應式路由

想象這樣一個場景:在小屏幕上,只顯示右側導航欄;在大屏幕上,顯示導航欄和詳情信息。這個場景在用戶旋轉手機從豎屏到橫屏時很常見。若是是靜態路由,並無一個真正的解決方案,可是動態路由就好辦了:

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

const Invoices = () => (
  <Layout> {/* always show the nav */} <InvoicesNav /> <Media query={PRETTY_SMALL}> {screenIsSmall => screenIsSmall ? ( // small screen has no redirect <Switch> <Route exact path="/invoices/dashboard" component={Dashboard} /> <Route path="/invoices/:id" component={Invoice} /> </Switch> ) : ( // large screen does! <Switch> <Route exact path="/invoices/dashboard" component={Dashboard} /> <Route path="/invoices/:id" component={Invoice} /> <Redirect from="/invoices" to="/invoices/dashboard" /> </Switch> ) } </Media> </Layout>
);
複製代碼

當用戶旋轉手機到橫屏時,這幾行代碼將會自動重定向到dashboard。 有效路由集的變化取決於用戶手中移動設備的動態特性。

爲了讓你的直覺與React Router一致,用組件而不是靜態路由的方式去思考。解決問題時考慮如何利用React的聲明式地組合性,由於幾乎每一個「React Router問題」均可能是一個「React問題」。

測試

React Router 依賴 React context 來工做。這會影響如何測試組件。

Context

若是你嘗試使用單元測試來測試一個會渲染出 <Link><Route> 等的組件,將會獲得關於context的error或warning。或許你可能會想本身添加router context,但咱們仍是建議你將單元測試包裹在如下Router組件之一:基礎的Router組件,包括history prop,或<StaticRouter>, <MemoryRouter>, 或 <BrowserRouter>(前提條件是在測試環境中能夠訪問全局的window.history)。

使用 <MemoryRouter> 或一個自定義的history是比較推薦的作法,目的是爲了在測試的時候可以重置router。

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,因此你能夠在特定的location啓動一個app(或app中任意更小的部分)。

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

導航

咱們作了不少測試,當location改變時routes能夠正常工做,因此你不須要親自測試這個。但若是你須要測試你的app裏的導航時,你能夠這樣作:

// app.js (一個組件文件)
import React from "react";
import { Route, Link } from "react-router-dom";

// 這裏測試的目標是 APP,但你也能夠測試你的app裏面更小的模塊。
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 => {
  // in a real test a renderer like "@testing-library/react"
  // would take care of setting up the DOM elements
  const root = document.createElement('div');
  document.body.appendChild(root);

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

  // Interact with page
  act(() => {
    // Find the link (perhaps using the text content)
    const goHomeLink = document.querySelector('#nav-logo-home');
    // Click it
    goHomeLink.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });

  // Check correct page content showed up
  expect(document.body.textContent).toBe('Home');
});
複製代碼

在測試中檢查location

你沒必要在測試中常常訪問location或history對象,可是若是你這麼作了(例如驗證url中設置的新的查詢參數),你能夠在測試中添加一個更新一個變量的route:

// 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.locationwindow.history(這些在Jest中都經過JSDOM默認提供了,可是沒法在測試間重置history),你也可使用BrowserRouter
  2. 比起傳遞一個自定義的route 到 MemoryRouter,你可使用包含從history庫中獲得的history prop的基礎Router
// 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無縫集成,好讓人們能一塊兒使用。一些人提出的想法:

  • 將路由數據同步到store中,並經過store來訪問
  • 能夠經過dispatch actions來改變路由
  • 支持在Redux devtools中就路由改變進行時間旅行debug

全部這些都須要深度集成。咱們給出的觀點是:不要把路由保存在Redux store中。理由以下:

  • 路由數據早就是大部分組件的一個prop了,無論它從store仍是router中來,對組件的代碼基本沒什麼變化
  • 大多數狀況下,你能夠利用Link, NavLink 和 Redirect進行導航。有時,你可能須要在某個操做啓動的異步任務後以編碼的方式來導航。例如,當用戶提交一個登陸表單,你可能會dispatch一個action。你的thunk, saga或其餘異步處理器完成鑑權等操做後,須要導航到一個新頁面。這裏的解決方案只是將history對象(提供給全部路由組件)包含在action的payload中,你的異步處理程序能夠在適當的時候使用它進行導航。
  • 路由變動對時間旅行debugging來講沒那麼重要。惟一明顯的狀況就是調試你的router/store的同步問題,若是你不一樣步它們,這個問題就會消失。

但若是你強烈地想同步你的路由到store中,你或許能夠嘗試一下Connected React Router——一個第三方的庫用來綁定React Router 和 Redux。

靜態路由

以前的版本使用靜態的路由配置,這容許在渲染前檢查和匹配路由,自從v4變爲動態的組件而不是路由配置,一些以前的用例變得很棘手。

咱們開發了一個包來處理靜態路由配置和React Router來繼續知足那些用例。它如今還在開發中,但你能夠試試。React Router Config

Hooks

React Router附帶了一些hooks來讓人能夠訪問router的狀態,以及在組件內部執行導航。 注意,你須要使用React的版本號>=16.8才能使用這些hooks。

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

這個hook返回location對象表示當前的URL。這在有些場景下頗有用,例如當你想觸發一個新的視圖事件,在頁面加載時使用web分析工具:

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

返回一個URL參數的鍵值對的對象。使用它來訪問當前的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

這個hook會像<Route>那樣匹配當前的URL。這在獲取match data可是不須要渲染一個<Route>組件的狀況時最有用。 例如比起這樣寫:

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

function BlogPost() {
  return (
    <Route path="/blog/:slug" render={({ match }) => { // Do whatever you want with the match... return <div />; }} />
  );
}
複製代碼

只須要:

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

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

  // Do whatever you want with the match...
  return <div />;
}
複製代碼

這個hook接收一個參數,就等同於matchPath的props參數。便可以是一個路徑字符串,也能夠是一個包含Route接收的matching props同樣的對象:

const match = useRouteMatch({
  path: "/BLOG/:slug/",
  strict: true,
  sensitive: true
});
複製代碼

組件

<BrowserRouter>

一個使用HTML5 history API(pushState, replaceState 以及 popstate event)來讓UI和URL保持同步的<Router>

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

basename: string

全部locations的base URL。若是你的app在服務器上的二級目錄,能夠將basename設置爲二級目錄。正確的格式是以斜線開頭,結尾沒有斜線:

<BrowserRouter basename="/calendar">
    <Link to="/today"/> // renders <a href="/calendar/today">
    <Link to="/tomorrow"/> // renders <a href="/calendar/tomorrow">
    ...
</BrowserRouter>
複製代碼

getUserConfirmation: func

一個使用確認導航的函數,默認會使用window.confirm

<BrowserRouter
  getUserConfirmation={(message, callback) => {
    // 這是默認行爲
    const allowTransition = window.confirm(message);
    callback(allowTransition);
  }}
/>
複製代碼

forceRefresh: bool

值爲true時,router將會在頁面導航時整頁刷新。你能夠利用這個方法模仿服務端渲染的app在頁面導航時整頁刷新。

<BrowserRouter forceRefresh={true} />
複製代碼

keyLength: number

location.key的長度,默認爲6。

<BrowserRouter keyLength={12} />
複製代碼

children: node

要渲染的子元素。注意React < 16必須使用單個子元素,由於render方法不能返回多個元素,若是你須要多個元素,不妨試試將他們包裹在div中。

<HashRouter>

一個使用URL的hash部分(例如window.location.hash)來將頁面UI與URL保持同步的<Router>組件。

重要提醒:hash history不支持location.keylocation.state。在早期版本中咱們試着提供支持,可是出現了一些沒法解決的邊緣狀況。全部須要支持的代碼或插件都不能正常工做。因爲<HashRouter>更多用來支持老瀏覽器,咱們推薦你仍是配置一下服務端而後使用<BrowserHistory>吧。

<HashRouter
  basename={optionalString}
  getUserConfirmation={optionalFunc}
  hashType={optionalString}
>
  <App />
</HashRouter>
複製代碼

basename: string

<BrowserRouter>

getUserConfirmation: func

<BrowserRouter>

children: node

<BrowserRouter>

hashType: string

window.locatin.hash使用的編碼方式。可選值有:

  • "slash" - 默認值,建立的hash形如 #/#/sunshine/lollipops
  • "noslash" - 建立的hash形如 ##sunshine/lollipops
  • "hashbang" - 建立「可抓取的ajax」(Google已廢棄)形如 #!/#!/sunshine/lollipops

<StaticRouter>

一個從不改變location的 <Router>

用於服務端渲染:在服務端渲染的場景下,用戶並無真的點擊,因此location從不改變。因此是static,靜態的。也被用於簡單測試,只須要插入一個location而後對渲染結果進行斷言。

下面有個示例:服務器會發送一個302狀態碼來使用 <Redirect> 進行重定向,對其餘請求返回常規的HTML。

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

http
  .createServer((req, res) => {
    // 這個 context 對象包含渲染結果
    const context = {};

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

    // 若是使用了一個 <Redirect>,context.url 會包含重定向到目標的URL
    if (context.url) {
      res.writeHead(302, {
        Location: context.url
      });
      res.end();
    } else {
      res.write(html);
      res.end();
    }
  })
  .listen(3000);
複製代碼

basename: string

全部location的base URL。正確的格式是開頭包含下劃線,結尾不包含下劃線。

<StaticRouter basename="/calendar">
  <Link to="/today"/> // 渲染成 <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>
複製代碼

當一個 匹配時,它把context 對象做爲一個staticContext prop傳遞給它渲染的組件。

更多詳情能夠查看指南中的「服務端渲染」章節。

渲染後,這些屬性能夠用來配置服務端的 response。

if (context.status === "404") {
  // ...
}
複製代碼

children: node

要渲染的子組件。

注意:在 React < 16 版本中,必需使用單個子元素,由於一個render方法不能返回多個元素。 若是須要多個元素,或許能夠試着將他們包裹進 div 元素。

<MemoryRouter>

一個 <Router>,將URL的history保存在內存中(不會讀寫入地址欄)。用於測試和非瀏覽器環境如React Native。

<MemoryRouter
  initialEntries={optionalArray}
  initialIndex={optionalNumber}
  getUserConfirmation={optionalFunc}
  keyLength={optionalNumber}
>
  <App />
</MemoryRouter>
複製代碼

initialEntries: array

在history棧中的一個location數組。能夠是一個完整的location對象如 { pathname, search, hash, state } 或簡單的字符串URL。

<MemoryRouter
  initialEntries={["/one", "/two", { pathname: "/three" }]}
  initialIndex={1}
>
  <App />
</MemoryRouter>
複製代碼

initialIndex: number

initialEntries數組中的location的初始的下標。

getUserConfirmation: func

一個用來確認導航的函數。當你直接使用 <MemoryRouter> 和 一個 <Prompt>時,這個選項是必需的。

keyLength: number

location.key 的長度,默認爲6。

<MemoryRouter keyLength={12} />
複製代碼

children: node

要渲染的子組件。

<Router>

全部router組件的公共低級組件,應用的中通常用到的是下面這些高級組件:

  • <BrowserRouter>
  • <HashRouter>
  • <MemoryRouter>
  • <NativeRouter>
  • <StaticRouter>

低級組件<Router>最經常使用的使用場景就是同步一個自定義的history到一個狀態管理庫中如 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>
複製代碼

<Switch>

渲染第一個匹配當前location的<Route><Redirect>

它和只使用一組<Route>有何區別?一組<Route>被包裹在<Switch>中時,只渲染第一個匹配的;沒有<Switch>包裹時,會渲染全部匹配的<Route>。例如:

import { Route } from "react-router";

let routes = (
  <div> <Route path="/about"> <About /> </Route> <Route path="/:user"> <User /> </Route> <Route> <NoMatch /> </Route> </div>
);
複製代碼

若是URL是/about,那麼<About>, <User>, 以及 <NoMatch>都會渲染,由於他們所有匹配當前路徑。這麼設計是爲了容許咱們使用各類不一樣方式組合使用<Route>來構造諸如側邊欄、麪包屑、引導tab等組件。

有時候咱們只想渲染一個<Route>,當URL爲/about時,只想展現<About />,能夠這麼作:

import { Route, Switch } from "react-router";

let routes = (
  <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/about"> <About /> </Route> <Route path="/:user"> <User /> </Route> <Route> <NoMatch /> </Route> </Switch>
);
複製代碼

<Switch>會遍歷查找匹配的<Route>,找到<Route path="/about"/>後就會中止查找,而後渲染出<About />

這在過渡動畫時頗有用,由於<Route>會在和上一個相同的位置渲染。

let routes = (
  <Fade> <Switch> {/* 這裏最終只會渲染一個子組件 */} <Route /> <Route /> </Switch> </Fade>
);

let routes = (
  <Fade> {/* 這裏老是會渲染兩個子組件,使得過渡計算起來有點麻煩 */} <Route /> <Route /> </Fade>
);
複製代碼

location: object

一個location對象被用來匹配子元素,不是當前history location(當前瀏覽器URL)。

children: node

<Switch>的全部子組件應該是<Route><Redirect>。只有第一個匹配當前location的子組件會被渲染。

<Route>組件使用其path屬性來匹配,<Redirect>組件使用其from屬性來匹配。一個沒有path的<Route>或一個沒有from的<Redirect>老是會匹配當前location。

當你在<Switch>中包含一個<Redirect>時,它可使用任何<Route>的location匹配屬性:path, exact, 以及strict。from只是path的別名。

若是一個location屬性傳遞給<Switch>,將會重寫要匹配子組件的location屬性。

import { Redirect, Route, Switch } from "react-router";

let routes = (
  <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/users"> <Users /> </Route> <Redirect from="/accounts" to="/users" /> <Route> <NoMatch /> </Route> </Switch>
);
複製代碼

<Route>

<Route>組件是React Router中要理解和掌握的最重要的組件。它最基本的功能就是當它的path匹配當前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
);
複製代碼

若是app的location是/,那UI多是:

<div>
  <Home />
  <!-- react-empty: 2 -->
</div>
複製代碼

若是app的location是/news,那UI多是:

<div>
  <!-- react-empty: 1 -->
  <NewsFeed />
</div>
複製代碼

「react-empty」註釋是React渲染null的實現細節。從技術上講,一個<Route>老是會被渲染,只不過path匹配當前URL時渲染它的子組件,也就是提供給路由的component;不匹配則渲染null。

若是同一個組件被用做多個<Route>的子組件,而且處在組件樹的相同位置,React將會把它們視爲同一個組件實例,當路由改變時,組件的state將會被保存。若是不想保存,那就給每一個路由組件添加一個惟一的key,這樣當路由改變時,React將會從新建立組件實例。

路由渲染方法

推薦的<Route>渲染方法是使用它的子組件,就像上面給出的示例那樣。還有一些其餘的渲染方法,這些方法主要是爲了支持那些在hooks被引入以前的早期版本構建的應用。

  • <Route component>
  • <Route render>
  • <Route children>

上面的方法使用一種便可,下面會解釋這些方法之間的差別。

路由 props

全部的渲染方法都會傳遞如下三個相同的路由props

  • match
  • location
  • history

component

只有當location匹配時,一個React組件纔會被渲染,而且會傳遞路由 props。

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

// 全部的路由 props (match, location 及 history) 均可以訪問
function User(props) {
  return <h1>Hello {props.match.params.username}!</h1>;
}

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

當你使用component而不是render或children,React Router將會使用React.createElement方法從給定的component建立一個React組件。因此若是你在component屬性中提供一個內聯的函數,每次渲染都會將會建立一個新的組件。這會致使已有的組件不會unmount而新的組件每次都會mount,而不是僅僅更新已有的組件。所以,若是想使用內聯的函數來內聯渲染,使用render或children屬性。

render: func

這個屬性容許內聯渲染,並且不會致使上面提到的預期外的從新mount。

你能夠傳遞一個函數給render,當location匹配時,會自動調用這個函數。在渲染函數中,依然能夠訪問路由props(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={() => <div>Home</div>} /> </Router>,
  node
);

// wrapping/composing
// 你可使用rest參數接收route props而後用spread運算符添加到子組件的props中,使得渲染的組件內部也能夠訪問路由props
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

有時候無論路由的path是否匹配location,你都須要渲染。這時你可使用children屬性,它和render屬性的做用基本相同,除了無論是否匹配都會被調用這一點不一樣。

children屬性和component、render屬性同樣,都接收相同的路由props,只有一點不一樣:當route匹配失敗,match的值爲null。這讓你能夠基於router是否匹配動態調整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>
  )}
/>
複製代碼

path: string | string[]

任何有效的URL路徑或path-to-regexp@^1.7.0能夠理解的路徑數組。

<Route path="/users/:id">
  <User />
</Route>
複製代碼
<Route path={["/users/:id", "/profile/:id"]}>
  <User />
</Route>
複製代碼

沒有path的Route老是匹配的。

exact: bool

值爲true時,只有path和location.pathname精確匹配時纔會匹配成功。

<Route exact path="/one">
  <About />
</Route>
複製代碼
path location.pathname exact 是否匹配?
/one /one/two true
/one /one/two false

strict: bool

值爲true時,一個以斜線結尾的path只會匹配結尾有斜線的location.pathname。當location.pathname還有多餘的片斷,則不會受影響。

<Route strict path="/one/">
  <About />
</Route>
複製代碼
path location.pathname 是否匹配?
/one/ /one
/one /one/
/one /one/two

注意:strict能夠用來強制使location.pathname不含結尾的斜線,爲了實現效果,exact也必須設爲true。

<Route exact strict path="/one">
  <About />
</Route>
複製代碼
path location.pathname 是否匹配?
/one /one
/one /one/
/one /one/two

location: object

一個<Route>組件會嘗試匹配本身的path和當前的history location(一般是當前的瀏覽器URL)。但是一個有不一樣pathname的location照樣能夠用來匹配。

這在當你須要匹配一個<Route>到一個location而不是當前的history location時會頗有用。

若是一個<Route>組件被包裹在一個<Switch>中,而且成功匹配到傳遞給<Switch>的location,或當前history location時,<Route>本來的location prop將會被<Switch>所使用的location重寫。

sensitive: bool

值爲true時,匹配路徑會區分大小寫。

<Route sensitive path="/one">
  <About />
</Route>
複製代碼
path location.pathname sensitive 是否匹配?
/one /one true
/One /one true
/One /one false

<Redirect>

渲染一個<Redirect>將會導航到新的location,並重寫history棧中當前location,就像服務端重定向(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

要重定向到哪一個location。pathname是path-to-regexp@^1.7.0能夠理解的任何有效的URL路徑。

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

state對象能夠在重定向到的組件中經過this.props.location.state訪問。好比這個示例中的referrer就能夠在/login路徑名對應的Login組件中經過this.props.location.state.referrer來訪問。

push: bool

值爲true時,重定向時將會在history棧中添加一個新的記錄,而不是替換當前的記錄。

<Redirect push to="/somewhere/else" />
複製代碼

from: string

一個路徑名,表示從哪裏重定向。from中全部匹配成功的URL參數都會提供給to,from的URL參數必須包含to中使用的全部參數,沒用到的參數會被忽略。

注意:當在<Switch>中渲染一個<Redirect>時,這隻能用來匹配一個location。詳情可見<Switch children>

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

// 帶有匹配參數的Redirect
<Switch> <Redirect from='/users/:id' to='/users/profile/:id'/> <Route path='/users/profile/:id'> <Profile /> </Route> </Switch>
複製代碼

exact: bool

精確匹配from,等同於Route.exact

注意:當在<Switch>中渲染一個<Redirect>時,這個屬性只能和from一塊兒使用來精確匹配一個location。詳情可見<Switch children>

<Switch>
  <Redirect exact from="/" to="/home" />
  <Route path="/home"> <Home /> </Route>
  <Route path="/about"> <About /> </Route>
</Switch> 
複製代碼

strict: bool

嚴格匹配,同Route.strict

注意:當在<Switch>中渲染一個<Redirect>時,這個屬性只能和from一塊兒使用來嚴格匹配一個location。詳情可見<Switch children>

<Switch>
  <Redirect strict from="/one/" to="/home" />
  <Route path="/home"> <Home /> </Route>
  <Route path="/about"> <About /> </Route>
</Switch>  
複製代碼

sensitive: bool

匹配from時不忽略大小寫,同Route.sensitive

<Prompt>

用來在離開一個頁面前提示用戶。當須要阻止用戶離開時例如編輯頁面的表單還未提交,這時能夠渲染一個 <Prompt> 來提醒用戶。

<Prompt
  when={formIsHalfFilledOut}
  message="你肯定要離開嗎?"
/>
複製代碼

message: string

顯示的提示文字。

<Prompt message="你肯定要離開嗎?" />
複製代碼

message: func

調用時傳遞的參數是用戶將要導航到目的地的location和action。 返回一個字符串來提示用戶進行確認或返回true來容許跳轉。

<Prompt
  message={(location, action) => {
    if (action === 'POP') {
      console.log("Backing up...")
    }

    return location.pathname.startsWith("/app")
      ? true
      : `Are you sure you want to go to ${location.pathname}?`
  }}
/>
複製代碼

when: bool

比起有條件地渲染一個<Prompt>, 你能夠直接渲染它,並經過傳遞 when={true}when={false} 來阻止或容許導航。

<Prompt when={formIsHalfFilledOut} message="Are you sure?" />
複製代碼

<Link>

在你的應用中提供一個聲明式的、可訪問的導航連接。

<Link to="/about">About</Link>
複製代碼

to: string

表示連接到的位置,由location的pathname、search、hash組成。

<Link to="/courses?sort=name" />
複製代碼

to: object

一個包含如下屬性的對象:

  • pathname: 一個字符串,表示要連接到的路徑
  • search: 一個字符串,表示查詢參數
  • hash: URL中的hash,如 #a-hash
  • state: 保存在location裏的state,能夠用來向目標頁面傳遞一些數據
<Link
  to={{
    pathname: "/courses",
    search: "?sort=name",
    hash: "#the-hash",
    state: { fromDashboard: true }
  }}
/>
複製代碼

to: function

一個函數 當前location做爲參數傳入,返回一個字符串或對象形式的location。

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

replace: bool

值爲true時,點擊連接將會替換history棧的當前記錄,而不是添加一個。

<Link to="/courses" replace />
複製代碼

innerRef: function

容許訪問組件內部的ref。

從React Router 5.1開始,若是你正在使用React 16,那你就不須要這個屬性,由於咱們已經轉發了ref到內部的<a>標籤,只使用普通的ref就好。

<Link
  to="/"
  innerRef={node => {
    // `node` 指向掛載的DOM元素,unmounted時指向null
  }}
/>
複製代碼

innerRef: RefObject

同上,只不過使用React.createRef來獲取底層的ref。

let anchorRef = React.createRef()

<Link to="/" innerRef={anchorRef} />
複製代碼

component: React.Component

若是你想利用你本身的導航組件,只須要將它傳遞給組件的component屬性便可。

const FancyLink = React.forwardRef((props, ref) => (
  <a ref={ref}>💅 {props.children}</a>
))

<Link to="/" component={FancyLink} />
複製代碼

其餘

你還能夠傳遞任何你想在<a>標籤上使用的屬性,例如title,id,className等等。

<NavLink>

一個特殊的<Link>,匹配當前URL時會自動添加樣式相關的屬性。

<NavLink to="/about">About</NavLink>
複製代碼

exact: bool

值爲true時,只有精確匹配時纔會應用active class/style。

<NavLink exact to="/profile">
  Profile
</NavLink>
複製代碼

strict: bool

值爲true時,匹配時會將一個location的pathname末尾的/也會考慮進來,詳情見<Route strict>

<NavLink strict to="/events/">
  Events
</NavLink>
複製代碼

activeClassName: string

激活時添加的class。默認值給定的class是active。這個類名會和className屬性合併。

<NavLink to="/faq" activeClassName="selected">
  FAQs
</NavLink>
複製代碼

activeStyle: object

激活時應用的樣式。

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

isActive: func

一個函數,用來添加額外的邏輯來決定這個連接是否要被激活。用於若是你想作更多的事來驗證這個link的pathname是否匹配當前URL的pathname。

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

    // 只考慮event id是偶數的event
    const eventID = parseInt(match.params.eventID);
    return !isNaN(eventID) && eventID % 2 === 1;
  }}
>
  Event 123
</NavLink>
複製代碼

location: object

一般匹配時比較的是history location(一般是當前瀏覽器URL),能夠經過這個屬性傳遞一個不一樣的location用於匹配時的比較。

aria-current: string

此屬性的值在一個active連接上使用,可選的值有:

  • "page" - 默認值,用來表示一組分頁連接中的一個連接
  • "step" - 用來表示步驟流程中的一個步驟
  • "location" - 用來表示一個流程圖中當前組件的高亮的圖像
  • "date" - 用來表示日曆中的當前日期
  • "time" - 用來表示時間表中的當前時間
  • "true" - 用來表示NavLink是不是激活狀態

基於 WAI-ARIA 1.1 規範

對象和方法

history

這篇文檔所說的「history」和「history object」都指的是history庫,這也是React Router除了React外兩個主要依賴之一,這個庫爲在不一樣環境中使用js管理session history提供了幾種不一樣的實現。

如下術語被用到:

  • 「browser history」:一個DOM實現,用於支持HTML5 history API的瀏覽器
  • 「hash history」:一個DOM實現,用於老瀏覽器
  • 「memory history」:一個內存history實現,用於測試和非DOM環境如React Native

history對象一般包含如下屬性和方法:

  • length - (number) 歷史記錄棧中的記錄數
  • action - (string) 當前的動做(PUSH, REPLACE, 或 POP)
  • location - (object) 當前的location,可能包含如下屬性:
    • pathname - (string) URL的path
    • search - (string) URL的查詢字符串
    • hash - (string) URL的hash片斷
    • state - (object) 特定於location的狀態,提供給諸如當這個location入棧時push(path, state)。只在瀏覽器和內存的history中可用。
  • push(path, [state]) - (function) 將一個新的記錄推入history棧
  • replace(path, [state]) - (function) 替換history棧的當前記錄
  • go(n) - (function) 將history棧的指針移動n步
  • goBack() - (function) 等同於 go(-1)
  • goForward() - (function) 等同於 go(1)
  • block(prompt) - (function) 阻止導航(參看 history 文檔)

history是可變的

history對象是可變的。所以推薦從<Route>的render props訪問location,而不是從history.location。這樣作確保了React處在正確的生命週期鉤子,舉個例子:

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

    // 不正確,結果老是false,由於history是可變的
    const locationChanged =
      this.props.history.location !== prevProps.history.location;
  }
}

<Route component={Comp} />;
複製代碼

更多屬性查看history文檔

location

location代表了當前app在哪兒,你想去哪兒,你想去的地方在哪兒,看起來就像這樣:

{
  key: 'ac3df4', // HashHistory沒有
  pathname: '/somewhere',
  search: '?some=search-string',
  hash: '#howdy',
  state: {
    [userDefined]: true
  }
}
複製代碼

能夠在如下幾個地方訪問location對象:

  • Route component —— this.props.location
  • Route render —— ({ location }) => ()
  • Route children —— ({ location }) => ()
  • withRouter —— this.props.location

還能夠在history.location中找到,但你不該該這麼用,由於它是可變的。

一個location對象永遠都是不可更改的,因此你能夠在生命週期鉤子中使用它來決定何時導航發生,這在獲取數據和動畫時很是有用:

componentWillReceiveProps(nextProps) {
  if (nextProps.location !== this.props.location) {
    // navigated!
  }
}
複製代碼

你能夠提供locations而不是字符串到不一樣的導航的地方:

  • Web Link to
  • Native Link to
  • Redirect to
  • history.push
  • history.replace

一般只用一個字符串,可是若是你須要添加一些「location state」來讓app到達那個特定的location時可用,可使用一個location對象。若是你想利用導航history而不是path來區分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:

  • Route
  • Switch

這會阻止他們使用router的state中實際的location。這對於動畫和處理中的導航頗有用,或任什麼時候候你想誘使組件在與實際location不一樣的location中進行渲染。

match

一個match對象包含了一個<Route path>怎樣匹配了URL.match對象的信息,包括如下屬性:

  • params - (object) 從URL解析的鍵值對,和path的動態片斷一致
  • isExact - (boolean) 若是整個URL都匹配則爲true
  • path - (string) 用來匹配的 path pattern,用於構建嵌套的<Route>
  • url - (string) 匹配的URL部分,用於構建嵌套的<Link>

你能夠在這些地方訪問match對象:

  • Route component —— this.props.match
  • Route render —— ({ match }) => ()
  • Route children —— ({ match }) => ()
  • withRouter —— this.props.match
  • matchPath —— 返回值
  • useRouteMatch —— 返回值

若是一個Route沒有path並所以老是會匹配到,你會獲得最近的上級匹配,withRouter也同樣。

什麼都沒匹配到

一個使用了children prop的<Route>將會調用它的children function,即便path不匹配當前的location。這種狀況下,match將會是null。當它匹配到了可以渲染一個<Route>的內容會頗有用,可是會有其餘問題。

解析URL的默認方式是將match.url字符串加入相對路徑:

let path = `${match.url}/relative-path;`
複製代碼

若是你在match爲null的時候嘗試這麼作,你會獲得一個TypeError。這意味着當使用children prop在一個<Route>裏面添加相對路徑被認爲是不安全的。

當你在一個<Route>內使用一個沒有path且match爲null的<Route>時也會發生相似的狀況。

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

無path的<Route>從父級繼承match對象,若是父級的match是null,那他們的match也是null。這意味着:

  1. 任何child routes/links都必須是絕對的由於沒有parent能夠解析
  2. 一個無path的路由的父級的match多是null的,將會須要使用children prop來渲染。

matchPath

這讓你可使用和<Route>相同的匹配代碼,但不在正常渲染週期以內,例如在服務端渲染前收集數據依賴。

import { matchPath } from "react-router";

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

pathname

第一個參數是你相匹配的pathname。若是你在服務端使用的是Node.js,這個值就是req.path

props

第二個參數是匹配選項,和Route接收的匹配props相同。它也能夠是一個字符串或一個字符串數組,做爲{ path }的簡寫。

{
  path, // 如 /users/:id; 能夠是一個字符串或字符串數組
  strict, // 可選, 默認false
  exact, // 可選, 默認false
}
複製代碼

返回值

當pathname和path屬性匹配成功時返回一個對象。

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

// {
// isExact: true
// params: {
// id: "2"
// }
// path: "/users/:id"
// url: "/users/2"
// }
複製代碼

當pathname和path屬性不匹配時返回null。

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

// null
複製代碼

withRouter

你能夠經過withRouter高階組件來訪問history對象屬性和最近的<Route>的match對象。withRouter會在被包裹的組件渲染時,將更新過的match, location, 和history做爲props傳遞給該組件。

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

// 一個展現了當前location的pathname的簡單組件
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>;
  }
}

// 建立了一個「鏈接」到router的新組件
const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
複製代碼

重要提醒:withRouter沒有像React Redux的connect訂閱state更新那樣訂閱location的變化。而是在location變化後從<Router>組件傳播出來,從新渲染組件。這就意味着withRouter不會在路由過渡(transitions)時從新渲染,除非父組件從新渲染了。

被包裹組件的全部非React特定的靜態方法和屬性都會自動複製到返回的組件中。

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)} />
    );
  }
}
複製代碼

generatePath

這個方法能夠用來給路由生成一個URL。內部使用了path-to-regexp庫。

import { generatePath } from "react-router";

generatePath("/user/:id/:entity(posts|comments)", {
  id: 1,
  entity: "posts"
});
// 將返回 /user/1/posts
複製代碼

將path編譯爲正則表達式的結果將會被緩存,因此使用相同的pattern生成多個path不會產生額外的開銷。

pattern: string

該方法有兩個參數,第一個參數是一個就像Route組件的path屬性值的pattern。

params: object

第二個參數是一個對象,提供了pattern要用到的路由參數。

若是提供的路由參數和path不匹配,將會報錯:

generatePath("/user/:id/:entity(posts|comments)", { id: 1 });
// TypeError: Expected "entity" to be defined
複製代碼

References

  1. 官方文檔:reactrouter.com/web/guides/…
  2. Github文檔:github.com/ReactTraini…
相關文章
相關標籤/搜索