前端路由原理及react-router的常用組件

在react中,通常都是使用單頁面應用(SPA),即整個頁面只有一個html,然后通過不同的url地址進行組件的匹配和切換。

我們看到的url地址可能會有兩種形式,一種是 localhost:3000/home,一種是 localhost:3000/#/home,兩種地址的區別在于有無#,有#的是根據hash來進行匹配,即url中的錨點,本質上是通過location.hash來改變href,hash后的內容是不會發送給服務器的,沒有#是通過html5的history來進行跳轉,兩者跳轉后都不會進行刷新。

具體來說,通過#來區分路由的實現原理是監聽hash的變化,這里監聽的方式是使用hashchange 事件,當url地址發生了變化時,通過匹配當前url的地址來進行展示,代碼如下所示

<body>
    <div>
       <a href="#/home">首頁</a>
       <a href="#/about">關于</a>
    </div>
    <div id="content"></div>

  <script>
    window.addEventListener('hashchange',()=>{
      const content = document.getElementById('content')
      switch(location.hash){
        case '#/home':
          content.innerHTML = '首頁'
          break
        case '#/about':
          content.innerHTML = '關于'
          break  
        default:  
          content.innerHTML = ''
      }
    })
  </script>
</body>

通過html5的history來實現跳轉無刷新,就需要阻止a標簽的默認行為,再通過history的pushState這一方法實現url地址的替換,同樣是監聽url地址的變化,這里使用popState方法,當url地址發生了變化之后,展示對應的內容。

<body>
  <div>
    <div>
      <a href="/home">首頁</a>
      <a href="/about">關于</a>
    </div>
    <div id="content">
   </div>
</body>

<script>
  const content = document.getElementById('content')
  const aEles = document.getElementsByTagName('a')

  for (let el of aEles) {
    el.addEventListener('click', (event) => {
      event.preventDefault()
      const href = el.getAttribute("href")
      history.pushState({}, '', href)
      urlchange()
    })
  }

  window.addEventListener('popstate', () => {
    urlchange()
  })

  function urlchange() {
    switch (location.pathname) {
      case '/home':
        content.innerHTML = '首頁'
        break
      case '/about':
       content.innerHTML = '關于'
        break
      default:
        content.innerHTML = ''
    }
  }
</script>

以上的錨點和history分別對應了react-router中的HashRouter和BrowserRouter,在react-router中,想要使用路由組件必須在最外層包裹一層HashRouter或者BrowserRouter,來選擇想要使用的路由類型,然后才能使用react-router提供的其它組件,react-route中用于web端的庫為react-router-dom,以下所有的組件都是從react-router-dom中導出。

下面來說說react-router的常用組件

1、<Link>和<NavLink>,這兩個標簽都是由a標簽的封裝,通過to屬性指定跳轉的鏈接地址,<NavLink>比<Link>多的是可以指定選中的樣式和類名,如

<Link to="/about">關于</Link>

通過<Link>標簽,點擊之后就可以跳轉到指定的地址,此時即使外層包裹的是HashRouter,也不需要自己加上#,react-router會幫我們在url上加上#


hashRouter.png

2、當使用<Link>定義了跳轉的url地址后,此時需要指定匹配跳轉該url地址時需要顯示的內容,此時使用的是<Route>,通過path屬性指定url地址,component屬性指定渲染的組件,格式如

 <Route path="/about" component={About} />

加上了之后, /about這個地址顯示的就是About這個組件里的內容。<Route>進行的是模糊匹配,一個url路徑可能可以匹配多個Route,如果需要嚴格匹配的話,可以增加一個屬性 exact,適合沒有二級路由的時候開啟。

3、在有很多的Route的情況下,即使在第一個Route匹配到合適的之后,仍然會繼續向下匹配,直到最后一個,所以在所有的<Route>外包裹一個<Switch>標簽可以讓它進行唯一的匹配,匹配到合適的之后就不繼續匹配了。

4、當所有的<Route>都無法匹配到url上的地址時,可以定義 <Redirect>組件直接重定向到一個頁面,通過to來指定路由地址,這個組件要放置到<Route>的最后面,因為它和<Link>不同,<Link>是點擊了之后才會跳轉對應的地址,而<Redirect>會直接執行并跳轉

<Redirect to="/about"/>

5、通過路由來匹配的組件稱為路由組件,<Route path="/about" component={About} />,這里的About就是路由組件,和其它的組件是不一樣的,路由組件的props里有一些數據,其中包括三大屬性,history、location和match,history可以自定義頁面的跳轉,location用來獲取url地址相關的信息,match可以用作動態路由的匹配。


props里傳遞的屬性.png

但一般的組件是沒有這些props屬性的,如果一般的組件也需要這樣一些屬性的話,可以通過一個高階組件 withRouter。如

class myCom extends PureComponent { }
export default withRouter(myCom)

再來說說路由傳參

有時候,我們希望在鏈接上帶一個id值或者兩個頁面之間跳轉的時候傳遞一些參數,這時候有三種路由傳參方式

1、params傳參

<NavLink to="/detail/1">商品詳情</NavLink>
<Route path="/detail/:id" component={Detail}/>

// 如果是自行定義跳轉的地址可以通過 this.props.history.push("/detail/1")

此時的id就是動態的,可以在id這個位置傳遞任意的數值或者字符串,然后通過props里的match對象中的params獲取動態匹配的內容

2、search傳參

<NavLink to="/detail?id=1">商品詳情</NavLink>
<Route path="/detail" component={Detail}/>

// 如果是自行定義跳轉的地址可以通過 this.props.history.push("/detail?id=1")

此時相當于在url上添加一個問號進行拼接,需要把地址拼成一種鍵值對的形式,通過 props里的location對象中的search屬性獲取從問號開始的匹配內容,這一種路由的匹配方式需要自行解析字符串

3、state傳參

<NavLink to={ pathname: "/detail", state: { id: 1} }>商品詳情</NavLink>
<Route path="/detail" component={Detail}/>

// 如果是自行定義跳轉的地址可以通過 this.props.history.push("/detail", { id: 1})

這樣的傳遞參數方式通過props里的location對象中的state屬性來獲取傳遞的值,這種方式的可以直接以對象的形式傳遞,并且可傳遞的數據更多,這些參數不會顯示在url上

用一個小的組合案例展示以上內容

import React, { PureComponent } from 'react'
import { NavLink, Route, Switch, withRouter, Redirect } from "react-router-dom"
import Home from "./pages/Home"
import About from "./pages/About"
import Detail from "./pages/Detail"
import Product from './pages/Product'

class App extends PureComponent {
  jumpToProduct(){
    this.props.history.push('/product')
  }
  render() {
    return (
      <div>
        <NavLink exact to="/">首頁</NavLink>
        <NavLink to="/about">關于</NavLink>
        <NavLink to="/detail/1">詳情</NavLink>
        <button onClick={e=>this.jumpToProduct()}>商品</button>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
          <Route path="/detail/:id" component={Detail} />
          <Route path="/product" component={Product}/>
          <Redirect to="/"/>  
        </Switch>
      </div>
    );
  }
}

export default withRouter(App)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容