React-Router 聊一聊

目錄:

前端路由

react-router 中常見概念

react-router 使用實例

React-Router GitHub地址:https://github.com/ReactTraining/react-router

本文完整例子:https://codesandbox.io/embed/charming-dream-916y1

文章比較長,很大一部分是截圖和示例代碼。

一、前端路由

前端路由原理很簡單:檢測瀏覽器 URL 變化,截獲 URL 地址,然后進行URL 路由匹配。

兩種路由實現(xiàn)方式:hash-mode、html5-mode。

1.1、hash-mode

在html5 標準落地之前,都是通過監(jiān)聽?hashchange?事件,當 URL 變化時,進行頁面跳轉(zhuǎn)。

例子:https://jsbin.com/xexogiy/5/edit?html,js,console,output

<!DOCTYPE html><html><head><metacharset="utf-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><title></title><linkrel="stylesheet"href=""></head><body><buttonid="toggle">提交</button></body><script>const$el=document.getElementById('toggle')$el.addEventListener('click',(e)=>{e.preventDefault()location.hash='#'+Math.floor(Math.random()*1000000)})window.addEventListener('hashchange',(e)=>{const{oldURL,newURL}=econsole.log(oldURL,newURL)})</script></html>

需要注意的是,通過hash方式的,路由地址會有?#:

http://test.com/#/a

http://test.com/#/b

1.2、html5-mode

html5標準中,為 history 添加了pushState()、replaceState()方法,以及?onpopstate?事件。但原理和 hash 方式是相同的。

https://developer.mozilla.org/zh-CN/docs/Web/API/History_APIdeveloper.mozilla.org/zh-CN/docs/Web/API/History_API

window.onpopstatedeveloper.mozilla.org/zh-CN/docs/Web/API/Window/onpopstate

通過 html5-mode 實現(xiàn)單頁路由的 URL 沒有?#。但因為沒有?#?,所以當用戶刷新頁面之類的操作時,瀏覽器還是會給服務器發(fā)送請求。

為了避免出現(xiàn)這種情況,所以這個實現(xiàn)需要服務器的支持,需要把所有路由都重定向到根頁面。

通過?html5-mode?方式的,路由地址沒有?#:

http://test.com/a

http://test.com/b

二、react-router 常見概念

主流的前端框架也都有自己的路由,比如 Backbone、Ember、Angular、React 等等,React-Router 是 React 生態(tài)的基礎路由庫,它通過管理 URL,實現(xiàn)組件的切換和狀態(tài)的變化。

React-Router?v5.0.1是目前最新版本。

React Router: Declarative Routing for Reactreacttraining.com/react-router/web/guides/quick-start

ReactTraining/react-routergithub.com/ReactTraining/react-router/releases

我們來看看各版本的差異

2.1、v4 vs pre-v4

v4 是目前大多數(shù)項目中使用的穩(wěn)定版本,不同于 pre-v4,v4屬于動態(tài)路由,而pre-v4 屬于靜態(tài)路由。

在 v4 中 React-Router 倉庫被拆分成了多個包進行發(fā)布:react-router、react-router-dom、react-router-native、react-router-config。

react-router:路由基礎庫

react-router-dom:適用于瀏覽器環(huán)境的再次封裝

react-router-native:適用于react-native環(huán)境的再次封裝

react-router-config:靜態(tài)路由配置助手

2.2、v5 vs v4

原本只是計劃發(fā)布 React Router 4.4 版本,但由于錯誤地使用了托字符 (^) —— 將依賴錯誤地寫成 "react-router": "^4.3.1",導致報錯。最后團隊決定撤銷 4.4 版本,直接改為發(fā)布 React Router v5。

完全兼容老版本v4

支持 React 16 , 兼容 React >= 15

消除在 React.StrictMode?嚴格模式中的警告

使用?create-react-context?實現(xiàn) context API

2.3、靜態(tài)路由

所謂“靜態(tài)路由”,就是說路由規(guī)則是固定的。只要應用程序一啟動,映射關(guān)系就固定不變。無論 express、Angular 還是 Rails 等業(yè)界響當當?shù)目蚣?,都用的是靜態(tài)路由。以 express 為例,路由規(guī)則差不多是這么寫的:

app.get('/',Home);app.get('/product/:id',Product);app.get('/about',About);

特點:

路由集中在一個地方。

布局和頁面嵌套是通過<Route>組件的嵌套而來的。

我們可以:

初始化路由的時候,加點業(yè)務邏輯去動態(tài)生成路由配置

通過路由參數(shù)(query)可以動態(tài)控制頁面的輸出

react-router v3 靜態(tài)路由

import{Router,Route,IndexRoute}from'react-router'constPrimaryLayout=props=>(<divclassName="primary-layout"><header>OurReactRouter3App</header><main>{props.children}</main></div>)constHomePage=()=><div>HomePage</div>constUsersPage=()=><div>UsersPage</div>constApp=()=>(<Routerhistory={browserHistory}><Routepath="/"component={PrimaryLayout}><IndexRoutecomponent={HomePage}/><Routepath="/users"component={UsersPage}/></Route></Router>)render(<App/>,document.getElementById('root'))

路由配置集中在 App 函數(shù)中,<Route> 直接嵌套于 <Router>,頁面組件融于其中作為路由的一部分。這便是整個路由的配置。

2.4、動態(tài)路由

所謂動態(tài)路由,指的是路由規(guī)則不是預先確定的,而是在渲染過程中確定的。

既然 React 組件渲染是動態(tài)發(fā)生的,那么就讓 Route 變成一個 React 組件,和其他組件一樣被渲染,在運行時完全可以決定某個Route渲染還是不渲染,也可以決定渲染這個 Route 的參數(shù) props 是怎樣,如此一來,路由規(guī)則也就完全動態(tài)了。

程墨Morgan:React Router v4 幾乎誤我一生181 贊同 · 76 評論文章

react-router v4 例子

import{BrowserRouter,Route}from'react-router-dom'constPrimaryLayout=()=>(<divclassName="primary-layout"><header>OurReactRouter4App</header><main><Routepath="/"exactcomponent={HomePage}/><Routepath="/users"component={UsersPage}/></main></div>)constHomePage=()=><div>HomePage</div>constUsersPage=()=><div>UsersPage</div>constApp=()=>(<BrowserRouter><PrimaryLayout/></BrowserRouter>)render(<App/>,document.getElementById('root'))

路由穿插在了各組件中,在嵌套路由(Nested Routes)的場景尤為明顯。

2.5、Router 組件

react-router 的工作方式,是在組件樹頂層放一個 Router 組件,然后在組件樹中散落著很多 Route 組件(注意比 Router 少一個“r”),頂層的 Router 組件負責分析監(jiān)聽 URL 的變化,在它之下的 Route 組件可以直接讀取這些信息。Router 是“提供者”,Route是“消費者”。

BrowserRouter:使用于現(xiàn)代瀏覽器,支持H5 history API

HashRouter:常用于舊款瀏覽器。根據(jù)不同的hashType

MemoryHistory:常用于非DOM環(huán)境,例如React Native或者測試,History存于內(nèi)存。

BrowserRouter 方式的路由:

https://xxx.com/ahttps://xxx.com/b

HashRouter 方式的路由:

https://xxx.com/#/ahttps://xxx.com/#/b

引入方式:

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

說明:

對于瀏覽器項目我們通常選用: <BrowserRouter 和 <HashRouter> 組件來實現(xiàn) Router

react-native 項目我們通常選用:<MemoryHistory>

2.6、Route 組件

Route只是一個具有渲染方法的普通 React 組件,路由匹配成功則渲染該組件。

三個常用屬性:

path:路由的匹配規(guī)則(可以省略)

exact:用于精確匹配路由(可以省略)

component:需要渲染的組件

所以一個完整的路由:

import{Route,BrowserRouterasRouter}from"react-router-dom";importAfrom'A'importBfrom'B'constrouting=(<Router><Routecomponent={Home}><Routeexactpath="/a"component={A}/><Routeexactpath="/b"component={B}/></Router>)ReactDOM.render(routing,document.getElementById("root"));

2.7 Link 與 NavLink 組件

對于單頁應用,需要在不同頁面之間切換,往往需要一個導航欄,我們在這里也實現(xiàn)一個簡單的導航欄。

在App.js中,我們讓網(wǎng)頁由兩個組件?Navigation?和?Content?組成,?Navigation?就是導航欄,而?Content?是具體內(nèi)容。

