前言
React Router是React的事實上的標準路由庫。當您需要在具有多個檢視的React應用程式中導航時,將需要一個路由器來管理URL。React Router會做到這一點,使您的應用程式UI和URL保持同步。
本教程向您介紹React Router v5以及您可以使用它進行的許多操作。
介紹React是一個流行的庫,用於建立在客戶端呈現的單頁應用程式(SPA)。SPA可能具有多個檢視(又稱頁面),並且與傳統的多頁面應用程式不同,在這些檢視中導航不應導致整個頁面被重新載入。相反,我們希望檢視在當前頁面中內聯呈現。習慣了多頁應用程式的終端使用者希望SPA中具有以下功能:
應用程式中的每個檢視都應具有唯一指定該檢視的URL。這樣一來,使用者便可以在URL上新增書籤以供以後參考。例如,www.example.com/products。瀏覽器的後退和前進按鈕應該可以正常工作。動態生成的巢狀檢視最好也應具有自己的URL。例如,,example.com/products/shoes/101其中101是產品ID。路由是保持瀏覽器URL與頁面上呈現的內容同步的過程。React Router使您可以宣告式處理路由。宣告式路由方法允許您通過說“路由應如下所示”來控制應用程式中的資料流:
<Route path="/about" component={About} />
您可以將<Route>元件放置在要渲染路線的任何位置。由於<Route>,<Link>以及我們將要處理的所有其他React Router API都是元件,因此您可以輕鬆地習慣於在React中進行路由。
開始之前的註釋。人們普遍誤以為React Router是Facebook開發的官方路由解決方案。實際上,它是一個第三方庫,它以其設計和簡單性而廣受歡迎。如果您的需求僅限於用於導航的路由器,則可以從頭開始實施自定義路由器,而不會帶來太多麻煩。但是,了解React Router的基礎知識將使您更好地了解路由器應如何工作。
總覽本教程分為不同的部分。首先,我們將使用npm設定React和React Router。然後,我們將直接進入React Router基礎知識。您將在實際中找到React Router的不同程式碼演示。本教程介紹的示例包括:
基本導航路線巢狀路由帶路徑引數的巢狀路由保護路由與構建這些路線有關的所有概念將一路討論。該專案的完整程式碼可在此GitHub儲存庫中找到。進入特定的演示目錄後,執行npm install以安裝依賴項。要在開發伺服器上為應用程式提供服務,請執行npm start並http://localhost:3000/轉至觀看演示示例。
讓我們開始吧!
設定React Router我假設您已經有一個開發環境正在執行。如果沒有,請轉到“ React和JSX入門 ”。另外,您可以使用Create React App生成建立基本React專案所需的檔案。這是Create React App生成的預設目錄結構:
react-router-demo ├── .gitignore ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── README.md ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ └── registerServiceWorker.js └── yarn.lock
該陣營路由器庫包括三個包:react-router,react-router-dom,和react-router-native。react-router是路由器的核心軟體包,而其他兩個是特定於環境的。react-router-dom如果您正在構建網站,並且react-router-native正在使用React Native在移動應用程式開發環境中,則應使用。
使用npm進行安裝react-router-dom:
npm install --save react-router-dom
React Router基礎這是我們路線的外觀示例:
<Router>/* App component */class App extends React.Component { render() { return ( <div> <nav className="navbar navbar-light"> <ul className="nav navbar-nav"> /* Link components are used for linking to other views */ <li> <Link to="/">Homes</Link> </li> <li> <Link to="/category">Category</Link> </li> <li> <Link to="/products">Products</Link> </li> </ul> </nav> /* Route components are rendered if the path prop matches the current URL*/ <Route path="/" component={Home} /> <Route path="/category" component={Category} /> <Route path="/products" component={Products} /> </div> ); }} <Route exact path="/" component={Home} /> <Route path="/category" component={Category} /> <Route path="/login" component={Login} /> <Route path="/products" component={Products} /></Router>
路由器您需要一個路由器元件和幾個路由元件來設定上述基本路由。由於我們正在構建基於瀏覽器的應用程式,因此可以使用React Router API中的兩種型別的路由器:
<BrowserRouter><HashRouter>它們之間的主要區別在於它們建立的URL:
// <BrowserRouter>http://example.com/about// <HashRouter>/file/2020/06/15/20200615133655_13874.jpg API歷史來跟蹤你的路由器的歷史當中是兩個更受歡迎。的<HashRouter>,而另一方面,使用URL(的雜湊部分window.location.hash)記住的東西。如果您打算支援舊版瀏覽器,則應堅持使用<HashRouter>。
將<BrowserRouter>元件包裝在App元件周圍。
index.js
/* Import statements */import React from "react";import ReactDOM from "react-dom";/* App is the entry point to the React code.*/import App from "./App";/* import BrowserRouter from 'react-router-dom' */import { BrowserRouter } from "react-router-dom";ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById("root"));
注意:路由器元件只能有一個子元素。子元素可以是HTML元素(例如div)或react元件。
為了使React Router正常工作,您需要從react-router-dom庫中匯入相關的API 。在這裡,我已將匯入BrowserRouter到中index.js。我還App從匯入了元件App.js。App.js您可能已經猜到了,這是React元件的入口點。
上面的程式碼為我們整個App元件建立了一個歷史例項。讓我正式向您介紹歷史。
歷史
history是一個JavaScript庫,可讓您在執行JavaScript的任何地方輕鬆管理會話歷史記錄。history提供了一個最小的API,可讓您管理歷史記錄堆疊,導航,確認導航以及在會話之間保持狀態。— React Training文件
每個路由器元件都建立一個歷史物件,該物件跟蹤當前位置(history.location)以及堆疊中的先前位置。當前位置更改時,將重新渲染檢視,您會感到導航。當前位置如何變化?歷史物件具有諸如history.push()和的方法history.replace()。history.push()單擊<Link>元件history.replace()時呼叫,使用時呼叫<Redirect>。其他方法(例如history.goBack()和history.goForward())可用於通過後退或前進頁面來瀏覽歷史記錄堆疊。
繼續,我們有連結和路線。
連結和路線該<Route>元件是React路由器中最重要的元件。如果當前位置與路線的路徑匹配,它將呈現一些UI。理想情況下,<Route>元件應具有一個名為的prop path,並且如果路徑名與當前位置匹配,則它將被呈現。
<Link>另一方面,該元件用於在頁面之間導航。與HTML錨點元素相當。但是,使用錨鏈接會導致瀏覽器重新整理,這是我們不希望的。因此,我們可以使用<Link>導航到特定的URL,並在不重新整理瀏覽器的情況下重新渲染檢視。
我們已經介紹了建立基本路由器所需的所有知識。讓我們來建立一個。
演示1:基本路由src / App.js
/* Import statements */import React, { Component } from "react";import { Link, Route, Switch } from "react-router-dom";/* Home component */const Home = () => ( <div> <h2>Home</h2> </div>);/* Category component */const Category = () => ( <div> <h2>Category</h2> </div>);/* Products component */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="/">Homes</Link> </li> <li> <Link to="/category">Category</Link> </li> <li> <Link to="/products">Products</Link> </li> </ul> </nav> /* Route components are rendered if the path prop matches the current URL */ <Route path="/" component={Home} /> <Route path="/category" component={Category} /> <Route path="/products" component={Products} /> </div> );}
我們已經在內部聲明了Home,Category和Products的元件App.js。儘管現在還可以,但是當元件開始變大時,最好為每個元件建立一個單獨的檔案。根據經驗,如果元件佔用的程式碼超過10行,我通常會為其建立一個新檔案。從第二個演示開始,我將為已變得太大而無法容納在檔案中的元件建立一個單獨的App.js檔案。
在App元件內部,我們編寫了路由邏輯。所述<Route>的路徑與當前位置匹配,並且元件被渲染。應該渲染的元件作為第二個屬性傳入。
這裡/匹配/和/category。因此,兩條路線都匹配並渲染。我們如何避免這種情況?您應該使用以下命令將exact= {true}道具傳遞到路由器path='/':
<Route exact={true} path="/" component={Home} />
如果只在路徑完全相同時才希望顯示路線,則應使用精確的道具。
巢狀路由
要建立巢狀路線,我們需要更好地了解其<Route>工作原理。來做吧。
<Route> 您可以使用三個道具來定義要渲染的內容:
元件。我們已經看到了這一點。匹配URL時,路由器使用會從給定的元件中建立一個React元素React.createElement。渲染。這對於內聯渲染很方便。渲染道具需要一個函式,當位置與路線的路徑匹配時,該函式將返回一個元素。孩子們。children props與render類似,因為它需要一個返回React元素的函式。但是,無論路徑與位置是否匹配,都會渲染子級。路徑匹配該路徑用於標識路由器應匹配的URL部分。它使用Path-to-RegExp庫將路徑字串轉換為正則表示式。然後將其與當前位置進行匹配。
如果路由器的路徑和位置成功匹配,則會建立一個物件,我們將其稱為匹配物件。匹配物件包含有關URL和路徑的更多資訊。可通過以下屬性訪問此資訊:
match.url。一個字串,返回URL的匹配部分。這對於構建巢狀<Link>s 尤其有用match.path。返回路由路徑字串的字串,即<Route path="">。我們將使用它來構建巢狀<Route>的。match.isExact。如果匹配完全正確(沒有任何尾隨字元),則返回true的布林值。match.params。一個物件,其中包含由Path-to-RegExp包解析的URL中的鍵/值對。
既然我們已經了解了<Route>s,那麼讓我們用巢狀路由構建一個路由器。
開關元件
在開始演示程式碼之前,我想向您介紹該<Switch>元件。當多個<Route>一起使用時,所有匹配的路由都被包含在內。考慮一下演示1中的這段程式碼。我添加了一條新路線來說明為什麼<Switch>有用:
<Route exact path="/" component={Home}/><Route path="/products" component={Products}/><Route path="/category" component={Category}/><Route path="/:id" render = {()=> (<p> I want this text to show up for all routes other than '/', '/products' and '/category' </p>)}/>
如果URL是/products,/products則呈現所有與該位置匹配的路由。因此,<Route>with路徑:id與Products元件一起呈現。這是設計使然。但是,如果這不是您所期望的行為,則應將<Switch>元件新增到路由中。使用<Switch>,只有<Route>與位置匹配的第一個孩子會被渲染。
演示2:巢狀路由
早前,我們創造了路線/,/category和/products。如果我們想要表單的URL /category/shoes怎麼辦?
src / App.js
import React, { Component } from "react";import { Link, Route, Switch } from "react-router-dom";import Category from "./Category";export default function App() { return ( <div> <nav className="navbar navbar-light"> <ul className="nav navbar-nav"> <li> <Link to="/">Homes</Link> </li> <li> <Link to="/category">Category</Link> </li> <li> <Link to="/products">Products</Link> </li> </ul> </nav> <Switch> <Route exact path="/" component={Home} /> <Route path="/category" component={Category} /> <Route path="/products" component={Products} /> </Switch> </div> );}/* Code for Home and Products component omitted for brevity */
與早期版本的React Router不同,在版本4及更高版本中,巢狀<Route>s應該最好放在父元件內部。也就是說,類別元件是此處的父元件,我們將宣告category/:name父元件內部的路由。
src / Category.jsx
import React from "react";import { Link, Route } from "react-router-dom";const Category = ({ match }) => { return ( <div> {" "} <ul> <li> <Link to={`${match.url}/shoes`}>Shoes</Link> </li> <li> <Link to={`${match.url}/boots`}>Boots</Link> </li> <li> <Link to={`${match.url}/footwear`}>Footwear</Link> </li> </ul> <Route path={`${match.path}/:name`} render={({ match }) => ( <div> {" "} <h3> {match.params.name} </h3> </div> )} /> </div> );};export default Category;
首先,我們為巢狀路線聲明了兩個連結。如前所述,match.url將用於構建巢狀連結和match.path巢狀路由。如果您在理解匹配的概念時遇到困難,請console.log(match)提供一些有用的資訊,可能有助於澄清它。
<Route path={`${match.path}/:name`} render={({ match }) => ( <div> <h3> {match.params.name} </h3> </div> )}/>
這是我們首次嘗試動態路由。我們沒有在路徑中硬編碼,而是在路徑名中使用了變數。:name是一個路徑引數,捕獲所有內容,category/直到遇到另一個正斜槓為止。因此,像這樣的路徑products/running-shoes名將建立一個params物件,如下所示:
{ name: "running-shoes";}
捕獲的資料應在道具傳遞方式下match.params或props.match.params取決於道具傳遞方式而可訪問。另一個有趣的事情是我們使用了render道具。render對於不需要自身元件的行內函數,props非常方便。
演示3:具有Path引數的巢狀路由
讓事情變得更加複雜吧?現實世界中的路由器必須處理資料並動態顯示。假設我們具有以下形式的伺服器API返回的產品資料。
src / Products.jsxconst 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", },];
我們需要為以下路徑建立路由:
/products。這應該顯示產品列表。/products/:productId。如果:productId存在的產品應顯示產品資料,如果不存在,則應顯示錯誤訊息。src / Products.jsx
/* Import statements have been left out for code brevity */const Products = ({ match }) => { const productsData = [ { id: 1, name: "NIKE Liteforce Blue Sneakers", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie.", status: "Available", }, //Rest of the data has been left out for code brevity ]; /* Create an array of `<li>` items for each product */ const linkList = productsData.map((product) => { return ( <li> <Link to={`${match.url}/${product.id}`}>{product.name}</Link> </li> ); }); return ( <div> <div> <div> <h3> Products</h3> <ul> {linkList} </ul> </div> </div> <Route path={`${match.url}/:productId`} render={(props) => <Product data={productsData} {...props} />} /> <Route exact path={match.url} render={() => <div>Please select a product.</div>} /> </div> );};
首先,我們<Links>使用productsData.ids 建立了s 的列表並將其儲存在中linkList。路由在路徑字串中採用與產品ID對應的引數。
<Route path={`${match.url}/:productId`} render={(props) => <Product data={productsData} {...props} />}/>
您可能期望component = { Product }使用內聯渲染功能。問題在於我們需要將productsData所有現有道具與產品元件一起傳遞。儘管還有其他方法可以執行此操作,但我發現此方法最簡單。{...props}使用ES6的傳播語法將整個props物件傳遞給元件。
這是產品元件的程式碼。
src / Product.jsx
/* Import statements have been left out for code brevity */const Product = ({ match, data }) => { var product = data.find(p => p.id == match.params.productId); var 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> );};
該find方法用於在陣列中搜索ID屬性等於的物件match.params.productId。如果產品存在,productData則顯示。如果不存在,則顯示“產品不存在”訊息。
保護路線
對於最後的演示,我們將討論與保護路線有關的技術。因此,如果有人嘗試訪問/admin,則需要他們先登入。但是,在保護路線之前,我們需要涵蓋一些內容。
重新導向
像伺服器端重定向一樣,<Redirect>將歷史記錄堆疊中的當前位置替換為新位置。新位置由to道具指定。這是我們將如何使用<Redirect>:
<Redirect to={{pathname: '/login', state: {from: props.location}}}
因此,如果有人嘗試/admin在登出時訪問,他們將被重定向到該/login路由。有關當前位置的資訊是通過狀態傳遞的,因此,如果身份驗證成功,則可以將使用者重定向回原始位置。在子元件內部,您可以在訪問此資訊this.props.location.state。
自定義路線
定製路線是巢狀在元件內部的路線的俗稱。如果我們需要決定是否應繪製路線,則編寫自定義路線是可行的方法。這是在其他路線中宣告的自定義路線。
src / App.js
/* Add the PrivateRoute component to the existing Routes */<nav className="navbar navbar-light"> <ul className="nav navbar-nav"> ... <li><Link to="/admin">Admin area</Link></li> </ul></nav><Switch> <Route exact path="/" component={Home} data={data} /> <Route path="/category" component={Category} /> <Route path="/login" component={Login} /> <PrivateRoute path="/admin" component={Admin} /> <Route path="/products" component={Products} /></Switch>
fakeAuth.isAuthenticated 如果使用者已登入,則返回true,否則返回false。
這是PrivateRoute的定義:
src / App.js
/* PrivateRoute component definition */const PrivateRoute = ({ component: Component, ...rest }) => { return ( <Route {...rest} render={props => fakeAuth.isAuthenticated === true ? ( <Component {...props} /> ) : ( <Redirect to={{ pathname: "/login", state: { from: props.location } }} /> ) } /> );};
如果使用者已登入,則該路由將呈現Admin元件。否則,會將使用者重定向到/login。這種方法的好處是,它顯然更具生命性並且PrivateRoute可以重用。
最後,這是Login元件的程式碼:
src / Login.jsx
import React, { useState } from "react";import { Redirect } from "react-router-dom";export default function Login(props) { const { from } = props.location.state || { from: { pathname: "/" } }; console.log(from); 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 = { isAuthenticated: false, authenticate(cb) { this.isAuthenticated = true; setTimeout(cb, 100); }};
下面的程式碼行演示了物件分解,它是ES6規範的一部分:
const { from } = this.props.location.state || { from: { pathname: "/" } };
讓我們把拼圖拼在一起吧?這是我們使用React路由器構建的應用程式的最終演示。
演示4:保護路線摘要
如您在本文中所見,React Router是一個功能強大的庫,可補充React來構建更好的宣告式路由。與第5版中的React Router的早期版本不同,所有內容都只是“元件”。而且,新的設計模式非常適合React的做事方式。
在本教程中,我們了解到:
如何設定和安裝React Router路由的基礎知識,如一些必要的元件<Router>,<Route>並且<Link>如何為導航和巢狀路線建立最小的路由器如何使用路徑引數構建動態路由
最後,我們學習了一些先進的路由技術,可以為受保護的路由建立最終的演示。