背景日常絮叨
看到這個文章 大部分也是react 有基礎(chǔ)或者剛?cè)腴T的小伙伴,react 目前比較流行 歡欣的一個庫欢揖,下面讓我們一步一步run 起來
路由跳轉(zhuǎn)是指在同步保持瀏覽器URL的過程中渲染頁面中的視圖花颗。React Router 讓你聲明式的操作路由跳轉(zhuǎn)白魂。聲明式路由方法允許你控制應(yīng)用中的數(shù)據(jù)流.
創(chuàng)建新的react 項目
- npm install -g create-react-app
- create-react-app myapp //myapp 是你的項目名稱
- cd myapp
但是打開項目會發(fā)現(xiàn)西土,一些與webpack相關(guān)的東西被隱藏掉了魄揉,只需要做單獨的配置鍵入下面的命令
- npm run eject
在運行之前 可以安裝一個react-router
- npm install --save react-router-dom
- npm install 安裝依賴
- npm run start
沒有意外的情況下,都可以運行起來金赦,提醒下 如果 你修改 項目之后 在執(zhí)行npm run eject 可能會有報錯的情況音瓷,如果需要還是提早執(zhí)行。
為了方便管理復(fù)用夹抗,我一般都會拆分組件绳慎,src 里面 創(chuàng)建 文件page route component ,如下圖:
react-router
- 上面的都準(zhǔn)備好之后,我們就開始路由的配置杏愤,react-router靡砌,一般我們用到路由分為:
- 路由的基本的跳轉(zhuǎn)
- 嵌套路由
- 帶路徑參數(shù)的嵌套路由
路由組件:BrowserRouter 和HashRouter
路徑匹配的組件: Route 和 Switch
導(dǎo)航組件: Link
- 一般基礎(chǔ)的路由
export default (
<Router history={historyConfig}>
<div>
<ul className="nav">
<li><Link to="/">App</Link></li>
<li><Link to="/About">About</Link></li>
<li><Link to="/User">User</Link></li>
<li><Link to="/Detail">Detail</Link></li>
</ul>
<hr />
<Route exact path="/" component={Home} />
<Route path="/About" component={About} />
<Route path="/User" component={User} />
<Route path="/Detail" component={DeatilComponent} />
</div>
</Router>
);
Router組件決定了我們使用html5 history api,
Route組件定義了路由的匹配規(guī)則和渲染內(nèi)容珊楼,
使用 Link 組件進行路由之間的導(dǎo)航通殃。
使用 exact 屬性來定義路徑是不是精確匹配。
Router
我們都知道 React Router API 中有兩種類型的路由
<BrowserRouter> http://localhost:300/home 比較常用
<HashRouter> 哈希路由 http://localhost:300/#/home
- 如果有服務(wù)器端的動態(tài)支持厕宗,建議使用 BrowserRouter画舌,否則建議使用 HashRouter。
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
將 BrowserRouter 修改為 HashRouter 就可以了媳瞪,基本不需要修改其他東西骗炉。
Route常用屬性
主要職責(zé)是當(dāng)Route的位置和路徑匹配的時候渲染對應(yīng)的ui
exact、path以及component屬性
Route會向component組件傳一個參數(shù)蛇受,包含屬性match句葵,location,history兢仰。match屬性對象又包含url乍丈,path,params等屬性把将。比較常用的就是match的url屬性轻专,可以繼續(xù)基于url指定組件里面的Link標(biāo)簽要鏈接到的url,從而顯示對應(yīng)的組件察蹲。
Route寫在哪里请垛,當(dāng)Route匹配到URL的時候,相應(yīng)的組件就會在那里進行渲染洽议。component宗收,render,children亚兄,Route的這三個屬性寫一個就行混稽,不能同時都寫。precendence order: component > render > children.
注意:children中的元素不管是否匹配到URL都會渲染审胚,不過沒有匹配到的Route向children的函數(shù)中傳的值是null匈勋,只有匹配到的時候才會有值。
- 橫向?qū)Ш綑冢菏褂肦outed 的 children屬性膳叨。
側(cè)邊菜單欄:左邊是Link洽洁,右邊把Route寫在右邊展示區(qū)域的div里面,匹配到的時候進行渲染菲嘴。
<Route>是如何渲染的诡挂?
當(dāng)路由 match 成功之后碎浇,route 根據(jù)
1、component : 一個React組件璃俗。當(dāng)帶有component參數(shù)的route匹配成功后,route會返回一個新的元素悉默,其為component參數(shù)所對應(yīng)的React組件(使用React.createElement創(chuàng)建)城豁。
2、render : 一個返回React element的函數(shù)[注5]抄课。當(dāng)匹配成功后調(diào)用該函數(shù)唱星。該過程與傳入component參數(shù)類似,并且對于行級渲染與需要向元素傳入額外參數(shù)的操作會更有用跟磨。
3间聊、children : 一個返回React element的函數(shù)。與上述兩個參數(shù)不同抵拘,無論route是否匹配當(dāng)前l(fā)ocation哎榴,其都會被渲染。
- Route向component組件進行參數(shù)傳遞
組件:
const Topics = ( {match} ) => ()
路由:
<Route path="/topics" component={Topics}/>
路由Route向組件傳的參數(shù):
{history:{},
location:{},
match:{}}.
執(zhí)行到 const Topics = ( {match} ) => ()的時候會將參數(shù)對象
賦值給對象{match}僵蛛,所以此時組件實際接受的參數(shù)只有match對象尚蝌。
- history 插件
$ npm install --save history
createBrowserHistory
import createBrowserHistory from 'history/createBrowserHistory';
const historyConfig = createBrowserHistory({
basename: '/' + AREA_ENV
});
history有三種使用方式:
- createBrowserHistory 現(xiàn)代瀏覽器使用 目前用的最多的
createBrowserHistory({
basename: '', // 基鏈接
forceRefresh: false, // 是否強制刷新整個頁面
keyLength: 6, // location.key的長度
getUserConfirmation: (message,callback) => callback(window.confirm(message)) // 跳轉(zhuǎn)攔截函數(shù) })
- createMemoryHistory 手機端使用
createMemoryHistory({
initialEntries: ['/'], // 初始載入路徑,和MemoryRouter中的initialEntries是一樣的 initialIndex: 0, // initialEntries初始載入索引
keyLength: 6, // location.key的長度
getUserConfirmation: null // 路由跳轉(zhuǎn)攔截函數(shù) })
- createHashHistory 老版本瀏覽器使用充尉,暫不介紹
Switch :
渲染與該地址匹配的第一個子節(jié)點 <Route> 或者 <Redirect>飘言。外面包一層Switch 和不用Switch 有什么不同呢?
- 用Switch 包含 只渲染一個路由驼侠,如果不用Switch則全部的路由都要渲染出來
下面的代碼 所有的路由 都會渲染出來姿鸿,那么如果有些需求比如 側(cè)欄,面包屑那么我們只能選擇一個 路由渲染出來
<Route exact path="/" component={Home} />
<Route path="/Detail" component={PrivateRoute} />
<Route path="/About" component={About} />
<Route path="/User" component={User} />
所以使用 Switch 實現(xiàn)
<Switch>
<Route exact path="/" component={Home} />
<Route path="/Detail" component={PrivateRoute} />
<Route path="/About" component={About} />
<Route path="/User" component={User} />
</Switch>
Link:
導(dǎo)航到指定的路由
react-router 的基本原理:
實現(xiàn)URl 和UI界面的同步倒源,其中在react-router中苛预,URL對應(yīng)location 對象,而UI對應(yīng)的是react components 來決定相速,這樣就是 location 和 components 的同步的問題碟渺。
- react-router 主要依賴于history
// 內(nèi)部的抽象實現(xiàn)
function createHistory(options={}) {
...
return {
listenBefore, // 內(nèi)部的hook機制,可以在location發(fā)生變化前執(zhí)行某些行為突诬,AOP的實現(xiàn)
listen, // location發(fā)生改變時觸發(fā)回調(diào)
transitionTo, // 執(zhí)行l(wèi)ocation的改變
push, // 改變location
replace,
go,
goBack,
goForward,
createKey, // 創(chuàng)建location的key苫拍,用于唯一標(biāo)示該location,是隨機生成的
createPath,
createHref,
createLocation, // 創(chuàng)建location
}
}
- 1旺隙、當(dāng)頁面路由發(fā)生變化的時候绒极;history 底層支持pushState, 將參數(shù)傳輸?shù)?createLocation 方法中,返回 location 的結(jié)構(gòu)如下:
location = {
pathname, // 當(dāng)前路徑蔬捷,即 Link 中的 to 屬性
search, // search
hash, // hash
state, // state 對象
action, // location 類型垄提,在點擊 Link 時為 PUSH榔袋,瀏覽器前進后退時為 POP,調(diào)用 replaceState 方法時為 REPLACE
key, // 用于操作 sessionStorage 存取 state 對象
};
- 2铡俐、將location對象作為參數(shù)傳入到 TransitionTo方法中凰兑,執(zhí)行l(wèi)oaction 的變化,
然后調(diào)用 window.location.hash 或者window.history.pushState() 修改了應(yīng)用的 URL
, 同時觸發(fā)了 history.listen 的監(jiān)聽审丘。 - 3吏够、更新location之后,然后系統(tǒng)調(diào)用 matchRoutes 方法配置 出與當(dāng)前l(fā)ocation對象匹配的一個子集滩报。
- 4锅知、 匹配成功之后開始渲染 -> 渲染組件 更新UI(內(nèi)部具體經(jīng)過要后續(xù)研究)
檢測url 前進:
- createBrowserHistory: pushState、replaceState
- createHashHistory: location.hash=*** location.replace()
- createMemoryHistory: 在內(nèi)存中進行歷史記錄的存儲
hashChange 監(jiān)聽window.location.hash 的變化脓钾,hash 發(fā)生變化售睹,瀏覽器更新url,同時history 棧中會產(chǎn)生一條新的記錄可训。
在 react-router 內(nèi)部注冊了 window.addEventListener('hashchange', listener, false) 事件監(jiān)聽器, listener 內(nèi)部可以通過 hash fragment 獲取到當(dāng)前 URL 對應(yīng)的 location 對象
pushState: window.history.pushState(state, title, path)方法 昌妹,改變?yōu)g覽器的url,通過location.state 來獲取到 state沉噩,
在 react-router內(nèi)部將該對象存儲到了 sessionStorage 中
檢測url 回退:
- createBrowserHistory: popstate
- createHashHistory: hashchange
- createMemoryHistory: 因為是在內(nèi)存中操作捺宗,跟瀏覽器沒有關(guān)系,不涉及UI層面的事情川蒙,所以可以直接進行歷史信息的回退
路由匹配原理:
路由有三個屬性決定是否匹配一個URL蚜厉;
- 嵌套關(guān)系
- 路徑語法
- 優(yōu)先級
1、嵌套關(guān)系
當(dāng)一個給定的 URL 被調(diào)用時畜眨,整個集合中(命中的部分)都會被渲染昼牛。嵌套路由被描述成一種樹形結(jié)構(gòu)。React Router 會深度優(yōu)先遍歷整個路由配置來尋找一個與給定的 URL 相匹配的路由康聂。
2贰健、路徑語法
-
paramName
– 匹配一段位于/
、?
或#
之后的 URL恬汁。 命中的部分將被作為一個參數(shù) - () – 在它內(nèi)部的內(nèi)容被認(rèn)為是可選的
- ** – 匹配任意字符(非貪婪的)直到命中下一個字符或者整個 URL 的末尾伶椿,并創(chuàng)建一個 splat 參數(shù)
<Route path="/hello/:name"> // 匹配 /hello/michael 和 /hello/ryan
<Route path="/hello(/:name)"> // 匹配 /hello, /hello/michael 和 /hello/ryan
<Route path="/files/*.*"> // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg
3、優(yōu)先級
路由是自頂向下匹配路由氓侧,確保前一個路由不會匹配后一個路由的路徑
BrowserRouter 和HashRouter 區(qū)別
BrowserRouter:
vue:mode:history
react: <BrowserRouter>
pushState脊另, replaceState會改變當(dāng)前路徑,但是他不會導(dǎo)致單頁面的重新渲染约巷,我們所使用時偎痛,頁面的渲染是由react或vue中的Router中監(jiān)聽了路由的變化
// 監(jiān)聽路由變化
this.unlisten = props.history.listen(location => {
if (this._isMounted) {
this.setState({ location });
} else {
this._pendingLocation = location;
}
});
// 以下就是Route在當(dāng)路由發(fā)生變化時做的渲染
{props.match
? children
? typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: children
: component
? React.createElement(component, props)
: render
? render(props)
: null
: typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: null}
當(dāng)刷新頁面時,瀏覽器會向服務(wù)器請求example.com/list独郎,服務(wù)器實際會去找根目錄下list.html這個文件踩麦,發(fā)現(xiàn)找不到枚赡,因為實際上我們的服務(wù)器并沒有這樣的 物理路徑/文件 或沒有配置處理這個路由,所有內(nèi)容都是通過React-Router去渲染React組件谓谦,自然會報404錯誤贫橙。這種情況我們可以通過配置Nginx或通過自建Node服務(wù)器來解決。
hashHistory:
vue:mode:hash
react: <HashRouter>
- hashHistory 使用 URL 中的 hash(#)部分去創(chuàng)建路由反粥,舉例來說料皇,用戶訪問http://www.example.com/,實際會看到的是http://www.example.com/#/星压。
從BrowserRouter.js和HashRouter.js文件中可以看到,history對象是由history插件生成的
// BrowserRouter.js
import { createBrowserHistory as createHistory } from "history";
history = createHistory(this.props);
// 用于createHistory傳入的配置對象參數(shù)鬼譬,也說明了這個配置是有父級傳遞的娜膘,而不是BrowserRouter自身的
BrowserRouter.propTypes = {
basename: PropTypes.string,
children: PropTypes.node,
forceRefresh: PropTypes.bool,
getUserConfirmation: PropTypes.func,
keyLength: PropTypes.number
};
// HashRouter.js
import { createHashHistory as createHistory } from "history";
history = createHistory(this.props);
// 用于createHistory傳入的配置對象參數(shù)
HashRouter.propTypes = {
basename: PropTypes.string,
children: PropTypes.node,
getUserConfirmation: PropTypes.func,
hashType: PropTypes.oneOf(["hashbang", "noslash", "slash"])
};
createMemoryHistory:
- Memory history 不會在地址欄被操作或讀取。這就解釋了我們是如何實現(xiàn)服務(wù)器渲染的优质。同時它也非常適合測試和其他的渲染環(huán)境(像 React Native )竣贪。和另外兩種history的一點不同是你必須創(chuàng)建它,這種方式便于測試巩螃。
React-router 按需加載方式:
一: create-react-app
- 創(chuàng)建一個異步組件 AsyncComponent
import React from 'react';
export default function (getComponent) {
return class AsyncComponent extends React.Component {
static Component = null;
state = { Component: AsyncComponent.Component };
componentWillMount() {
if (!this.state.Component) {
getComponent().then(({default: Component}) => {
AsyncComponent.Component = Component
this.setState({ Component })
})
}
}
render() {
const { Component } = this.state
if (Component) {
return <Component {...this.props} />
}
return null
}
}
}
- 使用異步組件:我們將使用asyncComponent動態(tài)導(dǎo)入我們想要的組件演怎。
import asyncComponent from './asyncComponent'
const Login = asyncComponent(() => load('login/login'))
const LayoutPage = asyncComponent(() => load('layout/layout'))
const NoticeDeatil = asyncComponent(() => load('noticeDetail/noticeDetail'))
export const appRouterMap = [
{path:"/login",name:"Login",component:Login,auth:false},
{path:"/web",name:"LayoutPage",component:LayoutPage,auth:false},
{path:"/notice/:id",name:"NoticeDeatil",component:NoticeDeatil,auth:false},
]
二、借助react-loadable來實現(xiàn)按需加載
1避乏、利用react-loadable這個高級組件爷耀,要做到實現(xiàn)按需加載這一點
第三種 bundle-loader 按需加載方式
不管vue 還是react 都可以使用
hash路由:
hash原理是觸發(fā)了onhashchange 事件,
hash —— 即地址欄 URL 中的 # 符號(此 hash 不是密碼學(xué)里的散列運算)拍皮。比如這個 URL:http://www.abc.com/#/hello歹叮,hash 的值為 #/hello。
它的特點在于:hash 雖然出現(xiàn)在 URL 中铆帽,但不會被包括在 HTTP 請求中咆耿,對后端完全沒有影響,因此改變 hash 不會重新加載頁面爹橱。
window.onhashchange = function(event){
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
document.body.style.color = hash;
}
前端路由的核心萨螺,就在于 —— 改變視圖的同時不會向后端發(fā)出請求。
history路由:
history —— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法愧驱。(需要特定瀏覽器支持)這兩個方法應(yīng)用于瀏覽器的歷史記錄棧慰技,在當(dāng)前已有的 back、forward冯键、go 的基礎(chǔ)之上惹盼,它們提供了對歷史記錄進行修改的功能。
只是當(dāng)它們執(zhí)行修改時惫确,雖然改變了當(dāng)前的 URL手报,但瀏覽器不會立即向后端發(fā)送請求蚯舱。
pushState() 設(shè)置的新 URL 可以是與當(dāng)前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分掩蛤,因此只能設(shè)置與當(dāng)前 URL 同文檔的 URL枉昏;
pushState() 設(shè)置的新 URL 可以與當(dāng)前 URL 一模一樣,這樣也會把記錄添加到棧中揍鸟;而 hash 設(shè)置的新值必須與原來不一樣才會觸發(fā)動作將記錄添加到棧中兄裂;
pushState() 通過 stateObject 參數(shù)可以添加任意類型的數(shù)據(jù)到記錄中;而 hash 只可添加短字符串阳藻;
history 模式下晰奖,
前端的 URL 必須和實際向后端發(fā)起請求的 URL 一致,
如 http://www.abc.com/book/id腥泥。如果后端缺少對 /book/id 的路由處理匾南,將返回 404 錯誤。
Vue-Router 官網(wǎng)里如此描述:“不過這種模式要玩好蛔外,還需要后臺配置支持……所以呢蛆楞,你要在服務(wù)端增加一個覆蓋所有情況的候選資源:如果 URL 匹配不到任何靜態(tài)資源,則應(yīng)該返回同一個 index.html 頁面夹厌,這個頁面就是你 app 依賴的頁面豹爹。”