React Router 6


  路由的概念,可以想像一下路由器,當來了一個請求時,路由器做了什么事情?它會把請求的IP地址和路由表進行匹配,匹配成功后,進行轉發,直到目標主機。可以看到路由有三部分組成,一個是請求,一個是路由表,一個是匹配轉發。對應到前端路由也是一個道理,只不過前端路由是攔截請求,顯示不同的頁面內容。首先要發起請求,說明你要到哪里去,React Router中定義了<Link>。其次要定義一個路由表,列出匹配規則和匹配成功后要顯示什么,就是React Router中一條條的<Route>。最后就是來了請求時進行動態匹配和轉發,React提供了<BrowserRouter>和<Routes>。<BrowserRouter> 把<Routes>包起來,<Routes>把<Route>包起來,來了請求,它就能匹配路由表。

  <Link> 有一個to屬性,就是標明去哪里

<Link to="/home">Home</Link>

  <Route> 有兩個屬性,一個是path,就是列出匹配規則,一個是element,就是匹配成功后要顯示什么內容。如果請求‘/home’,就顯示<Home>組件內容,就可以如下定義

<Route path=’/home’, element={<Home/>} /> // 要寫好Home 組件。

  要注意的是elemet 接受的真是React Element。這里只是定義一條路由,路由表里的一條記錄。當要匹配其它請求時,還要再寫路由, 比如About

<Route path=’/about’, element={<About/>} />

  要匹配多少請求,就要寫多少條路由。這樣一條條的路由就定義好了,放到<BrowserRouter>和<Routes>下面,只要來了請求,就能進行動態匹配。<BrowserRouter>提供頁面的URL(Provides the cleanest URLs)。<Routes>進行動態匹配。

  使用create-react-app創建項目router-tutorial,然后cd router-tutorial 並npm install react-router-dom。 在index.js中引入BrowserRouter 和<Routes>, BrowserRouter把Route包起來。整個index.js如下

import React from 'react';
import ReactDOM from 'react-dom'; import { BrowserRouter, Routes, Route } from "react-router-dom"; import { Home, About, Contact, Products, Events } from "./pages"; ReactDOM.render( <BrowserRouter> <Routes> <Route path='/' element={<Home />} /> <Route path='/about' element={<About />} /> <Route path='/contact' element={<Contact />} /> <Route path='/products' element={<Products />} /> <Route path='/events' element={<Events />} /> </Routes> </BrowserRouter>, document.getElementById('root') );

  為了讓路由起作用,還要創建Home, About 等組件。在src目錄下,新建一個pages.js文件,內容如下:

import React from 'react'

// 首頁內容
export const Home = () => (
  <section className="home">
    <h1>企業網站</h1>
    <p>首頁內容</p>
  </section>
) // 企業事件內容 export const Events = () => ( <section className="events"> <h1>企業大事件</h1> </section> ) // 公司產品 export const Products = () => ( <section className="products"> <h1>公司產品:手機、電腦</h1> </section> ) // 聯系我們 export const Contact = () => ( <section className="contact"> <h1>聯系我們</h1> <p>公司電話:0755 - 12345678</p> </section> ) // 關於我們 export const About = () => ( <section className="about"> <h1>公司理念</h1> <p>公司以人為本</p> </section> )

  npm start,localhost:3000

  怎么訪問其它頁面的內容呢?使用<Link />, <Link>表示要到哪里去,只要設置它的to屬性和Route中的path屬性一一對應就可以了,如<Link to=’/about’>關於我們</Link>,就表示要到/about下面。Home組件中增加四個<Link>

import { Link } from "react-router-dom";
export const Home = () => ( <section className="home"> <h1>企業網站</h1> <p>首頁內容</p> <nav> {/* 添加了四個導航組件Link */} <Link to='/about'>關於我們</Link> <Link to='/events'>企業事件</Link> <Link to='/products'>公司產品</Link> <Link to='/contact'>聯系我們</Link> </nav> </section> )

  這時,點擊不同的link 就去到不同頁面,同時它還會改變地址欄,這時如要在地址欄中隨便輸入一個路徑,頁面一片空白,因為沒一個路由和它匹配。最好寫一個匹配不成功的路由,來處理一下這種情況。那路由的path怎么寫?用“*”。要顯示的組件可以隨便寫一下,在pages.js 下面再寫一個組件, 

