深入了解 React Router 原理

說到 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 做嘗試:

  1. 在進(jìn)入頁(yè)面的時(shí)候獲取當(dāng)前 url 的 hash 值,根據(jù)這個(gè) hash 值去更新 UI 從而通過 showUI() 來(lái)切換到對(duì)應(yīng)的組件
  2. 同時(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-domreact-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)系圖如下:

關(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)就可以了两波。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末箫锤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雨女,更是在濱河造成了極大的恐慌,老刑警劉巖阳准,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氛堕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡野蝇,警方通過查閱死者的電腦和手機(jī)讼稚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門括儒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人锐想,你說我怎么就攤上這事帮寻。” “怎么了赠摇?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵固逗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我藕帜,道長(zhǎng)烫罩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任洽故,我火速辦了婚禮贝攒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘时甚。我一直安慰自己隘弊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布荒适。 她就那樣靜靜地躺著梨熙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吻贿。 梳的紋絲不亂的頭發(fā)上串结,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音舅列,去河邊找鬼肌割。 笑死,一個(gè)胖子當(dāng)著我的面吹牛帐要,可吹牛的內(nèi)容都是我干的把敞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼榨惠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼奋早!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起赠橙,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤耽装,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后期揪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掉奄,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年凤薛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了姓建。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诞仓。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖速兔,靈堂內(nèi)的尸體忽然破棺而出墅拭,到底是詐尸還是另有隱情,我是刑警寧澤涣狗,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布谍婉,位于F島的核電站,受9級(jí)特大地震影響屑柔,放射性物質(zhì)發(fā)生泄漏屡萤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一掸宛、第九天 我趴在偏房一處隱蔽的房頂上張望死陆。 院中可真熱鬧,春花似錦唧瘾、人聲如沸措译。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)领虹。三九已至,卻和暖如春求豫,著一層夾襖步出監(jiān)牢的瞬間塌衰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工蝠嘉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留最疆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓蚤告,卻偏偏與公主長(zhǎng)得像努酸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杜恰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 在這個(gè)教程里获诈,我們會(huì)從一個(gè)例子React應(yīng)用開始學(xué)習(xí)react-router-dom。其中你會(huì)學(xué)習(xí)如何使用Link...
    uncle_charlie閱讀 34,715評(píng)論 1 40
  • 一心褐、基本用法 React Router 安裝命令如下舔涎。 $ npm install -S react-router...
    sunnyghx閱讀 4,509評(píng)論 0 6
  • 喝了兩粒液體鈣之后 頓時(shí)又有了想你的力氣 那天站點(diǎn)前相擁著的踱步 好像把前半生所有的 委屈和溫柔都給了你 我倘使是...
    Dianee閱讀 477評(píng)論 1 3
  • 今天好像就這么被荒廢了。
    倪飛飛飛飛飛閱讀 292評(píng)論 0 2
  • 曾經(jīng)有一段時(shí)間昼伴,經(jīng)常問孩子,今天高興的事是什么镣屹?意在培養(yǎng)孩子發(fā)現(xiàn)美圃郊、尋找快樂的積極向上的情緒狀態(tài)。一開始孩子并不能...
    巴西芒果閱讀 431評(píng)論 0 0