React-Router v4 學習

React-Router v4

1. 設計理念

React-Router v4 的核心思想是 “動態路由”,它是相對于 “靜態路由” 提出的設計概念。

何為靜態路由?在服務器端所有的路由都是在設計過程已經規劃完成。在許多客戶端框架中也遵循了上述思路,即在頁面渲染之前先規劃好路由,將路由導入到頂層的模塊中。但這種理念在 React-Router 中不再適用,作者希望在學習 React-Router 之前放棄 “靜態路由” 設計思路。

1.1. 動態路由

在 React-Router 中的所有設置都是組件,因此路由是隨著應用的渲染被同時計算得到,而不是獨立于應用之外。為了解釋這個理念我們首先學習 React-Router 在應用中的使用,

  • 將 React-Router 的組件聲明到應用頂層:

    import { BrowserRouter } from 'react-router-dom'
    
    ReactDOM.render((
      <BrowserRouter>
        <App/>
      </BrowserRouter>
    ), el)
    
  • 使用 link 組件指向相應的位置:

    const App = () => (
      <div>
        <nav>
          <Link to="/dashboard">Dashboard</Link>
        </nav>
      </div>
    )
    
  • 最后利用 Route 組件關聯位置與顯示 UI:

    const App = () => (
      <div>
        <nav>
          <Link to="/dashboard">Dashboard</Link>
        </nav>
        <div>
          <Route path="/dashboard" component={Dashboard}/>
        </div>
      </div>
    )
    

如果用戶訪問 /dashboard , Route 組件會將路由形式的對象 {match, location, history} 作為屬性參數傳給<Dashboard {...props}> 組件進行渲染,否則不會渲染。

到此為止,似乎與 “靜態路由” 的概念沒有區別,但是不要忘記 Route 是一個組件,這就意味著 Route 組件是否渲染和其中的 pathcomponent 都可以由父組件的狀態或屬性進行設置。

1.2. 嵌套路由

在動態路由中配置嵌套路由就好像 div 標簽中嵌套一個 div , 因為 Route 就是一個組件標簽。

const App = () => (
  <BrowserRouter>
    {/* 這里是 div 標簽*/}
    <div>
      {/* 這里是 Route 組件標簽*/}
      <Route path="/tacos" component={Tacos}/>
    </div>
  </BrowserRouter>
)

// 當 url 匹配 /tacos 時, 該組件被渲染
const Tacos  = ({ match }) => (
  // 這是嵌套的 div 標簽
  <div>
    {/* 這是嵌套的 Route 組件標簽,
        match.url 用以實現相對路徑 */}
    <Route
      path={match.url + '/carnitas'}
      component={Carnitas}
    />
  </div>
)

1.3. 響應式路由

利用動態路由理念很容易在 React-Native 中實現響應式的布局。

const App = () => (
  <AppLayout>
    <Route path="/invoices" component={Invoices}/>
  </AppLayout>
)

const Invoices = () => (
  <Layout>

    {/* always show the nav */}
    <InvoicesNav/>

    <Media query={PRETTY_SMALL}>
      {screenIsSmall => screenIsSmall
        // small screen has no redirect
        ? <Switch>
            <Route exact path="/invoices/dashboard" component={Dashboard}/>
            <Route path="/invoices/:id" component={Invoice}/>
          </Switch>
        // large screen does!
        : <Switch>
            <Route exact path="/invoices/dashboard" component={Dashboard}/>
            <Route path="/invoices/:id" component={Invoice}/>
            <Redirect from="/invoices" to="/invoices/dashboard"/>
          </Switch>
      }
    </Media>
  </Layout>
)

2. 快速入門

首先給出官方的基礎案例:

import React from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link
} from 'react-router-dom'

// 三個基礎呈現組件

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
)

const About = () => (
  <div>
    <h2>About</h2>
  </div>
)

const Topic = ({ match }) => (
  <div>
    <h3>{match.params.topicId}</h3>
  </div>
)

// 一個內嵌的組件

const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`${match.url}/rendering`}>
          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/components`}>
          Components
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/props-v-state`}>
          Props v. State
        </Link>
      </li>
    </ul>

    <Route path={`${match.url}/:topicId`} component={Topic}/>
    <Route exact path={match.url} render={() => (
      <h3>Please select a topic.</h3>
    )}/>
  </div>
)

// 首頁組件

const BasicExample = () => (
  <Router>
    <div>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/topics">Topics</Link></li>
      </ul>

      <hr/>

      <Route exact path="/" component={Home}/>
      <Route path="/about" component={About}/>
      <Route path="/topics" component={Topics}/>
    </div>
  </Router>
)
export default BasicExample

隨后分析上例給出的三個 React-Router 組件,分別是 <BrowserRouter><Route><Link>

2.1. <BrowserRouter>

這是一個路由管理器,使用 HTML5 的 history API (pushState, replaceState, popState) 同步訪問 URL 和組件。具體聲明如下:

import { BrowserRouter } from 'react-router-dom'

<BrowserRouter
  basename={optionalString}
  forceRefresh={optionalBool}
  getUserConfirmation={optionalFunc}
  keyLength={optionalNumber}