export const NotFound404 = () =>(
  <div className="whoops-404">
      <h1>沒有頁面可以匹配</h1>
  </div>
)

  路由就是

<Route path='*' element={<NotFound404/>}></Route>

  和普通路由一樣,把它加到<Routes>組件下面。*表示,其它路由都不匹配的時候,才匹配它。

<BrowserRouter>
    <Routes>
      <Route path='/' element={<Home/>} />
      <Route path='/about' element={<About />} />
      <Route path='/contact' element={<Contact />} />
      <Route path='/products' element={<Products/>} />
      <Route path='/events' element={<Events/>} />
      <Route path='*' element={<NotFound404/>}></Route>
    </Routes>
  </BrowserRouter>

   此時,路由有一個問題,那就是點擊<About>之后,回不去了,只能點擊瀏覽器的回退按鈕,要是頁面始終展示導航條就好了。由於導航條在Home組件,也就是說Home組件始終要顯示。About等組件的內容都是在點擊之后才會顯示,也就是先顯示Home組件,再顯示 About組件,只有先導航到home路由,才有機會導航到about的路由, home路由是about路由的父路由,在React Router 6中,Route可以嵌套,只要一個<Route>包含其它Route,它就是父路由,那么路由就可以這么寫

<BrowserRouter>
    <Routes>
      <Route path='/' element={<Home />} >
        <Route path='about' element={<About />} />
        <Route path='contact' element={<Contact />} />
        <Route path='products' element={<Products />} />
        <Route path='events' element={<Events />} />
      </Route>
      <Route path='*' element={<NotFound404 />}></Route>
    </Routes>
  </BrowserRouter>

  子路由前面的/可以去掉,React會自動組合(父路由/+子路由"about")。那子路由匹配成功后,要展示內容放到什么地方? 由於是子路由,肯定要先匹配父路由,只有父路由匹配成功了,才能匹配子路由。也就是只有父路由對應的組件展示出來了,才有機會展示子路由對應的內容,子路由的內容應該放到父路由的對應的組件里面,也就是About等組件要放到Home組件里面 。那具體怎么寫呢?React Router 提供了<Outlet>組件,只要子路由匹配成功,<Outlet />組件可以動態成渲染子路由定義的組件內容。<Outlet />放到Home組件中,具體放到哪里,就看業務需要,比如與導航條並列

import { Link, Outlet } from "react-router-dom";

// 首頁內容
export const Home = () => ( <section className="home"> <h1>企業網站</h1> <p>首頁內容</p> <nav> {/* 添加了四個導航組件Link */} <Link to='/about'>關於我們</Link> <Link to='/events'>企業事件</Link> <Link to='/products'>公司產品</Link> <Link to='/contact'>聯系我們</Link> </nav> <Outlet /> </section> )

  點擊每一個<Link>, 和它匹配成功的子路由所定義組件,都會正確地渲染,並且是渲染在<Outlet> 位置,正確的組件替換掉了<Outlet>。

  給整個組件添加點樣式,pages.css 內容如下,並在index.js中引用