classAppextendsComponent{render(){return(<divclassName="App"><Navigation/><Content/></div>);}}

NavLink?組件,是基于?Link?組件,它有一個?activeClassName?屬性,目的在于如果路由匹配成功,則為當前導航添加選中樣式。

Navigation 組件:

importReactfrom'react';classNavigationextendsComponent{render(){<div><NavLinkactiveClassName="active"to="/a">A</NavLink><NavLinkactiveClassName="active"to="/b">B</NavLink></div>}}

Content組件:

importReactfrom"react";importAfrom'./A';importBfrom'./B';import404from'./404'constrouting=(<Router><Switch><Routeexactpath="/a"component={A}/><Routeexactpath="/b"component={B}/><Routecomponent={404}/></Switch></Router>);

2.8 history對象

每個 router 都會創(chuàng)建一個 history 對象,用來保持對當前位置的追蹤還有在頁面發(fā)生變化的時候重新渲染頁面。

React Router 提供的其他組件依賴在 context 上儲存的 history 對象,所以他們必須在 router 對象的內(nèi)部渲染。

一個沒有 router 祖先元素的 React Router 對象將無法正常工作。

https://medium.com/@pshrmn/a-little-bit-of-history-f245306f48ddmedium.com/@pshrmn/a-little-bit-of-history-f245306f48dd

三、React-Router 實例

React Router: Declarative Routing for Reactreacttraining.com/react-router/web/guides/quick-start

https://codeburst.io/getting-started-with-react-router-5c978f70df91codeburst.io/getting-started-with-react-router-5c978f70df91

前面提到,v4 之前是靜態(tài)路由,v4之后是動態(tài)路由。

在單頁面應用程序中,只有一個html頁面,我們重復使用相同的 html 頁面,根據(jù)導航渲染不同的組件。但是在多頁面應用程序中,當您導航時,您將從服務器獲得一個全新的頁面。

3.1、安裝

為了方便我們使用?create-react-app?來創(chuàng)建應用程序

npxcreate-react-approutingcdrouting

安裝 package

npm install react-router-dom

npm start? ? // dev環(huán)境 運行

創(chuàng)建如下目錄結(jié)構(gòu):

public/index.html

<!DOCTYPE html><htmllang="en"><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, initial-scale=1, shrink-to-fit=no"><metaname="theme-color"content="#000000"><linkrel="manifest"href="%PUBLIC_URL%/manifest.json"><linkrel="shortcut icon"href="%PUBLIC_URL%/favicon.ico"><title>React App</title></head><body><divid="root"></div></body></html>

src/index.js

importReactfrom"react";importReactDOMfrom"react-dom";import"./index.css";importAppfrom"./App";importUsersfrom"./Users";importContactfrom"./Contact";ReactDOM.render(<App/>,document.getElementById("root"));

src/App.js

importReactfrom'react'classAppextendsReact.Component{render(){return(<div><h1>Home</h1></div>)}}exportdefaultApp

src/Users.js

importReactfrom"react";classUsersextendsReact.Component{render(){return<h1>Users</h1>;}}exportdefaultUsers;

src/Contact.js

importReactfrom"react";classContactextendsReact.Component{render(){return<h1>Contact</h1>;}}exportdefaultContact;

此時我們有三個 組件:APP、Users、Contact

3.2、Route 組件:路由控制

打開入口文件 index.js, 并導入 三個組件(App、Users、Concat),然后導入?react-router-dom,react-router?給了我們?nèi)齻€組件(Route、Link、BrowserRouter),幫助我們實現(xiàn)路由。

src/index.js

importReactfrom'react'importReactDOMfrom'react-dom'import'./index.css'import{Route,Link,BrowserRouterasRouter}from'react-router-dom'importAppfrom'./App'importUsersfrom'./Users'importContactfrom'./Contact'constrouting=(<Router><div><Routepath="/"component={App}/><Routepath="/users"component={Users}/><Routepath="/contact"component={Contact}/></div></Router>)ReactDOM.render(routing,document.getElementById('root'))

在右側(cè)是瀏覽器地址欄訪問/urser/?運行效果:

但是,此時你會發(fā)現(xiàn)一個問題,/?與?/users?所對應的兩個組件 Home、Users 都被渲染。這是因為?/users?既能夠被?/匹配 也能夠被?/users?匹配。我們需要使用?exact?屬性進行精確匹配。

src/index.js

importReactfrom"react";importReactDOMfrom"react-dom";import"./index.css";import{Route,Link,BrowserRouterasRouter}from"react-router-dom";importAppfrom"./App";importUsersfrom"./Users";importContactfrom"./Contact";constrouting=(<Router><div><Routeexactpath="/"component={App}/><Routepath="/users"component={Users}/><Routepath="/contact"component={Contact}/></div></Router>);ReactDOM.render(routing,document.getElementById("root"));

再次運行,此時只會渲染 Users 組件。

3.3、Link 組件:實現(xiàn)導航

src/index.js

importReactfrom'react'importReactDOMfrom'react-dom'import'./index.css'import{Route,Link,BrowserRouterasRouter}from'react-router-dom'importAppfrom'./App'importUsersfrom'./Users'importContactfrom'./Contact'constrouting=(<Router><div><ul><li><Linkto="/">Home</Link></li><li><Linkto="/users">Users</Link></li><li><Linkto="/contact">Contact</Link></li></ul><Routeexactpath="/"component={App}/><Routepath="/users"component={Users}/><Routepath="/contact"component={Contact}/></div></Router>)ReactDOM.render(routing,document.getElementById('root'))

在頁面上能夠看到導航欄的渲染結(jié)果,點擊導航會渲染對應的 組件

3.4、Switch組件:實現(xiàn)404頁面

404 代表頁面不存在(not found),也就是導航路徑不存在的時候,我們需要讓用戶感知到提示信息,此時就需要添加404頁面。

react-router 提供了一個Switch?組件,用于支持當路由匹配成功,渲染對應組件。若未能匹配路由,則渲染404組件。

創(chuàng)建 404 頁面?Notfound.js

importReactfrom'react'constNotfound=()=><h1>Notfound</h1>exportdefaultNotfound

index.js

importReactfrom"react";importReactDOMfrom"react-dom";import"./index.css";import{Route,Link,BrowserRouterasRouter,Switch}from"react-router-dom";importAppfrom"./App";importUsersfrom"./Users";importContactfrom"./Contact";importNotfoundfrom"./Notfound";constrouting=(<Router><div><ul><li><Linkto="/">Home</Link></li><li><Linkto="/users">Users</Link></li><li><Linkto="/contact">Contact</Link></li></ul><Switch><Routeexactpath="/"component={App}/><Routepath="/users"component={Users}/><Routepath="/contact"component={Contact}/><Routecomponent={Notfound}/></Switch></div></Router>);ReactDOM.render(routing,document.getElementById("root"));

此時我們訪問一個不存在的路由地址:/notfound

3.5、Path 屬性的 URL 參數(shù)

url params 能夠讓我們實現(xiàn)動態(tài)渲染同一組件,比如:/users/1、/users/2、/users/3?分別對應3個不同的用戶。語法如下:

<Route path="users/:id" component={Users} />

index.js

importReactfrom"react";importReactDOMfrom"react-dom";import"./index.css";import{Route,Link,BrowserRouterasRouter,Switch}from"react-router-dom";importAppfrom"./App";importUsersfrom"./Users";importContactfrom"./Contact";importNotfoundfrom"./Notfound";constrouting=(<Router><div><ul><li><Linkto="/">Home</Link></li><li><Linkto="/users">Users</Link></li><li><Linkto="/contact">Contact</Link></li></ul><Switch><Routeexactpath="/"component={App}/><Routepath="/users/:id"component={Users}/><Routepath="/contact"component={Contact}/><Routecomponent={Notfound}/></Switch></div></Router>);ReactDOM.render(routing,document.getElementById("root"));

在 Users.js 中?console.log,打印傳遞的 URL 參數(shù)

Users.js

import React from 'react'

class Users extends React.Component {

? render() {

? ? console.log(this.props)

? ? return <h1>Users</h1>

? }

}

export default Users

再次訪問:/users/1?你會得到如下結(jié)果:

我們將?console.log?的信息渲染到頁面上,對 Users.js 進行簡單改造:

Users.js

importReactfrom"react";classUsersextendsReact.Component{render(){const{params}=this.props.match;return(<div><h1>Users</h1><p>{params.id}</p></div>);}}exportdefaultUsers;

3.6、嵌套路由

嵌套路由,能夠幫助我們實現(xiàn)子路由渲染。比如:/users/1,/users/2。

importReactfrom"react";importReactDOMfrom"react-dom";import"./index.css";import{Route,Link,BrowserRouterasRouter,Switch}from"react-router-dom";importAppfrom"./App";importUsersfrom"./Users";importContactfrom"./Contact";importNotfoundfrom"./Notfound";constrouting=(<Router><div><ul><li><Linkto="/">Home</Link></li><li><Linkto="/users">Users</Link></li><li><Linkto="/contact">Contact</Link></li></ul><hr/><Switch><Routeexactpath="/"component={App}/><Routepath="/users"component={Users}/><Routepath="/contact"component={Contact}/><Routecomponent={Notfound}/></Switch> </div></Router>);ReactDOM.render(routing,document.getElementById("root"));

在 Users.js 文件中,需要導入?react-router-dom?package 中的?Route、Link組件,用于在Users組件中實現(xiàn)子路由。

Users.js

importReactfrom"react";import{Route,Link}from"react-router-dom";constUser=({match})=><p>{match.params.id}</p>;classUsersextendsReact.Component{render(){const{url}=this.props.match;return(<div><h1>Users</h1><strong>selectauser</strong><ul><li><Linkto="/users/1">User1</Link></li><li><Linkto="/users/2">User2</Link></li><li><Linkto="/users/3">User3</Link></li></ul><Routepath="/users/:id"component={User}/></div>);}}exportdefaultUsers;

運行效果:

3.7、NavLink組件: 實現(xiàn)選中樣式

NavLink它用于根據(jù)不同路由設置不同的樣式,以便用戶知道在網(wǎng)站上瀏覽的頁面。

NavLink 與 Link 的區(qū)別:

Link 用于導航站點上的不同路由。

NavLink是一種特殊的Link,用于將樣式屬性添加到當前路由。

在我們的路由應用程序中,我們有三個路徑(home,/ users,/ contact)讓我們使用NavLink設置它們的樣式。

activeClassName 屬性

我們需要向NavLink組件添加一個名為activeClassName 的新屬性,以便路由匹配成功時,能夠添加選中class狀態(tài)。

index.css

.active{color:red;}

index.js

importReactfrom"react";importReactDOMfrom"react-dom";import"./index.css";import{Route,NavLink,BrowserRouterasRouter,Switch}from"react-router-dom";importAppfrom"./App";importUsersfrom"./Users";importContactfrom"./Contact";importNotfoundfrom"./Notfound";constrouting=(<Router><div><ul><li><NavLinkexactactiveClassName="active"to="/">Home</NavLink></li><li><NavLinkactiveClassName="active"to="/users">Users</NavLink></li><li><NavLinkactiveClassName="active"to="/contact">Contact</NavLink></li></ul><hr/><Switch><Routeexactpath="/"component={App}/><Routepath="/users"component={Users}/><Routepath="/contact"component={Contact}/><Routecomponent={Notfound}/></Switch></div></Router>);ReactDOM.render(routing,document.getElementById("root"));

運行后,可以看到?red 樣式?應用于當前路由:

3.8、history對象: 實現(xiàn)程序化導航

什么是程序化導航?

我們需要在當前路由狀態(tài)下發(fā)生路由改變時,重定向用戶到新的路由。例如,當用戶成功登錄時,用戶被重定向到個人主頁。

如何在?react-router?中以編程方式導航?

要以編程方式導航,我們需要獲取由 react 路由器傳遞的歷史對象的幫助。讓我們在?Contact?組件中添加一個聯(lián)系表單。

history 對象中有一個 push 方法,使用push方法,只要用戶提交表單,我們就會將用戶重定向到 Home page。

concat.js

importReactfrom"react";classContactextendsReact.Component{onSubmit=()=>{this.props.history.push("/");};render(){return(<form><inputplaceholder="name"type="name"/><inputplaceholder="email"type="email"/><buttononClick={this.onSubmit}>Submit</button></form>);}}exportdefaultContact;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容