>
  <App/>
</BrowserRouter>

2.1.1. basename: string

應用的根目錄,如果你的應用只是整個服務中的子服務,僅適應于某個子目錄,那么就應該設置這個參數。

<BrowserRouter basename="/calendar"/>
<Link to="/today"/> // renders <a href="/calendar/today">

2.2. <Link>

該組件用以聲明應用的鏈接(導航)

import { Link } from 'react-router-dom'

<Link to="/about">About</Link>

2.2.1. to: string || object

定義需要導航的路徑。

<Link to="/courses"/>

<Link to={{
  pathname: '/courses',
  search: '?sort=name',
  hash: '#the-hash',
  state: { fromDashboard: true }
}}/>

2.2.2. replace: bool

如果該選項設置為 true,當你點擊鏈接時,會在 history 棧中取代當前的狀態路徑而不是添加一個新狀態路徑。

2.3. <Route>

這是 React-Router 中最重要的組件了,當請求的狀態路徑與該組件給出的 path 一致時,會渲染所對應的 UI 組件。

import { BrowserRouter as Router, Route } from 'react-router-dom'

<Router>
  <div>
    <Route exact path="/" component={Home}/>
    <Route path="/news" component={NewsFeed}/>
  </div>
</Router>

在 <Route> 組件中有三種渲染方式:

  • <Route component>
  • <Route render>
  • <Route children>

每一種方式都會傳入相同形式的路由屬性 —— {match, location, history}

2.3.1. component

使用 component 渲染方式時,React 會自動將所對應的組件轉化為 React 組件,因此如果所對應組件是內聯函數形式,請使用 render 或 children 渲染方式,避免每次都生成新的 React 組件。

<Route path="/user/:username" component={User}/>

2.3.2. render: func

該方式取代了React 生成組件的過程,而是直接執行一個函數,此外還經常用于路由的打包。

// 直接執行函數
<Route path="/home" render={() => <div>Home</div>}/>

// 打包路由
const FadingRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={props => (
    <FadeIn>
      <Component {...props}/>
    </FadeIn>
  )}/>
)

<FadingRoute path="/cool" component={Something}/>

2.3.3. children: func

有時無論路徑是否匹配都要渲染組件,這種情況下使用 children 渲染方式,它和 render 方式類似只是多了一個匹配過程。

<ul>
  <ListItemLink to="/somewhere"/>
  <ListItemLink to="/somewhere-else"/>
</ul>

const ListItemLink = ({ to, ...rest }) => (
  <Route path={to} children={({ match }) => (
    <li className={match ? 'active' : ''}>
      <Link to={to} {...rest}/>
    </li>
  )}/>
)

注意: component 和 render 方式都優先于 children 方式,因此無法在一個 <Route> 組件中同時使用。

2.3.4. path: string

一個符合正則的有效 URL 路徑,當 Route 中沒有 path 時,該路由總是匹配 。

<Route path="/users/:id" component={User}/>

2.3.5. exact: bool

完全匹配的選項。

<Route exact path="/one" component={About}/>

完全匹配的邏輯:

path location.pathname exact matches?
/one /one/two true no
/one /one/two false yes

2.4. history

<Route> 組件相關的還有三個對象,分別是 historymatchlocation。在這節中我們先來了解 history 這個可變對象。

history 是 React-Router 極少的依賴模塊之一,它主要作用是在不同的環境下管理會話。經常會使用到的 history 有:

  • browser history:DOM 的具體應用,支持 HTML5 的 history API。
  • hash history:DOM 的具體應用,用以兼容老版本的瀏覽器。
  • memory history:在無 DOM 或測試環境下通過內存方式處理來實施,適合 React-Native。

和history 對象相關的主要屬性和方法有:

  • length:history 棧中的狀態路徑個數。
  • action:當前的操作,字符串形式(PUSH, REPLACE, POP)
  • location:當前的狀態路徑。
    • pathname: URL 路徑字段
    • search:URL 查詢字段
    • hash:URL 的 hash 字段
    • state:路徑的狀態,只在 browser 和 memory history 中有效。
  • push(path, [state]):將狀態路徑壓入 history 棧。
  • replace(path, [state]):替換當前的狀態路徑。
  • go(n):向前移動當前指針 n 次。
  • goBack()go(-1)
  • goForward()go(1)
  • block(prompt):暫時阻止導航(“您確定離開”)。

history 是可變的,因此狀態路徑的訪問方式推薦使用 <Route> 的屬性(props)里間接獲取,而不是直接使用 history.location。這可以保證在 React 生存周期里能夠獲得正確的比較。

class Comp extends React.Component {
  componentWillReceiveProps(nextProps) {
    // 返回 true
    const locationChanged = nextProps.location !== this.props.location

    // 錯誤,總是 false,因為 history 是可變的。
    const locationChanged = nextProps.history.location !== this.props.history.location
  }
}

<Route component={Comp}/>

2.5. location

在本文中我一直稱之為狀態路徑,意思很明顯就是具有狀態的路徑。該對象的與 url 對應,其具體屬性如下:

{
  key: 'ac3df4', // not with HashHistory!
  pathname: '/somewhere'
  search: '?some=search-string',
  hash: '#howdy',
  state: {
    [userDefined]: true
  }
}

狀態路徑會在以下幾個位置由 React-Router 給出:

  • Route component 由 this.props.location 給出
  • Route render({location}) => () 給出
  • Route children({location}) => () 給出
  • withRouter 由 this.props.location 給出

你也可以在這幾個位置定義狀態路徑:

  • Link to
  • Redirect to
  • history.push
  • history.replace

狀態路徑中的狀態可用于 UI 分支的顯示等情景。你也可以通過 <Route><Switch> 組件傳遞狀態路徑,這在動畫顯示和延遲導航中非常有用。

2.6. match

match 對象用以描述 <Route path> 匹配 URL 的結果,其實就是 <Route> 組件的 state ,<Route> 組件根據 match 對象進行狀態轉換,主要的屬性有:

  • params:鍵值對對象對應正則模式中的參數字段。
  • isExact:是否完全匹配。
  • path:匹配使用的正則模式。
  • url:所匹配的 URL 路徑字段。

可以在下面位置訪問該對象,意味著將 <Route> 的 state 傳遞為子組件的 props:

  • Route component 由 this.props.match 給出
  • Route render 由 ({match}) => () 給出
  • Route children 由 ({match}) => () 給出
  • withRouter 由 this.props.match 給出

如果路由中沒有 path,將匹配所有路徑, match 對象就是其父對象的 match。

3. API 查漏補缺

官方教程中給出了許多實例,如果了解了上述 API 大部分代碼都能看懂,還有一些 API 主要是增強使用的,為程序員在編程中提供了更多選擇和便利。

3.1. <Redirect>

執行 <Redirect> 將會使應用導航到一個新的位置,同時在 history 棧中新位置替換當前位置。因為是替換操作所以在 <Redirect> 中通常會使用狀態路徑,并在狀態路徑中記錄當前位置,以實現回溯路徑的操作。

import { Route, Redirect } from 'react-router'

<Route exact path="/" render={() => (
  loggedIn ? (
    <Redirect to="/dashboard"/>
  ) : (
    <PublicHomePage/>
  )
)}/>

3.1.1. to: string || object

重定向的位置。

3.1.2. push: bool

該屬性為 true 時,history 棧的操作不再是替換操作而是壓入新的位置元素。

<Redirect push to="/somewhere/else"/>

3.1.3. from: string

表明從那個位置進行重定向操作的。該屬性只有在 <Switch> 組件中才有效。實際上該屬性就是 <Route> 組件中 path 屬性的別稱,與此同理還可以使用 exactstrictpath

<Switch>
  <Redirect from='/old-path' to='/new-path'/>
  <Route path='/new-path' component={Place}/>
</Switch>

3.2. <Switch>

組件和語法中的 switch 功能類似,執行第一個匹配的路由。這個邏輯很直觀也就是排他性,主要解決使用多個 <Route> 時多個路由同時匹配的問題。

<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>

如果 URL 是 /about ,那么上述三個路由都匹配,此時希望只有第一個路由渲染,就需要使用 <Switch> 組件了:

import { Switch, Route } from 'react-router'

<Switch>
  <Route exact path="/" component={Home}/>
  <Route path="/about" component={About}/>
  <Route path="/:user" component={User}/>
  <Route component={NoMatch}/>
</Switch>

3.2.1. location: object

如果 <Switch> 組件中給出了 location 屬性,子元素將不再與當前位置或 URL 進行匹配操作,而是與該屬性進行匹配,并且匹配的子元素的 location 值將被覆蓋為 <Switch> 的 location 屬性值。

3.2.2. children: node

<Switch> 組件中子元素可以是 <Route><Redirect> 組件, <Route> 使用 path 屬性進行匹配,而 <Redirect> 使用 from 屬性進行匹配。

<Switch>
  <Route exact path="/" component={Home}/>

  <Route path="/users" component={Users}/>
  <Redirect from="/accounts" to="/users"/>

  <Route component={NoMatch}/>
</Switch>

3.3. <NavLink>

特殊的 <Link> 組件,主要作用是實現導航欄。

activeClassName: string

當鏈接激活時的類名,默認是 active

activeStyle: object

鏈接激活時的樣式。

<NavLink
  to="/faq"
  activeStyle={{
    fontWeight: 'bold',
    color: 'red'
   }}
>FAQs</NavLink>

isActive: func

在鏈接匹配的基礎上添加邏輯以決定是否該鏈接激活。

// only consider an event active if its event id is an odd number
const oddEvent = (match, location) => {
  if (!match) {
    return false
  }
  const eventID = parseInt(match.params.eventID)
  return !isNaN(eventID) && eventID % 2 === 1
}

<NavLink
  to="/events/123"
  isActive={oddEvent}
>Event 123</NavLink>

其他屬性 exact, strict, location 參考 <Link>

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

推薦閱讀更多精彩內容