html, body, #root {
  height: 100%;
}
h1 {
  font-size: 3em;
  color: slategray;
}
/* home 組件 */
.home {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.home > nav {
  display: flex;
  justify-content: space-around;
  padding: 1em;
  width: calc(100% - 2em);
  border-top: dashed 0.5em ghostwhite;
  border-bottom: dashed 0.5em ghostwhite;
  background-color: slategray;
}

.home > nav a {
  font-size: 2em;
  color: ghostwhite;
  flex-basis: 200px;
}

/* 其它組件 */
section.events,
section.products,
section.contact {
  flex-grow: 1;
  margin: 1em;
  display: flex;
  justify-content: center;
  align-items: center;
}
/* 404頁面 */
.whoops-404 {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 99;
  display: flex;
  width: 100%;
  height: 100%;
  margin: 0;
  justify-content: center;
  align-items: center;
  background-color: darkred;
  color: ghostwhite;
  font-size: 1.5em;
}

  整個頁面如下展示

 

 

   這時也會發現一個問題,剛進入頁面時,頁面只展示了導航條, 沒有顯示實際的內容,只有點擊某個導航后,才顯示內容,這顯然不合適,即使不點擊,也要看到核心內容,比如公司產品。針對此種情況,React Router 提供了索引路由,

<Route path='/' element={<Home />} >
        {/* 索引路由 */} <Route index element={<Products/>} /> <Route path='about' element={<About />} /> <Route path='contact' element={<Contact />} /> <Route path='products' element={<Products />} /> <Route path='events' element={<Events />} /> </Route>

  索引路由和父路由共享路徑。當localhost:3000時,path='/'匹配成功,渲染<Home>組件,渲染過程中有<Outlet>組件,就要去匹配子路由,由於索引路由的路徑也是父路由的路徑,此時URL就是父路由的路徑,匹配成功,顯示Products組件,也可以把索引路由看作是默認的子路由,當沒有其它子路由匹配的時候,就渲染它,總要顯示一個子路由嗎?稍微修飾一下公司產品模塊,列出幾個產品,比如手機,電腦等,

export const Products = () => (
  <section className="products">
    <Link to='/details/telphone'>手機</Link>
        &nbsp; <Link to='/details/computer'>電腦</Link> </section> )

  當點擊某個產品,進入產品詳情頁面,在pages.js中加一個詳情組件

// 產品詳情組件
export const Details = () => {
  return <p>詳情內容</p> }

  問題是當跳轉到產品詳情頁面時,它怎么知道是哪個產品呢?匹配的路由怎么寫?路由的格式就是路徑后面加上冒號 ,再加參數,比如details/:type,組件中可以使用useParams獲取。為了展示,把路由寫到根路由index.js下

<Route path='products' element={<Products />} />
<Route path='details/:type' element={<Details />} />
<Route path='events' element={<Events />} />

  同時Details組組件改為

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

export const Details = () => { let params = useParams(); return <p>產品: {params.type}</p> }

  還有 一個小問題,導航條能不能提示位於哪個導航上?React Router 提供了NavLink 組件, 它和Link功能是一樣的,都是標識請求,只是使用的場景不一樣。navLink 提供了一個isActive 屬性,可以設置高亮樣式。

 <NavLink to='/about' className={({ isActive }) => isActive ? "selectedStyle" : ""}>關於我們</NavLink>

  當選中之后,isActive是true。

  除了這種聲明式的路由,也可以使用編程式路由,核心就是useNavigate. 在Detail里面,添加button,跳轉到Product。

import { useParams, useNavigate } from "react-router-dom";
export const Details = () => { let params = useParams(); let navigate = useNavigate(); return ( <p> 產品: {params.type} <br /> <button onClick={() => { navigate("/products"); }}>后退</button> </p> ); }

  React Router 主要概念

  React Router三個主要作用:監聽(訂閱)和操作瀏覽器的歷史記錄,匹配URL到你配置的路由,從匹配的路由中渲染嵌套的UI(完整的UI)

  location是基於瀏覽器內置的window.location對象,React Router 自己擁有的一個特殊的對象。它表示用戶在哪里,幾乎就是URL的對象表示,但包含的信息比URL多。Location State則是存在Location對象上的一個值,但它並沒有在URL中顯示。它存在瀏覽器的內存中,並不可見,樣子像hash或搜索參數。當用戶在網頁中導航時,瀏覽器持續追蹤每一個location,形成瀏覽器的歷史記錄。

  history也是一個對象,它使React Router 可以訂閱URL的變化,並提供API來操作瀏覽器的歷史記錄。

  segment:URL和路徑模式中兩個/之間的部分,比如'/user/123', user和123都是segment。路徑模式很像URL,但它包含特殊字符,比如 "/users/:userId"和 "/docs/*" ,一個包含:,一個包含*。路徑模式下“:userId” 又稱為動態segment,因為它能匹配任意值。URL params就是匹配動態segment成功的值。/users/123 匹配/users/:userId, 123就是url params。

  Match也是一個對象,它包含了URL和路由匹配成功的信息,比發path和URL Params。Matches是和當前URL匹配成功的一組路由。

  客戶端路由時,開發者可以通過API,如history.pushState(),來操作瀏覽器的歷史記錄,而不會發送服務器請求,但它僅僅是改變了URL,並沒有改變UI。我們需要做的是改變URL的同時,改變UI。問題是瀏覽器並沒有提供一個方法來監聽URL,從而訂閱變化。為此,React Router創建了histroy 對象,來監聽URL的變化。<Router>,就是<BrowserRouter>,它會創建history對象,並訂閱瀏覽器歷史記錄的變化,當URL變化時,它會更新history對象的state,從而引起APP的重新渲染,正確的UI顯示出來。history的state是一個location對象,如下所示

{
  pathname: "/bbq/pig-pickins",
  search: "?campaign=instagram",
  hash: "#menu",
  state: null,
  key: "aefz24ie"
}

  pathname,路由匹配的部分,路由進行匹配的時候,只匹配pathname。state,來自於history.pushState()。pushState()第一個參數就是state, 但它不會改變URL。React Router 進行抽象,把state放到了location對象中。改變location 的state有兩種方式

<Link to="/pins/123" state={{ fromDashboard: true }} />;

let navigate = useNavigate();
navigate("/users/123", { state: partialUser });

  在跳轉到的page,可以使用useLocation獲取到

let location = useLocation();
location.state;

  只要改變URL,就會改變location 對象,想要獲取URL的信息,就使用location對象。當URL改變后,React Router就用location來匹配你配置的路由,然后把匹配成功的拿出來,進行渲染。配置的路由就是<Routes>和<Route>組件。

<Routes>
  <Route path="/" element={<App />}>
    <Route index element={<Home />} />
    <Route path="teams" element={<Teams />}>
      <Route path=":teamId" element={<Team />} />
      <Route path="new" element={<EditTeam />} />
    </Route>
  </Route>
  <Route element={<PageLayout />}>
    <Route path="/privacy" element={<Privacy />} />
  </Route>
</Routes>

  <Routes>會遞歸遍歷它的children,也就是<Route>,把屬性拿出來,形成一個對象。

let routes = [
  {
    element: <App />,
    path: "/",
    children: [
      {
        index: true,
        element: <Home />
      },
      {
        path: "teams",
        element: <Teams />,
        children: [
          {
            path: ":teamId",
            element: <Team />
          },
          {
            path: "new",
            element: <NewTeamForm />
          }
        ]
      }
    ]
  },
  {
    element: <PageLayout />,
    children: [
      {
        element: <Privacy />,
        path: "/privacy"
      }
    ]
  }
];

  形成一個總的路由配置表

[
  "/",
  "/teams",
  "/teams/:teamId",
  "/teams/new",
  "/privacy"
];

  如果現在有一個URL是/teams/new,哪個路由會匹配它,有兩個

/teams/new
/teams/:teamId

  這時React Router 必須做決定,因為只能有一條路由匹配,React Router會對你路由進行排名,依據就是路由中的segment, 靜態,動態,數量等等,找出最精准匹配的那一條路由。在這里,就是 /teams/new 這條路由。當一條路由成功匹配到URL后,它會以match對象的方式進行展示。匹配到<Route path=":teamId" element={<Team/>}/>這條路由,將會生成下面這個對象

{
  pathname: "/teams/firebirds",
  params: {
    teamId: "firebirds"
  },
  route: {
    element: <Team />,
    path: ":teamId"
  }
}

  由於路由是樹形結構,一個URL可能匹配到樹的整個分支。/teams/firebirds

<Routes>
  <Route path="/" element={<App />}>
    <Route path="teams" element={<Teams />}>
      <Route path=":teamId" element={<Team />} />
    </Route>
  </Route>
</Routes>

  React Router從這些路由和URL中,會創建一組匹配的路由,從而渲染出嵌套的UI來匹配嵌套的路由。

[
  {
    pathname: "/",
    params: null,
    route: {
      element: <App />,
      path: "/"
    }
  },
  {
    pathname: "/teams",
    params: null,
    route: {
      element: <Teams />,
      path: "teams"
    }
  },
  {
    pathname: "/teams/firebirds",
    params: {
      teamId: "firebirds"
    },
    route: {
      element: <Team />,
      path: ":teamId"
    }
  }
];

  有了匹配的路由,渲染React Element樹就簡單了

<App>
  <Teams>
    <Team />
  </Teams>
</App>

   看一下"/privacy",它匹配的路由是

<Route path="/" element={<App />}>
  <Route element={<PageLayout />}>
    <Route path="/privacy" element={<Privacy />} />
  </Route>
</Route>

  要渲染的React Element樹

<App>
  <PageLayout>
    <Privacy />
  </PageLayout>
</App>

  PageLayout路由有點奇怪,沒有path屬性,只有Element屬性,稱為布局路由,僅僅用來做布局的。

  V5 和V6的不同

  1,React Router v6 使用了大量的React Hooks,因此升級V6前,要先升級React到16.8以上。

  2,使用<Routes> 代替<Switch>,<Routes>使用的是最佳匹配路由算法,並且路由能嵌套

  3,組件內部有<Link>和<Route>時,<Link>的to屬性和Route>的path屬性,不用再手動構建,而是直接寫

<Route path={`${match.path}/:id`}> -> <Route path=":id" element={<UserProfile />} />

  此時<Route path>和<Link to> 是相對路由和Link,它們自動構建在父路由path和URL上。對應的,當路由有后代路由,且這些路由是定義在其它組件中,路由的path要用*,表示深度匹配。

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="users/*" element={<Users />} />
      </Routes>
    </BrowserRouter>
  );
}

