<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React Router 5 完整指南

          共 33392字,需瀏覽 67分鐘

           ·

          2021-06-06 19:02

          點擊上方 前端瓶子君,關注公眾號

          回復算法,加入前端編程面試算法每日一題群

          來源:vortesnail

          https://juejin.cn/post/6966242922278682632

          最近在搭建自己的網(wǎng)站時,以前一直被自己認為寫起來很簡單的路由狠狠地給了我一巴掌,我既然怎么也想不到該怎么去合理地設計路由,痛定思痛,閱讀了很多文章及官方文檔,過程中也讀到了這一篇很好的基礎性文章,想翻譯下來向大家分享下!同時,文中一些沒講到點上的,我都會進行補充,歡迎大家閱讀與留言!

          另外,Twitter 已經(jīng)私信給原作者,得到了翻譯許可!

          React Router 是 React 社區(qū)最受歡迎的路由庫,當你需要在一個有多個頁面的 React 應用程序中根據(jù) URL 來導航到對應的頁面時,就可以使用 React Router 來處理這個問題,它會使你的應用的 UI 和 URL 保持同步。

          本教程將會向你介紹 React Router 5 以及你可以利用它而做到的一大堆事情。

          介紹

          我們都知道 React 是一個用于創(chuàng)建在客戶端進行渲染單頁應用(SPA)的流行庫,在一個 SPA 中可能有多個視圖(也可以叫頁面),但是與傳統(tǒng)的多頁應用程序不同的是,瀏覽這些頁面時不會導致整個頁面被重新加載。我們希望的是這些頁面能夠在當前頁面中進行內(nèi)聯(lián)渲染,當然了,如果我們習慣了多頁應用程序,那么希望 SPA 中也要具有以下的功能:

          • 每個頁面都應該有一個唯一指定該頁面的 URL,這是為了能讓用戶可以將 URL 加入書簽或直接輸入瀏覽器而訪問,比如 www.example.com/products
          • 點擊瀏覽器的后腿和前進按鈕都應該如其如期工作。
          • 動態(tài)生成的嵌套頁面最好也有一個自己的 URL,比如 www.example.com/products/shoes/101 ,其中 101 是產(chǎn)品 ID。

          路由是使瀏覽器的 URL 與頁面上正在展示的頁面保持同步的過程。React Router 讓你以聲明的方式處理路由,聲明式路由方法允許你控制應用程序中的數(shù)據(jù)流,基本的使用方式就像下面一樣簡單:

          <Route path="/about">
            <About />
          </Route>
          復制代碼

          這里簡單提一下聲明式路由函數(shù)式路由分別長啥樣:

          • 聲明式:<NavLink to='/products' />
          • 函數(shù)式:histor.push('/products')

          你可以把 <Route> 組件放在任何你想渲染路由的地方,因為 <Route><Link> 以及其它 React Router 的 APIs 都只是組件而已,所以你可以很容易地在 React 中啟動和運行路由。

          ?? 注意:有一個普遍的誤解,認為 React Router 是由 Facebook 開發(fā)的官方路由解決方案。實際上,它只是一個第三方庫,但因其設計和簡單性而廣受歡迎。

          概覽

          本教程將會分為幾個小節(jié),首先我們會使用 npm 來安裝 React 和 React Router,接著就直接介紹 React Router 的基礎知識。你會看到根據(jù)不同知識點而寫的不同的代碼演示,本教程中涉及的例子有:

          • 基本的導航路由
          • 嵌套路由
          • 帶路徑參數(shù)的嵌套路由
          • 權(quán)限路由

          所有與構(gòu)建這些路由有關的概念都將在此過程中討論。另外,該項目的全部代碼可在 GitHub repo 上找到。 現(xiàn)在就讓我們搞起來吧!

          安裝 React Router

          請保證你電腦上安裝了 nodenpm ,然后利用 create-react-app 來創(chuàng)建一個新的 React 項目,我們直接使用 npx 來進行項目的新建:

          npx create-react-app react-router-demo
          復制代碼

          npx 可以使你不需要全局安裝 create-react-app 就能創(chuàng)建 cra 項目。

          接下來切換到該項目目錄下:

          cd react-router-demo
          復制代碼

          React Router 庫包含三個包:react-router、react-router-dom 和 react-router-native 。路由操作相關的核心包是 react-router,而其他兩個是特定環(huán)境下使用的。如果你正在開發(fā)一個 web 應用,你應該使用 react-router-dom,如果你在使用 React Native 開發(fā)移動應用,則應該使用 react-router-native。 使用 npm 來安裝 react-router-dom

          npm install react-router-dom
          復制代碼

          然后執(zhí)行以下命令來啟動本地服務:

          npm run start
          復制代碼

          好了,你現(xiàn)在已經(jīng)有了一個安裝了 React Router 的 React 應用,你可以在 http://localhost:3000/ 查看該應用的運行情況了。

          React Router 基礎知識

          現(xiàn)在讓我們熟悉一下 React Router 的基礎知識,為了做到這一點,我們將制作一個有三個獨立頁面的應用程序:Home,Category 和 Products。

          Router 組件

          我們需要做的第一件事是將我們的 <App> 組件包裹在一個 <Router> 組件中(由 React Router 提供)。由于我們正在建立的是一個基于瀏覽器的 web 應用程序,我們可以使用 React Router API 中的兩種類型的路由:

          • BrowserRouter
          • HashRouter

          兩者主要區(qū)別在于他們創(chuàng)建的 URL 上:

          // <BrowserRouter>
          http://example.com/about
          // <HashRouter>
          http://example.com/#/about
          復制代碼

          <BrowserRouter> 在兩者中會更受歡迎些,因為它使用的是 HTML5 History API 來保持應用的頁面與 URL 同步,而 <HashRouter> 則使用的是 URL 的哈希部分(window.location.hash)。如果你的代碼運行在不支持 History API 的傳統(tǒng)瀏覽器上,你應該使用 <HashRouter> ,否則 <BrowserRouter> 對于大多數(shù)情況來說是更好的選擇。

          導入 BrowserRouter 組件并用其包裹 <App> 組件:

          // src/index.js

          import React from "react";
          import ReactDOM from "react-dom";
          import App from "./App";
          import { BrowserRouter as Router } from "react-router-dom";

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

          在上面代碼中,我們?yōu)檎麄€ <App> 組件創(chuàng)建了一個 history 實例,等會向大家解釋這意味著什么。

          ?? 為了能讓大家更加明白這兩者有啥區(qū)別,我會在下面做一個簡短的說明。

          <BrowserRouter><HashRouter> 區(qū)別

          BrowserRouter:

          BrowserRouter 要求服務端對發(fā)送的不同的 URL 都要返回對應的 HTML,比如說現(xiàn)在有如下兩個 URL 發(fā)送 GET 請求到服務端:

          http://example.com/home http://example.com/about
          復制代碼

          那么這個時候服務端拿到的是完整的 URL,這時候服務端就必須分別對 /home/about 做處理并返回相應的 HTML 來給到客戶端渲染。這個帶來的影響就是,如果你切換到某個服務端沒有做相應處理的頁面路由,比如:

          http://example.com/article
          復制代碼

          如果你在 SPA 中寫了這部分路由要渲染的頁面,在頁面無刷新情況下跳轉(zhuǎn)是沒啥問題的。但是如果你直接在此路由下進行頁面的刷新,就會得到一個 404。

          HashRouter

          HashRouter 在 URL 中使用哈希符號(#)來使服務端忽略 # 后面所有的 URL 內(nèi)容,比如你在瀏覽器地址欄中直接輸入以下 URL:

          http://example.com/#/home http://example.com/#/about
          復制代碼

          服務端拿到的只會是 http://example.com/ ,這樣服務端只需要對這個路由做處理并返回 HTML,然后后面的路由 /home/about 將全部交給客戶端(也就是我們的 SPA 應用)來處理并渲染對應的頁面。所以你在任意的路由進行頁面的刷新都不會是 404。

          History 的小知識

          history 這個庫可以讓你在 JavaScript 運行的任何地方都能輕松地管理回話歷史,history 對象抽象化了各個環(huán)境中的差異,并提供了最簡單易用的的 API 來給你管理歷史堆棧、導航,并保持會話之間的持久化狀態(tài)。— React Training 文檔

          每個 <Router> 組件都會創(chuàng)建一個 history 對象,它記錄了當前的位置(history.location),還記錄了堆棧中以前的位置。在當前位置發(fā)生變化時,頁面會被重新渲染,于是你就有一種導航跳轉(zhuǎn)的感覺。

          那么如何改變當前的位置呢?也就是說如何做到導航跳轉(zhuǎn)呢?這時候 history 的作用就來了,這個對象暴露了一些方法,比如 history.pushhistory.replace ,它們就可以拿來處理上面的問題。

          當你點擊一個 <Link> 組件時,history.push 就會被調(diào)用,而當你使用一個 <Redirect> 組件時,history.replace 就會被調(diào)用。其它的方法比如 history.goBackhistory.goForward 可以用來在歷史堆棧中回溯或前進。

          LinkRoute 組件

          可以說 <Route> 組件是 React Router 中最重要的組件了,如果當前的位置與路由的路徑匹配,就會渲染對應的 UI。理想情況下,<Route> 組件應該有一個名為 path 的屬性,如果路徑名稱與當前位置匹配,它就會被渲染。

          <Link> 組件被用來在頁面之間進行導航,它其實就是 HTML 中的 <a> 標簽的上層封裝,不過在其源碼中使用 event.preventDefault 禁止了其默認行為,然后使用 history API 自己實現(xiàn)了跳轉(zhuǎn)。我們都知道,如果使用 <a> 標簽去進行導航的話,整個頁面都會被刷新,這是我們不希望看到的,當然,跳轉(zhuǎn)到首頁這種行為我倒是蠻喜歡用 <a> 標簽的~

          所以我們使用 <Link> 組件來導航到一個目標 URL,可以在不刷新頁面的情況下重新渲染頁面。 現(xiàn)在我們已經(jīng)知道了所有要完成我們的 APP 所需要的知識,接著更新 src/App.js ,如下所示:

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

          const Home = () => (
            <div>
              <h2>Home</h2>
            </div>

          );

          const Category = () => (
            <div>
              <h2>Category</h2>
            </div>

          );

          const Products = () => (
            <div>
              <h2>Products</h2>
            </div>

          );

          export default function App() {
            return (
              <div>
                <nav className="navbar navbar-light">
                  <ul className="nav navbar-nav">
                    <li>
                      <Link to="/">Home</Link>
                    </li>
                    <li>
                      <Link to="/category">Category</Link>
                    </li>
                    <li>
                      <Link to="/products">Products</Link>
                    </li>
                  </ul>
                </nav>
                {/* 如果當前路徑與 path 匹配就會渲染對應的組件 */}
                <Route path="/">
                  <Home />
                </Route>
                <Route path="/category">
                  <Category />
                </Route>
                <Route path="/products">
                  <Products />
                </Route>
              </div>

            );
          }
          復制代碼

          在上面的 App.js 中我們定義了三個組件分別為 HomeCategoryProducts 。雖然現(xiàn)在這樣做還算說得過去,但是當一個組件內(nèi)的代碼變得很多時,最好的方式是為每一個組件建立一個獨立的文件。就我的經(jīng)驗來說,如果一個組件占用的代碼超過 10 行,我就會為它創(chuàng)建一個新的文件。所以從第二個演示開始,我將會為那些代碼過多而放在 App.js 中會顯得特別臃腫的組件單獨創(chuàng)建一個文件來存放。

          App 組件中我們已經(jīng)寫好了路由的邏輯,<Route>path 如果與當前位置相匹配的話,對應的組件也會被渲染。在以前,要被渲染的組件應該作為 <Route> 組件的屬性傳入的,但是現(xiàn)在的版本只要作為 <Route> 的子組件就可以被正確渲染。

          在上面的路由設計中,/ 將會匹配 //category 以及 /products ,這帶來的結(jié)果是會同時在頁面上渲染三個組件,即 HomeCategoryProducts ,這不是我們所希望看到的。因此,我們可以通過傳入 exact 屬性給 <Route> 組件來避免這個問題出現(xiàn):

          <Route exact path="/">
            <Home />
          </Route>
          復制代碼

          所以如果你期望的是根據(jù)一個安全匹配的 path 去渲染對應的組件,你就應該考慮使用屬性 exact 了。

          嵌套路由

          如果想要使用嵌套路由,我們要更加深入地理解 <Route> 組件的工作方式,接下來我們一探究竟。 通過 React Router 官方文檔 可知,使用 <Route> 渲染一個頁面(或組件)的最佳方式是使用子元素方式,就像我們上面的演示一樣。然而,還是有一些其它的方式,這些方式是為了兼容在沒有引進 hooks 之前的早期版本的 React Router 構(gòu)建的 APP:

          • component :當 URL 匹配時,React Router 會使用 React.createElement 從給定的組件創(chuàng)建一個 React 元素。
          • render :能使你便捷的渲染內(nèi)聯(lián)組件或是嵌套組件,你可以給這個屬性傳入一個函數(shù),當路由的路徑匹配時調(diào)用,返回一個元素。
          • children :與 render 屬性有些類似,它也是接收一個函數(shù),不同的是,無論現(xiàn)在 path 是否與當前位置匹配,這個函數(shù)都會被執(zhí)行。

          路徑和匹配

          屬性 path 是用于識別路由應該被匹配到的 URL 部分,它使用 path-to-regexp 庫將字符串形式的 path 轉(zhuǎn)換為一個正則表達式,然后將它與當前的位置進行匹配。

          如果路由的 path 與當前位置完全匹配時,一個 match 對象 就會被創(chuàng)建,這個對象中有關于 URL 和路徑的更多信息,這些信息可以通過這個對象的屬性來進行訪問,下面為大家列出有哪些屬性:

          • match.url :一個字符串(string),返回 URL 匹配的部分,這對于構(gòu)建嵌套的 <Link> 組件特別有用。
          • match.path :一個字符串(string),返回路由的 path ,即 <Route path=""> ,我們將使用它來構(gòu)建嵌套的 <Route> 組件。
          • match.isExact :一個布爾值(boolean),如果匹配時精確的,即沒有任何尾部字符,則返回 true
          • match.params :一個對象(object),返回的是從 URL 中解析出來鍵值對。

          屬性的隱式傳遞

          請注意,當使用 component 屬性來渲染路由時,matchlocationhistory 這些路由屬性是隱式地傳給被渲染的組件的。但當使用比較新的路由渲染模式時,情況有所不同。

          比如,以下面這個組件為例:

          const Home = (props) => {
            console.log(props);

            return (
              <div>
                <h2>Home</h2>
              </div>

            );
          };
          復制代碼

          以這種方式渲染路由:

          <Route exact path="/" component={Home} />
          復制代碼

          控制臺打印的日志:

          {
            history: { ... }
            location: { ... }
            match: { ... }
          }
          復制代碼

          但是現(xiàn)在如果以這種方式渲染路由:

          <Route exact path="/">
            <Home />
          </Route>
          復制代碼

          控制臺打印的日志將會是這樣:

          {}
          復制代碼

          可能你會覺得以這種方式來使用不太好,因為我們在渲染的組件中拿不到路由屬性了。但是不用擔心,React v5.1 引入了幾個 hooks,通過在組件內(nèi)部使用這些 hooks 可以助你訪問到上面隱式傳遞的任何路由屬性,這是一種新的管理路由狀態(tài)的方法,并在一定程度上使我們的組件更加整潔。 我將在本教程中使用其中的一些 hooks,但是如果你想要更深入地了解,可以查看 React Router v5.1 的發(fā)布公告。請注意,hooks 是在 React 的 16.8 版本中引入的,所以你至少需要在這個版本以上才能使用它們。

          Switch 組件

          在開始代碼演示之前,我想先向大家介紹一下 Switch 組件。當多個 <Route> 被一起使用時,所有匹配到的路由都會被渲染,大家看下下面的代碼,我會向大家解釋為什么 <Switch> 是有用的:

          <Route exact path="/"><Home /></Route>
          <Route path="/
          category"><Category /></Route>
          <Route path="
          /products"><Products /></Route>
          <Route path="
          /:id">
            <p>This text will render for any route other than '/'</p>
          </Route>
          復制代碼

          如果 URL 是 /products ,那么 path/products/:id 的路由會一起在頁面渲染出來,這就是這樣設計的。然而,這種行為基本不可能是我們所期待的,所以才要用到 <Switch> ,有了 <Switch> ,只有第一個與當前 URL 匹配到的子 <Route> 才會被渲染:

          <Switch>
            <Route exact path="/">
              <Home />
            </Route>

            <Route path="/category">
              <Category />
            </Route>

            <Route path="/products">
              <Products />
            </Route>

            <Route path="/:id">
              <p>This text will render for any route other than those defined above</p>
            </Route>

          </Switch>
          復制代碼

          path:id 部分用于動態(tài)路由,它將匹配斜杠后面的任何東西,并且這個匹配到的值在被渲染的組件中是可以拿到的,我們會在下一節(jié)演示如何取這個值。

          現(xiàn)在我們知道了關于 <Route><Switch> 組件的一切,讓我們看看本節(jié)的主題嵌套路由的示例吧。

          動態(tài)嵌套路由

          在上面的示例中我們創(chuàng)建了 //category/products 路由,但是如果我們想要匹配一個 /category/shoes 的路由咋辦呢?讓我們更新一波 src/App.js 的代碼:

          import React from "react";
          import { Link, Route, Switch } from "react-router-dom";
          import Category from "./Category";

          const Home = () => (
            <div>
              <h2>Home</h2>
            </div>

          );

          const Products = () => (
            <div>
              <h2>Products</h2>
            </div>

          );

          export default function App() {
            return (
              <div>
                <nav className="navbar navbar-light">
                  <ul className="nav navbar-nav">
                    <li>
                      <Link to="/">Home</Link>
                    </li>
                    <li>
                      <Link to="/category">Category</Link>
                    </li>
                    <li>
                      <Link to="/products">Products</Link>
                    </li>
                  </ul>
                </nav>

                <Switch>
                  <Route path="/">
                    <Home />
                  </Route>
                  <Route path="/category">
                    <Category />
                  </Route>
                  <Route path="/products">
                    <Products />
                  </Route>
                </Switch>
              </div>

            );
          }
          復制代碼

          你應該注意到了,我已經(jīng)把 Category 組件獨立出來了,而我們的嵌套路由就在這個組件中去定義,那么現(xiàn)在就來創(chuàng)建 Category.js 吧!

          // src/Category.js

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

          const Item = () => {
            const { name } = useParams();

            return (
              <div>
                <h3>{name}</h3>
              </div>

            );
          };

          const Category = () => {
            const { url, path } = useRouteMatch();

            return (
              <div>
                <ul>
                  <li>
                    <Link to={`${url}/shoes`}>Shoes</Link>
                  </li>
                  <li>
                    <Link to={`${url}/boots`}>Boots</Link>
                  </li>
                  <li>
                    <Link to={`${url}/footwear`}>Footwear</Link>
                  </li>
                </ul>
                <Route path={`${path}/:name`}>
                  <Item />
                </Route>
              </div>

            );
          };

          export default Category;
          復制代碼

          在這里我們使用 useRouteMatch hook 來獲取上面我們說過的 match 對象。如前所述,match.url 為 URL 匹配的部分,用于構(gòu)建嵌套鏈接。match.path 為路由的 path ,用于構(gòu)建嵌套路由。 如果你覺得在 match 對象中的屬性有理解上的困難,沒關系,console.log(useRouteMatch()) 打印在控制臺仔細看看它的屬性的值是什么,你就大概能知道啥意思了。

          <Route path={`${path}/:name`}>
            <Item />
          </Route>
          復制代碼

          這就是我們對動態(tài)路由的第一次嘗試,因為我們沒有將路由寫死,而是在屬性 path 中使用了一個變量,:name 是一個路徑參數(shù),可以捕捉到 category/ 之后的所有內(nèi)容,直到遇到另外一個正斜杠(/)。因此,像 category/running-shoes 這樣的路徑名稱將會創(chuàng)建一個 params 對象,如下所示:

          {
            name"running-shoes";
          }
          復制代碼

          為了在 <Item> 組件中訪問到這個值,我們使用 useParams hook ,它返回一個 URL 參數(shù)的鍵值對的對象。 你可以在控制臺中打印下看看返回的到底是什么,那么現(xiàn)在 Category 應該就會有三個子路由了。

          帶路徑參數(shù)的嵌套路由

          我們把這個例子在復雜化一點,以便我們更好地去理解。在實際開發(fā)中,我們的路由必須具有處理數(shù)據(jù)并動態(tài)展示它們的功能。假設有一些 API 返回的產(chǎn)品數(shù)據(jù),其格式如下:

          const productData = [
            {
              id: 1,
              name: "NIKE Liteforce Blue Sneakers",
              description:
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie.",
              status: "Available",
            },
            {
              id: 2,
              name: "Stylised Flip Flops and Slippers",
              description:
                "Mauris finibus, massa eu tempor volutpat, magna dolor euismod dolor.",
              status: "Out of Stock",
            },
            {
              id: 3,
              name: "ADIDAS Adispree Running Shoes",
              description:
                "Maecenas condimentum porttitor auctor. Maecenas viverra fringilla felis, eu pretium.",
              status: "Available",
            },
            {
              id: 4,
              name: "ADIDAS Mid Sneakers",
              description:
                "Ut hendrerit venenatis lacus, vel lacinia ipsum fermentum vel. Cras.",
              status: "Out of Stock",
            },
          ];
          復制代碼

          假設我們還需要為以下的路徑創(chuàng)建路由:

          • /products :這應該顯示一個產(chǎn)品列表。
          • /products/:productId :如果匹配到 :productId 那么就應該顯示這個產(chǎn)品的數(shù)據(jù),如果沒有就顯示一個錯誤信息。

          創(chuàng)建一個新文件 src/Products.js 文件,并添加以下代碼:

          import React from "react";
          import { Link, Route, useRouteMatch } from "react-router-dom";
          import Product from "./Product";

          const Products = ({ match }) => {
            const productData = [ ... ];
            const { url } = useRouteMatch();

            /* Create an array of `<li>` items for each product */
            const linkList = productData.map((product) => {
              return (
                <li key={product.id}>
                  <Link to={`${url}/${product.id}`}>{product.name}</Link>
                </li>

              );
            });

            return (
              <div>
                <div>
                  <div>
                    <h3>Products</h3>
                    <ul>{linkList}</ul>
                  </div>
                </div>

                <Route path={`${url}/:productId`}>
                  <Product data={productData} />
                </Route>
                <Route exact path={url}>
                  <p>Please select a product.</p>
                </Route>
              </div>

            );
          };

          export default Products;
          復制代碼

          首先我們使用了 useRouteMatch 鉤子,并從 match 對象中拿到 URL ,然歐根據(jù)每個產(chǎn)品的 id 屬性來建立一個 <Link> 組件的列表,并將其返回存儲到一個 linkList 變量中。

          第一個路由使用 path 中的一個變量,它與產(chǎn)品 id 對應,當匹配成功時,我們就會渲染 <Product> 組件(我們馬上進行定義),將我們的產(chǎn)品數(shù)據(jù)傳遞給它:

          <Route path={`${url}/:productId`}>
            <Product data={productData} />
          </Route>
          復制代碼

          注意到第二個路由中有一個 exact 屬性,只有當 URL 是 /products 且其后面沒有任何路徑參數(shù)時才會渲染。

          OK,下面是 <Product> 組件的代碼,你只需要在 src/Product.js 創(chuàng)建這個文件:

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

          const Product = ({ data }) => {
            const { productId } = useParams();
            const product = data.find((p) => p.id === Number(productId));
            let productData;

            if (product) {
              productData = (
                <div>
                  <h3> {product.name} </h3>
                  <p>{product.description}</p>
                  <hr />
                  <h4>{product.status}</h4>
                </div>

              );
            } else {
              productData = <h2> Sorry. Product doesn't exist </h2>;
            }

            return (
              <div>
                <div>{productData}</div>
              </div>

            );
          };

          export default Product;
          復制代碼

          find 方法用于在產(chǎn)品數(shù)組中搜索一個 id 屬性與 match.params.productId 相同的對象。如果該產(chǎn)品存在,就會渲染對應的數(shù)據(jù)。如果不存在,就會顯示 “產(chǎn)品不存在”的信息。

          最后,更新你的 <App> 組件,如下所示:

          import React from "react";
          import { Link, Route, Switch } from "react-router-dom";
          import Category from "./Category";
          import Products from "./Products";

          const Home = () => (
            <div>
              <h2>Home</h2>
            </div>

          );

          export default function App() {
            return (
              <div>
                <nav className="navbar navbar-light">
                  <ul className="nav navbar-nav">
                    <li>
                      <Link to="/">Home</Link>
                    </li>
                    <li>
                      <Link to="/category">Category</Link>
                    </li>
                    <li>
                      <Link to="/products">Products</Link>
                    </li>
                  </ul>
                </nav>

                <Switch>
                  <Route exact path="/">
                    <Home />
                  </Route>
                  <Route path="/category">
                    <Category />
                  </Route>
                  <Route path="/products">
                    <Products />
                  </Route>
                </Switch>
              </div>

            );
          }
          復制代碼

          現(xiàn)在你就可以在瀏覽器中訪問你寫的這些路由了,如果你選擇“Products”,你會看到一個子菜單,并且顯示了產(chǎn)品的數(shù)據(jù)。 嘗試著好好理解下這個演示中的代碼,確保你要掌握這部分內(nèi)容。

          權(quán)限路由

          在如今大多數(shù)網(wǎng)站應用中,只有登錄了的用戶才能訪問網(wǎng)站的某些部分,比如掘金登錄之后才會有進入到個人主頁的入口。接下來這一節(jié),我會告訴大家如何去實現(xiàn)一個權(quán)限路由,也就是說如果有人試圖訪問 /admin ,他將會首先被要求登錄。

          然而,我們需要先了解 React Router 的幾個方面。

          <Redirect> 組件

          與服務端的重定向類似,React Router 的 Redirect component 將會用一個新的位置替換歷史棧中的當前位置,新的位置是由 to 屬性來指向的。那么接下來我就會向大家介紹如何使用 <Redirect>

          <Redirect to={{ pathname: '/login', state: { from: location }}}
          復制代碼

          如果有人試圖在未登錄狀態(tài)下訪問 /admin 路由,他就會被重定向到 /login 路由,關于當前位置的信息是由 state 屬性進行傳遞的,這樣做是為了在用戶登錄成功之后,用戶又可以被重定向到他試圖訪問的路由頁面。

          自定義路由

          如果我們需要決定一個路由是否應該被渲染,那么編寫一個自定義路由是個好辦法,接下來在 src 目錄下創(chuàng)建一個新文件 PrivateRoute.js ,并寫入以下代碼:

          import React from "react";
          import { Redirect, Route, useLocation } from "react-router-dom";
          import { fakeAuth } from "./Login";

          const PrivateRoute = ({ component: Component, ...rest }) => {
            const location = useLocation();

            return (
              <Route {...rest}>
                {fakeAuth.isAuthenticated === true ? (
                  <Component />
                ) : (
                  <Redirect to={{ pathname: "/login", state: { from: location } }} />
                )}
              </Route>

            );
          };

          export default PrivateRoute;
          復制代碼

          如你所見,在函數(shù)定義中,我們將接收到的 props 中拿到一個 Component 還有一個剩余屬性 restComponent 將包含我們的 <PrivateRoute> 所保護的任何組件(在該例中為 Admin 組件),其余的屬性將會通過 rest 傳遞給 <Route>

          我們返回的是一個 <Route> 組件,該組件會根據(jù)用戶是否登錄來決定是否渲染受到保護的組件,如果沒有登錄將會重定向到 /login 路由。這是由 fakeAuth.isAuthenticated 屬性決定的,這個屬性從 <Login> 組件中導入。 這種封裝的方法好處在于是聲明式的,而且 <PrivateRoute> 可被重復使用。

          實踐權(quán)限路由

          現(xiàn)在我們可以修改 src/App.js

          import React from "react";
          import { Link, Route, Switch } from "react-router-dom";
          import Category from "./Category";
          import Products from "./Products";
          import Login from "./Login";
          import PrivateRoute from "./PrivateRoute";

          const Home = () => (
            <div>
              <h2>Home</h2>
            </div>

          );

          const Admin = () => (
            <div>
              <h2>Welcome admin!</h2>
            </div>

          );

          export default function App() {
            return (
              <div>
                <nav className="navbar navbar-light">
                  <ul className="nav navbar-nav">
                    <li>
                      <Link to="/">Home</Link>
                    </li>
                    <li>
                      <Link to="/category">Category</Link>
                    </li>
                    <li>
                      <Link to="/products">Products</Link>
                    </li>
                    <li>
                      <Link to="/admin">Admin area</Link>
                    </li>
                  </ul>
                </nav>

                <Switch>
                  <Route exact path="/">
                    <Home />
                  </Route>
                  <Route path="/category">
                    <Category />
                  </Route>
                  <Route path="/products">
                    <Products />
                  </Route>
                  <Route path="/login">
                    <Login />
                  </Route>
                  <PrivateRoute path="/admin" component={Admin} />
                </Switch>
              </div>

            );
          }
          復制代碼

          正如你所見,我們在文件的頂部添加了一個 <Admin> 組件,并在 <Switch> 組件下添加了一個 <PrivateRoute> 組件。正如前面所說,如果用戶已經(jīng)登錄的話,這個自定義路由將會渲染的是 <Admin> 組件,否則,用戶會被重定向到 /login

          最后,這里是 Login 組件代碼:

          import React, { useState } from "react";
          import { Redirect, useLocation } from "react-router-dom";

          export default function Login() {
            const { state } = useLocation();
            const { from } = state || { from: { pathname"/" } };
            const [redirectToReferrer, setRedirectToReferrer] = useState(false);

            const login = () => {
              fakeAuth.authenticate(() => {
                setRedirectToReferrer(true);
              });
            };

            if (redirectToReferrer) {
              return <Redirect to={from} />;
            }

            return (
              <div>
                <p>You must log in to view the page at {from.pathname}</p>
                <button onClick={login}>Log in</button>
              </div>

            );
          }

          /* A fake authentication function */
          export const fakeAuth = {
            isAuthenticatedfalse,
            authenticate(cb) {
              this.isAuthenticated = true;
              setTimeout(cb, 100);
            },
          };
          復制代碼

          我們使用 useLocation hook 來訪問路由的 location 屬性,也就是從 state 屬性帶過來的。然后我們使用對象的解構(gòu)來獲取用戶在被要求登錄之前試圖訪問的 URL,這個這個值不存在,我們就設為 { pathname: "/" }

          然后我們使用 React 的 useState 鉤子來初始化一個 redirectToReferrer 狀態(tài)為 false ,根據(jù)這個值來決定用戶是被重定向到他們想要訪問的路徑(也就是說用戶已經(jīng)登錄了),還是向用戶展示一個按鈕讓他們登錄。

          一旦按鈕被點擊,fakeAuth.authenticate 這個方法就會被執(zhí)行,它將 fakeAuth.isAuthenticated 設為 true ,并(在一個回調(diào)函數(shù)中)將 redirectToReferrer 狀態(tài)更新為 true ,這將導致組件重新渲染,用戶將被重定向。

          完整示例

          以下就是我們使用學到的東西做出來的最終 demo:

          Edit React Router Demo

          最后

          歡迎關注【前端瓶子君】??ヽ(°▽°)ノ?
          回復「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認真的解答喲!
          回復「交流」,吹吹水、聊聊技術、吐吐槽!
          回復「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
           》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持




          瀏覽 70
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄片在线看午夜 | 一级操逼片 | 超碰97在线免费观看 | 理论片无码 | 撸撸一撸撸操逼视频。 |