React Router V4使用指南(精講)
一、前端路由和后端路由
1)后端路由
多頁(yè)應(yīng)用中,一個(gè)URL對(duì)應(yīng)一個(gè)HTML頁(yè)面,一個(gè)Web應(yīng)用包含很多HTML頁(yè)面,在多頁(yè)應(yīng)用中,頁(yè)面路由控制由服務(wù)器端負(fù)責(zé),這種路由方式稱為后端路由。
多頁(yè)應(yīng)用中,每次頁(yè)面切換都需要向服務(wù)器發(fā)送一次請(qǐng)求,頁(yè)面使用到的靜態(tài)資源也需要重新加載,存在一定的浪費(fèi)。而且,頁(yè)面的整體刷新對(duì)用戶體驗(yàn)也有影響,因?yàn)椴煌?yè)面間往往存在共同的部分,例如導(dǎo)航欄、側(cè)邊欄等,頁(yè)面整體刷新也會(huì)導(dǎo)致共用部分的刷新。
2)前端路由
在單面應(yīng)用中,URL發(fā)生并不會(huì)向服務(wù)器發(fā)送新的請(qǐng)求,所以“邏輯頁(yè)面”的路由只能由前端負(fù)責(zé),這種路由方式稱為前端路由。
目前,國(guó)內(nèi)的搜索引擎大多對(duì)單頁(yè)應(yīng)用的SEO支持的不好,因此,對(duì)于 SEO 非??粗氐?Web
應(yīng)用(例如,企業(yè)官方網(wǎng)站,電商網(wǎng)站等),一般還是會(huì)選擇采用多頁(yè)面應(yīng)用。React 也并非只能用于開(kāi)發(fā)單頁(yè)面應(yīng)用。
二、React Router 安裝
這里使用的 React Router 的大版本號(hào)是 v4, 這也是目前最新版本。
React Router 包含3個(gè)庫(kù), react-router、react-router-dom、和 react-router-native。react-router 提供最基本的路由功能,實(shí)際使用,我們不會(huì)直接安裝 react-router,而是根據(jù)應(yīng)用運(yùn)行的環(huán)境選擇安裝 react-router-dom(在瀏覽器中使用)或 react-router-native(在 react-native中使用)。react-router-dom 和 react-router-native 都依賴 react-router,所以在安裝時(shí), react-router 也會(huì)自動(dòng)安裝。
創(chuàng)建 Web應(yīng)用,使用
npm install react-router-dom
創(chuàng)建 navtive 應(yīng)用,使用
npm install react-router-native
三、路由器
React Router 通過(guò) Router 和 Route 兩個(gè)組件完成路由功能。Router 可以理解成路由器,一個(gè)應(yīng)用中需要一個(gè) Router 實(shí)例,所有跌幅配置組件 Route 都定義為 Router 的子組件。在 Web應(yīng)用中,我們一般會(huì)使用對(duì) Router 進(jìn)行包裝的 BrowserRouter 或 HashRouter 兩個(gè)組件 BrowserRouter使用 HTML5 的 history API(pushState、replaceState等)實(shí)現(xiàn)應(yīng)用的 UI 和 URL 的同步。HashRouter 使用 URL 的 hash 實(shí)現(xiàn)應(yīng)用的 UI 和 URL 同步。
BrowserRouter 創(chuàng)建的 URL 形式如下:http://example.com/some/path
HashRouter 創(chuàng)建的 URL 形式如下: http://example.com/#/some/path
使用 BrowserRouter 時(shí),一般還需要對(duì)服務(wù)器進(jìn)行配置,讓服務(wù)器能正確地處理所有可能的URL。例如,當(dāng)瀏覽器發(fā)生 http://example.com/some/path 和 http://example.com/some/path2 兩個(gè)請(qǐng)求時(shí),服務(wù)器需要能返回正確的 HTML 頁(yè)面(也就是單頁(yè)面應(yīng)用中唯一的 HTML 頁(yè)面)
HashRouter 則不存在這個(gè)問(wèn)題,因?yàn)?hash 部分的內(nèi)容會(huì)被服務(wù)器自動(dòng)忽略,真正有效的信息是 hash 前端的部分,而對(duì)于單頁(yè)應(yīng)用來(lái)說(shuō),這部分是固定的。
Router 會(huì)創(chuàng)建一個(gè) history 對(duì)象,history 用來(lái)跟蹤 URL, 當(dāng)URL 發(fā)生變化時(shí), Router,的后代組件會(huì)重新渲染。React Router 中提供的其他組件可以通過(guò) context 獲取 history 對(duì)象,這也隱含說(shuō)明了 React Router 中其他組件必須作為 Router 組件后代使用。但 Router 中只能唯一的一個(gè)子元素,例如:
// 正確 ReactDOM.render( ( <BrowserRouter> <App /> </BrowserRouter>), document.getElementById('root') ) //錯(cuò)誤,Router 中包含兩個(gè)子元素 ReactDOM.render( ( <BrowserRouter> <App1 /> <App2 /> </BrowserRouter>), document.getElementById('root') )
四、路由器
Route 是 React Router中用于配置路由信息的組件,也是 React Router 中使用頻率最高的組件。每當(dāng)有一個(gè)組件需要根據(jù) URL 決定是否渲染時(shí),就需要?jiǎng)?chuàng)建一個(gè) Route。
1) path
每個(gè) Route 都需要定義一個(gè) path 屬性,當(dāng)使用 BrowserRouter 時(shí),path 用來(lái)描述這個(gè)Router匹配的 URL 的pathname;當(dāng)使用 HashRouter時(shí),path 用來(lái)描述這個(gè) Route 匹配的 URL 的 hash。例如,使用 BrowserRouter 時(shí),<Route path=''foo' /> 會(huì)匹配一個(gè) pathname 以 foo 開(kāi)始的 URL (如: http://example.com/foo)。當(dāng) URL 匹配一個(gè) Route 時(shí),這個(gè) Route 中定義的組件就會(huì)被渲染出來(lái)。
2)match
當(dāng) URL 和 Route匹配時(shí),Route 會(huì)創(chuàng)建一個(gè) match 對(duì)象作為 props 中的一個(gè) 屬性傳遞給被渲染的組件。這個(gè)對(duì)象包含以下4個(gè)屬性。
(1)params: Route的 path 可以包含參數(shù),例如 <Route path="/foo/:id" 包含一個(gè)參數(shù) id。params就是用于從匹配的 URL 中解析出 path 中的參數(shù),例如,當(dāng) URL = 'http://example.ocm/foo/1' 時(shí),params= {id: 1}。
(2)isExact: 是一個(gè)布爾值,當(dāng) URL 完全匹時(shí),值為 true; 當(dāng) URL 部分匹配時(shí),值為 false.例如,當(dāng) path='/foo'、URL="http://example.com/foo" 時(shí),是完全匹配; 當(dāng) URL="http://example.com/foo/1" 時(shí),是部分匹配。
(3)path: Route 的 path 屬性,構(gòu)建嵌套路由時(shí)會(huì)使用到。
(4)url: URL 的匹配的方式
3)Route 渲染組件的方式
(1)component
component 的值是一個(gè)組件,當(dāng) URL 和 Route 匹配時(shí),Component屬性定義的組件就會(huì)被渲染。例如:
<Route path='/foo' component={Foo} >
當(dāng) URL = "http://example.com/foo" 時(shí),F(xiàn)oo組件會(huì)被渲染。
(2) render
render 的值是一個(gè)函數(shù),這個(gè)函數(shù)返回一個(gè) React 元素。這種方式方便地為待渲染的組件傳遞額外的屬性。例如:
<Route path='/foo' render={(props) => { <Foo {...props} data={extraProps} /> }}> </Route>
Foo 組件接收了一個(gè)額外的 data 屬性。
(3)children
children 的值也是一個(gè)函數(shù),函數(shù)返回要渲染的 React 元素。 與前兩種方式不同之處是,無(wú)論是否匹配成功, children 返回的組件都會(huì)被渲染。但是,當(dāng)匹配不成功時(shí),match 屬性為 null。例如:
<Route path='/foo' render={(props) => { <div className={props.match ? 'active': ''}> <Foo {...props} data={extraProps} /> </div> }}> </Route>
如果 Route 匹配當(dāng)前 URL,待渲染元素的根節(jié)點(diǎn) div 的 class 將設(shè)置成 active.
4)Switch 和 exact
當(dāng)URL 和多個(gè) Route 匹配時(shí),這些 Route 都會(huì)執(zhí)行渲染操作。如果只想讓第一個(gè)匹配的 Route 沉浸,那么可以把這些 Route 包到一個(gè) Switch 組件中。如果想讓 URL 和 Route 完全匹配時(shí),Route才渲染,那么可以使用 Route 的 exact 屬性。Switch 和 exact 常常聯(lián)合使用,用于應(yīng)用首頁(yè)的導(dǎo)航。例如:
<Router> <Switch> <Route exact path='/' component={Home}/> <Route exact path='/posts' component={Posts} /> <Route exact path='/:user' component={User} /> </Switch> </Router>
如果不使用 Switch,當(dāng) URL 的 pathname 為 "/posts" 時(shí),<Route path='/posts' /> 和 <Route path=':user' /> 都會(huì)被匹配,但顯然我們并不希望 <Route path=':user' /> 被匹配,實(shí)際上也沒(méi)有用戶名為 posts 的用戶。如果不使用 exact, "/" "/posts" "/user1"等幾乎所有 URL 都會(huì)匹配第一個(gè) Route,又因?yàn)镾witch 的存在,后面的兩個(gè) Route永遠(yuǎn)不會(huì)被匹配。使用 exact,保證 只有當(dāng) URL 的 pathname 為 '/'時(shí),第一個(gè)Route才會(huì)匹配。
5)嵌套路由
嵌套路由是指在Route 渲染的組件內(nèi)部定義新的 Route。例如,在上一個(gè)例子中,在 Posts 組件內(nèi)再定義兩個(gè) Route:
const Posts = ({match}) => { return ( <div> {/* 這里 match.url 等于 /posts */} <Route path={`${match.url}/:id`} component={PostDetail} /> <Route exact path={match.url} component={PostList} /> </div> ) }
五、鏈接
Link 是 React Router提供的鏈接組件,一個(gè) Link 組件定義了當(dāng)點(diǎn)擊該 Link 時(shí),頁(yè)面應(yīng)該如何路由。例如:
const Navigation = () => { <header> <nav> <ul> <li><Link to='/'>Home</Link></li> <li><Link to='/posts'>Posts</Link></li> </ul> </nav> </header> }
Link 使用 to 屬性聲明要導(dǎo)航到的URL地址。to 可以是 string 或 object 類型,當(dāng) to 為 object 類型時(shí),可以包含 pathname、search、hash、state 四個(gè)屬性,例如:
<Link to={{ pathname: '/posts', search: '?sort=name', hash:'#the-hash', state: { fromHome: true} }}> </Link>
除了使用Link外,我們還可以使用 history 對(duì)象手動(dòng)實(shí)現(xiàn)導(dǎo)航。history 中最常用的兩個(gè)方法是 push(path,[state]) 和 replace(path,[state]),push會(huì)向?yàn)g覽器記錄中新增一條記錄,replace 會(huì)用新記錄替換記錄。例如:
history.push('/posts'); history.replace('/posts');
六、路由設(shè)計(jì)
路由設(shè)計(jì)的過(guò)程可以分為兩步:
- 為每一個(gè)頁(yè)面定義有語(yǔ)義的路由名稱(path)
- 組織 Route 結(jié)構(gòu)層次
1)定義路由名稱
我們有三個(gè)頁(yè)面,按照頁(yè)面功能不難定義出如下的路由名稱:
- 登錄頁(yè): /login
- 帖子列表頁(yè): /posts
- 帖子詳情頁(yè): /posts/:id(id代表帖子的ID)
但是這些還不夠,還需要考慮打開(kāi)應(yīng)用時(shí)的默認(rèn)頁(yè)面,也就是根路徑"/"對(duì)應(yīng)的頁(yè)面。結(jié)合業(yè)務(wù)場(chǎng)景,帖子列表作為應(yīng)用的默認(rèn)頁(yè)面為合適,因此,帖子列表對(duì)應(yīng)兩個(gè)路由名稱: '/posts'和 '/'
2)組織 Route 結(jié)構(gòu)層次
React Router 4并不需要在一個(gè)地方集中聲明應(yīng)用需要的所有 Route, Route實(shí)際上也是一個(gè)普通的 React 組件,可以在任意地方使用它(前提是,Route必須是 Router 的子節(jié)點(diǎn))。當(dāng)然,這樣的靈活性也一定程度上增加了組織 Route 結(jié)構(gòu)層次的難度。
我們先考慮第一層級(jí)的路由。登錄頁(yè)和帖子列表頁(yè)(首頁(yè))應(yīng)該屬于第一層級(jí):
<Router> <Switch> <Route exact path="/" component={Home}></Route> <Route exact path="/login" component={Login}></Route> <Route exact path="/posts" component={Home}></Route> </Switch> </Router>
第一個(gè)Route 使用了 exact 屬性,保證只有當(dāng)訪問(wèn)根路徑時(shí),第一個(gè) Route 才會(huì)匹配成功。Home 是首頁(yè)對(duì)應(yīng)組件,可以通過(guò) "/posts" 和 “/” 兩個(gè)路徑訪問(wèn)首頁(yè)。注意,這里并沒(méi)有直接渲染帖子列表組件,真正渲染帖子列表組件的地方在 Home 組件內(nèi),通過(guò)第二層級(jí)的路由處理帖子列表組件和帖子詳情組件渲染,components/Home.js 的主要代碼如下:
class Home extends Component { /**省略其余代碼 */ render() { const {match, location } = this.props; const { username } = this.state; return( <div> <Header username = {username} onLogout={this.handleLogout} location = {location} > </Header> {/* 帖子列表路由配置 */} <Route path = {match.url} exact render={props => <PostList username={username} {...this.props}></PostList>} ></Route> </div> ) } }
Home的render內(nèi)定義了兩個(gè) Route,分別用于渲染帖子列表和帖子詳情。PostList 是帖子列表組件,Post是帖子詳情組件,代碼使用Router 的render屬性渲染這兩個(gè)組件,因?yàn)樗鼈冃枰邮疹~外的 username 屬性。另外,無(wú)論訪問(wèn)是帖子列表頁(yè)面還是帖子詳情頁(yè)面,都會(huì)共用相同 Header 組件。
七、代碼分片
默認(rèn)情況下,當(dāng)在項(xiàng)目根路徑下執(zhí)行 npm run build 時(shí) ,create-react-app內(nèi)部使用 webpack將 src路徑下的所有代碼打包成一個(gè) JS 文件和一個(gè) Css 文件。
當(dāng)項(xiàng)目代碼量不多時(shí),把所有代碼打包到一個(gè)文件的做法并不會(huì)有什么影響。但是,對(duì)于一個(gè)大型應(yīng)用,如果還把所有的代碼都打包到一個(gè)文件中,顯然就不合適了。
create-react-app 支持通過(guò)動(dòng)態(tài) import() 的方式實(shí)現(xiàn)代碼分片。import()接收一個(gè)模塊的路徑作為參數(shù),然后返回一個(gè) Promise 對(duì)象, Promise 對(duì)象的值就是待導(dǎo)入的模塊對(duì)象。例如
// moduleA.js const moduleA = 'Hello' export { moduleA }; // App.js import React, { Component } from 'react'; class App extends Component { handleClick = () => { // 使用import 動(dòng)態(tài)導(dǎo)入 moduleA.js import('./moduleA') .then(({moduleA}) => { // 使用moduleA }) .catch(err=> { //處理錯(cuò)誤 }) }; render() { return( <div> <button onClick={this.handleClick}>加載 moduleA</button> </div> ) } } export default App;
上面代碼會(huì)將 moduleA.js 和它所有依賴的其他模塊單獨(dú)打包到一個(gè)chunk文件中,只有當(dāng)用戶點(diǎn)擊加載按鈕,才開(kāi)始加載這個(gè) chunk 文件。
當(dāng)項(xiàng)目中使用 React Router 是,一般會(huì)根據(jù)路由信息將項(xiàng)目代碼分片,每個(gè)路由依賴的代碼單獨(dú)打包成一個(gè)chunk文件。我們創(chuàng)建一個(gè)函數(shù)統(tǒng)一處理這個(gè)邏輯:
import React, { Component } from 'react'; // importComponent 是使用 import()的函數(shù) export default function asyncComponent(importComponent) { class AsyncComponent extends Component { constructor(props) { super(props); this.state = { component: null //動(dòng)態(tài)加載的組件 } } componentDidMount() { importComponent().then((mod) => { this.setState({ // 同時(shí)兼容 ES6 和 CommonJS 的模塊 component: mod.default ? mod.default : mod; }); }) } render() { // 渲染動(dòng)態(tài)加載組件 const C = this.state.component; return C ? <C {...this.props}></C> : null } } return AsyncComponent; }
asyncComponent接收一個(gè)函數(shù)參數(shù) importComponent, importComponent 內(nèi)通過(guò)import()語(yǔ)法動(dòng)態(tài)導(dǎo)入模塊。在AsyncComponent被掛載后,importComponent就會(huì)陰調(diào)用,進(jìn)而觸發(fā)動(dòng)態(tài)導(dǎo)入模塊的動(dòng)作。
下面利用 asyncComponent 對(duì)上面的例子進(jìn)行改造,代碼如下:
import React, { Component } from 'react'; import { ReactDOM, BrowserRouter as Router, Switch, Route } from 'react-dom'; import asyncComponent from './asyncComponent' //通過(guò)asyncComponent 導(dǎo)入組件,創(chuàng)建代碼分片點(diǎn) const AsyncHome = asyncComponent(() => import("./components/Home")) const AsyncLogin = asyncComponent(() => import("./components/Login")) class App extends component { render() { return( <Router> <Switch> <Route exact path="/" component={AsyncHome}></Route> <Route exact path="/login" component={AsyncLogin}></Route> <Route exact path="/posts" component={AsyncHome}></Route> </Switch> </Router> ) } } export default App;
這樣,只有當(dāng)路由匹配時(shí),對(duì)應(yīng)的組件才會(huì)被導(dǎo)入,實(shí)現(xiàn)按需加載的效果。
這里還有一個(gè)需要注意的地方,打包后沒(méi)有單獨(dú)的CSS文件了。這是因?yàn)?CSS樣子被打包到各個(gè) chunk 文件中,當(dāng) chunk文件被加載執(zhí)行時(shí),會(huì)有動(dòng)態(tài)把 CSS 樣式插入頁(yè)面中。如果希望把 chunk 中的 css打包到一個(gè)單獨(dú)的文件中,就需要修改 webpack 使用的 ExtractTextPlugin 插件的配置,但 create-react-app 并沒(méi)有直接把 webpack 的配置文件暴露給用戶,為了修改相應(yīng)配置
,需要將 create-react-app 管理的配置文件“彈射”出來(lái),在項(xiàng)目根路徑下執(zhí)行:
npm run eject
項(xiàng)目中會(huì)多出兩個(gè)文件夾:config和 scripts,scrips中包含項(xiàng)目啟動(dòng)、編譯和測(cè)試的腳本,config 中包含項(xiàng)目使用的配置文件,webpack配置文件 就在這個(gè)路徑下,打包 webpack.config.prod.js 找到配置 ExtractTextPlugin 的地方,添加 allChunks:true 這項(xiàng)配置:
new ExtractTextPlugin({ filename: cssFilename, allChunks: true })
然后重新編譯項(xiàng)目,各個(gè)chunk 文件 使用的 CSS 樣式 又會(huì)統(tǒng)一打包到 main.css 中。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
react?實(shí)現(xiàn)表格列表拖拽排序的示例
本文主要介紹了react?實(shí)現(xiàn)表格列表拖拽排序,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02react后臺(tái)系統(tǒng)最佳實(shí)踐示例詳解
這篇文章主要為大家介紹了react后臺(tái)系統(tǒng)最佳實(shí)踐示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01詳解如何用webpack4從零開(kāi)始構(gòu)建react開(kāi)發(fā)環(huán)境
這篇文章主要介紹了詳解如何用webpack4從零開(kāi)始構(gòu)建react開(kāi)發(fā)環(huán)境,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01create-react-app全家桶router?mobx全局安裝配置
這篇文章主要為大家介紹了create-react-app全家桶router?mobx全局安裝配置,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06利用React Router4實(shí)現(xiàn)的服務(wù)端直出渲染(SSR)
這篇文章主要介紹了利用React Router4實(shí)現(xiàn)的服務(wù)端直出渲染(SSR),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01React實(shí)現(xiàn)二級(jí)聯(lián)動(dòng)效果(樓梯效果)
這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)二級(jí)聯(lián)動(dòng)效果,樓梯效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09使用useMutation和React Query發(fā)布數(shù)據(jù)demo
這篇文章主要為大家介紹了使用useMutation和React Query發(fā)布數(shù)據(jù)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12利用React高階組件實(shí)現(xiàn)一個(gè)面包屑導(dǎo)航的示例
這篇文章主要介紹了利用React高階組件實(shí)現(xiàn)一個(gè)面包屑導(dǎo)航的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08解讀useState第二個(gè)參數(shù)的"第二個(gè)參數(shù)"
這篇文章主要介紹了useState第二個(gè)參數(shù)的"第二個(gè)參數(shù)",具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03