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>http://example.com/#/about
该<BrowserRouter>
因为它使用了HTML5 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,可让您管理历史记录堆栈,导航,确认导航以及在会话之间保持状态。
每个路由器组件都创建一个历史对象,该对象跟踪当前位置(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> <Link to="/category">Category</Link> <> <li> <Link to="/products">Products</Link> <> </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.jsx
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", }, ];
我们需要为以下路径创建路由:
-
/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.id
s 创建了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><> </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<tton> </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路由器构建的应用程序的最终演示。
总结
如您在本文中所见,React Router是一个功能强大的库,可补充React来构建更好的声明式路由。与第5版中的React Router的早期版本不同,所有内容都只是“组件”。而且,新的设计模式非常适合React的做事方式。
在本教程中,我们了解到:
-
如何设置和安装React Router
-
路由的基础知识,如一些必要的组件
<Router>
,<Route>
并且<Link>
-
如何为导航和嵌套路线创建最小的路由器
-
如何使用路径参数构建动态路由
最后,我们学习了一些先进的路由技术,可以为受保护的路由创建最终的演示。