說到 React 我們一定離不開和 Router 打交道。不管 Vue Router 和 React Router 窑多,他們的原理都是差不多的。這篇文章會(huì)從一個(gè)簡(jiǎn)單的例子一直拓展到真正的 React Router洼滚。
什么是路由
路由(routing)是指分組從源到目的地時(shí)埂息,決定端到端路徑的網(wǎng)絡(luò)范圍的進(jìn)程
上面就是百度百科對(duì)路由的定義。比如我想去某個(gè)地方遥巴,那這個(gè)東西就帶我去那個(gè)地方耿芹,這個(gè)東西就叫路由。
一個(gè)例子
先來(lái)說說需求挪哄,假設(shè)我們有兩個(gè)組件 Login 和 Register,和兩個(gè)對(duì)應(yīng)的按鈕琉闪。點(diǎn)擊 Login 按鈕就顯示 Login 組件迹炼,點(diǎn)擊 Register 顯示 Register 組件。
我們這里使用 Hooks API 來(lái)創(chuàng)建 App 組件的自身狀態(tài)颠毙。UI
代表了當(dāng)前顯示的是哪個(gè)組件的名字斯入。
function Login() {
return <div>Register</div>;
}
function Register() {
return <div>Login</div>;
}
function App() {
let [UI, setUI] = useState('Login');
let onClickLogin = () => {
setUI('Login')
}
let onClickRegister = () => {
setUI('Register')
}
let showUI = () => {
switch(UI) {
case 'Login':
return <Login/>
case 'Register':
return <Register/>
}
}
return (
<div className="App">
<button onClick={onClickLogin}>Login</button>
<button onClick={onClickRegister}>Register</button>
<div>
{showUI()}
</div>
</div>
);
}
這個(gè)其實(shí)就是路由的雛形了,每個(gè)頁(yè)面對(duì)應(yīng)著一個(gè)組件蛀蜜,然后在不同狀態(tài)下去切換 刻两。
使用 hash 來(lái)切換
當(dāng)然我們更希望看到的是
不同 url -> 不同頁(yè)面 -> 不同組件
我們先用 url 里的 hash 做嘗試:
- 在進(jìn)入頁(yè)面的時(shí)候獲取當(dāng)前 url 的 hash 值,根據(jù)這個(gè) hash 值去更新
UI
從而通過showUI()
來(lái)切換到對(duì)應(yīng)的組件 - 同時(shí)添加 onClick 事件點(diǎn)擊不同按鈕時(shí)滴某,就在 url 設(shè)置對(duì)應(yīng)的 hash磅摹,并切換對(duì)應(yīng)的組件
這時(shí)候組件 App 可以寫成這樣:
function App() {
// 進(jìn)入頁(yè)面時(shí)滋迈,先初始化當(dāng)前 url 對(duì)應(yīng)的組件名
let hash = window.location.hash
let initUI = hash === '#login' ? 'login' : 'register'
let [UI, setUI] = useState(initUI);
let onClickLogin = () => {
setUI('Login')
window.location.hash = 'login'
}
let onClickRegister = () => {
setUI('Register')
window.location.hash = 'register'
}
let showUI = () => {
switch(UI) {
case 'Login':
return <Login/>
case 'Register':
return <Register/>
}
}
return (
<div className="App">
<button onClick={onClickLogin}>Login</button>
<button onClick={onClickRegister}>Register</button>
<div>
{showUI()}
</div>
</div>
);
}
這樣其實(shí)已經(jīng)滿足我們的要求了,如果我在地址欄里輸入 localhost:8080/#login
户誓,就會(huì)顯示 <Login/>
饼灿。但是這個(gè) “#” 符號(hào)不太好看,如果輸入 localhost:8080/login
就完美了帝美。
使用 pathname 切換
如果要做得像上面說的那樣碍彭,我們只能用 window.location.pathname
去修改 url 了。只要把上面代碼里的 hash
改成 pathname
就好了悼潭,那么組件 App
可以寫成這樣:
function App() {
// 進(jìn)入頁(yè)面時(shí)庇忌,先初始化當(dāng)前 url 對(duì)應(yīng)的組件名
let pathname = window.location.pathname
let initUI = pathname === '/login' ? 'login' : 'register'
let [UI, setUI] = useState(initUI);
let onClickLogin = () => {
setUI('Login')
window.location.pathname = 'login'
}
let onClickRegister = () => {
setUI('Register')
window.location.pathname = 'register'
}
let showUI = () => {
switch(UI) {
case 'Login':
return <Login/>
case 'Register':
return <Register/>
}
}
return (
<div className="App">
<button onClick={onClickLogin}>Login</button>
<button onClick={onClickRegister}>Register</button>
<div>
{showUI()}
</div>
</div>
);
}
但是這里有個(gè)問題,每次修改 pathname
的時(shí)候頁(yè)面會(huì)刷新舰褪,這是完全不符合我們的要求的皆疹,還不如用 hash 好。
使用 history 切換
幸運(yùn)的是 H5 提供了一個(gè)好用的 history API抵知,使用 window.history.pushState()
使得我們即可以修改 url 也可以不刷新頁(yè)面墙基,一舉兩得。
現(xiàn)在只需要修改點(diǎn)擊回調(diào)里的 window.location.pathname = 'xxx'
就可以了刷喜,用 window.history.pushState()
去代替残制。
function App() {
// 進(jìn)入頁(yè)面時(shí),先初始化當(dāng)前 url 對(duì)應(yīng)的組件名
let pathname = window.location.pathname
let initUI = pathname === '/login' ? 'login' : 'register'
let [UI, setUI] = useState(initUI);
let onClickLogin = () => {
setUI('Login')
window.history.pushState(null, '', '/login')
}
let onClickRegister = () => {
setUI('Register')
window.history.pushState(null, '', '/register')
}
let showUI = () => {
switch(UI) {
case 'Login':
return <Login/>
case 'Register':
return <Register/>
}
}
return (
<div className="App">
<button onClick={onClickLogin}>Login</button>
<button onClick={onClickRegister}>Register</button>
<div>
{showUI()}
</div>
</div>
);
}
到此掖疮,一個(gè) Router 就已經(jīng)被我們實(shí)現(xiàn)了初茶。當(dāng)然這個(gè) Router 功能不多,不過這就是 Vue Router 和 React Router 的思想浊闪,他們是基于此來(lái)開發(fā)更多的功能而已恼布。
約束
在前端使用路由要有個(gè)前提,那就是后端要將全部的路徑都指向首頁(yè)搁宾,即 index.html
折汞。否則后端會(huì)出現(xiàn) 404 錯(cuò)誤。
什么叫全部路徑都指向首頁(yè)呢盖腿?我們想一下正常的多頁(yè)網(wǎng)頁(yè)是怎么樣的:如果訪問了一個(gè)不存在的路徑爽待,如 localhost:8080/fuck.html
,那么后端會(huì)返回一個(gè) error.html
翩腐,里面內(nèi)容顯示 “找不到網(wǎng)頁(yè)”鸟款,這種情況就是后端處理網(wǎng)頁(yè)的路由了。因?yàn)檎呛蠖烁鶕?jù)不同 url 返回不同的 xxx.html
呀茂卦。
如果前端使用路由何什,那么后端將全部路徑都指向 index.html
。當(dāng)我們?cè)L問到一個(gè)不存在路徑時(shí)等龙,如 localhost:8080/fuck
处渣,后端不管三七二十一返回 index.html
伶贰。但是這個(gè) index.html
里有我們寫的 JS 代碼(React 打包后的)呀,這 JS 代碼其中就包含了我們做的路由霍比。所以我們的路由發(fā)現(xiàn)不存在這個(gè)路徑時(shí)幕袱,就切換到 Error 組件來(lái)充當(dāng) “找不到網(wǎng)頁(yè)” 的 HTML 文件。這就叫前端控制路由悠瞬。
React Router
現(xiàn)在我們使用 React Router 來(lái)重寫這個(gè)代碼们豌。看官網(wǎng)的時(shí)候一定要看這個(gè)官網(wǎng)浅妆,別看 Github 的那個(gè)望迎,因?yàn)槟莻€(gè)是 v3.0 的。
react-router 和 react-router-dom
先說說這兩個(gè)鬼的不同凌外。當(dāng)我還是先手的時(shí)候總是被這兩個(gè)東西搞蒙辩尊。
react-router
: 實(shí)現(xiàn)了路由的核心功能。
react-router-dom
: 基于react-router康辑,加入了在瀏覽器運(yùn)行環(huán)境下的一些功能摄欲。
說到底就是 react-router-dom
是 react-router
的加強(qiáng)版唄。那為毛不兩個(gè)合在一起呢疮薇?像 Vue Router 那樣多好胸墙。因?yàn)?React Native 也要路由系統(tǒng)呀。所以還有一個(gè)庫(kù)叫 react-router-native
按咒,這個(gè)庫(kù)也是基于 react-router
的迟隅,它類似 react-router-dom
,加入了 React Native 運(yùn)行環(huán)境下的一些功能励七。關(guān)系圖如下:
所以我們寫網(wǎng)站的時(shí)候一般引入 react-router-dom
就可以了智袭。
重構(gòu)
使用了 React Router 之后代碼就可以精簡(jiǎn)成下面這樣了。
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
function Login() {
return <div>Register</div>;
}
function Register() {
return <div>Login</div>;
}
function App() {
return (
<Router>
<div className="App">
<Link to="/login">Login</Link>
<Link to="/register">Register</Link>
<Route path="/login" component={Login}></Route>
<Route path="/register" component={Register}></Route>
</div>
</Router>
);
}
可以看到 React Router 幫我們做了很多的事掠抬。比如正則的匹配吼野,路由的切換等等。更多的用法就看上面的那個(gè)官網(wǎng)就可以了两波。