function Users() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>

      <Routes>
        <Route path=":id" element={<UserProfile />} />
        <Route path="me" element={<OwnUserProfile />} />
      </Routes>
    </div>
  );
}

  4,<Route>使用element,而不是compoent, render屬性,element的取值也是React Element,好處就是可以像普通的React Element傳遞屬性

// animate 是自定義屬性
<Route path=":userId" element={<Profile animate={true} />} />
// 組件使用
function Profile({ animate }) {
  let params = useParams();
  let location = useLocation();
}

  5, <Route> 的path屬性不再接受正則表達式,/users/:id? 無效了。

  6,react-router-config包里的功能都集成到V6中,使有useRoute,而不是react-router-config

function App() {
  let element = useRoutes([
    // These are the same as the props you provide to <Route>
    { path: "/", element: <Home /> },
    {
      path: "invoices",
      element: <Invoices />,
      // Nested routes use a children property, which is also
      // the same as <Route>
      children: [
        { path: ":id", element: <Invoice /> },
        { path: "sent", element: <SentInvoices /> }
      ]
    },
    // Not found routes work as you'd expect
    { path: "*", element: <NotFound /> }
  ]);

  // The returned element will render the entire element
  // hierarchy with all the appropriate context it needs
  return element;
}

  當進行服務端渲染時,要用matchRoutes 

  7,useNavigate 代替了useHistory , Navigate 組件代替了Redirect 組件

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

function App() {
  return <Navigate to="/home" replace state={state} />;
}

  8, <Link>沒有了component屬性,只能渲染成標簽。

  9,<NavLink/> 去掉了activeClassName 和 activeStyle, 要使用isActive

  10,StaticRouter 稱動了react-router-dom/server.

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM