React Router 4 簡介及其背後的路由哲學

rccoder發表於2018-01-21

本文翻譯自:An Introduction to React Router v4 and its Philosophy Toward Routing

譯文地址:github.com/rccoder/blo…

本文翻譯自:An Introduction to React Router v4 and its Philosophy Toward Routing

React Router 4 引入了一種基於 component 的動態路由。

這篇文章中會討論思考 React Router 背後的哲學,同時也會通過分析 React Router 文件中的示例程式碼來介紹一下它的語法。

更多 React Router 的介紹請戳 這裡

不想看文字版本還可以點選 這裡 看視訊。

如果你這幾年一直在密切關注著 React,你應該會注意到 React Router 已經經歷了多個版本的迭代。今天的 v4 版本更是一個巨大的改變。

產生這些變化的原因都是非常自然的 —— 今天 React 的開發者相比於 React Router 剛產生的時候更有經驗。在 2014 年的時候,人人都是新手。沒有人會想到 component 的概念在不足一年的 React 中會有這麼重要。

因為上面的原因,React Router 的第一個 commit 是這樣的:

React Router 4 簡介及其背後的路由哲學

當時,React Router 的作者 Michael 和 Ryan 都有著 Ember 的開發經驗。自然著,React Router 的第一個版本和 Ember 的路由有點相似 —— 都是靜態著去建立路由,作為應用初始化的一部分。

這種路由的概念就和熟悉的 Express、Angular、Ember 的概念一致。甚至在 React Router 4 release 之前,使用的也是靜態路由。

使用靜態路由的時候往往會有這樣的一段程式碼寫在 routes.js 裡面:

const routes = (
  <Router>
    <Route path='/' component={Main}>
      <IndexRoute component={Home} />
      <Route path='playerOne' component={Prompt} />
      <Route path='playerTwo/:playerOne' component={Prompt} />
      <Route path='battle' component={ConfirmBattle} />
      <Route path='results' component={Results} />
      <Route onEnter={checkAuth} path='dashboard' component={Dashboard} />
    </Route>
  </Router>
)
export default routes
複製程式碼

然後在初始化應用的時候,會把路由匯入,然後渲染:

// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import routes from './config/routes'
ReactDOM.render(routes, document.getElementById('app'))
複製程式碼

這就產生了一個問題:靜態路由不好用嗎?

答案顯示是 “不,好用”。但大家仍然會覺得靜態路由模式不是 React 的風格。

在 React Router 誕生以來,其作者不僅對構建複雜應用的路由有了更多的經驗,同時對 React 本身也有了更多的理解。後面在工作與討論中發現,React Router 的 API 和 React 背後遵循的一些原則有點背道相馳。再回過頭來看下前面的程式碼,我們將一個 onEnter 的 prop 傳給 Route 元件。

<Route onEnter={checkAuth} path='dashboard' component={Dashboard} />
複製程式碼

這段程式碼是指在渲染 dashboard 元件的時候需要經過一層鑑權。這聽起來像是在 Dashboard 的 componentDidMount 生命週期裡去做一些事情?的確就是這樣。

在 React Router 4 之前,它所做的事情有點超出路由這個層面。React Router 4 針對這些問題作出了修正,使得他和 React 相處的更好。如果你瞭解 React 以及它使用 component 的優勢,React Router 4 會讓你感覺更加親切 —— 你需要先忘記你對傳統靜態路由的一些瞭解。

現在還有一個問題是:React Router 4 到底做了什麼不再讓他和 React 有衝突呢?答案是他拋棄了靜態路由而開始偏向於使用動態路由,所有的 API 都是基於 component。這也就意味著宣告路由的時候就像普通元件一模一樣。

簡單看下下面的例子:

先從最基本的程式碼開始,然後為其新增路由功能。

import React, { Component } from 'react'
class App extends Component {
  render() {
    return (
      <div>
        React Rotuer Course
      </div>
    )
  }
}
export default App
複製程式碼

和我前面所說的一樣,React Router 4 就是一個普通的 component。因此第一件事是 import 我們所需要的東西。

import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'
複製程式碼

這裡需要注意一些事情。首先,我們 import 了 BrowserRouter 並將之重新命名為 Router。這雖然不是必須的,但在程式碼中還是很常見的。BrowerRouter 所做的事情就是允許 React Router 將應用的路由資訊傳給任何他需要的元件(通過 context)。因此,要讓 React Router 正常工作,需要在應用程式的根節點中渲染 BrowerRouter

import React, { Component } from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'
class App extends Component {
  render() {
    return (
      <Router>
        <div>
          React Rotuer Course
        </div>
      </Router>
    )
  }
}
export default App
複製程式碼

接下來將使用 RouteRoute 是 React Router 4 背後的支撐。當應用程式的 location 匹配到某個路由的時候,Route 將渲染指定的 component,否則渲染 null。舉個例子,當我們應用的路由是 / 的時候要渲染一個 Home 元件,程式碼會長的像下面這樣:

import React, { Component } from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'
const Home = () => (
  <h2>Home</h2>
)
class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <Route path='/' component={Home} />
        </div>
      </Router>
    )
  }
}
export default App
複製程式碼

上面的程式碼中,如果我們的路徑是 / 的時候,將看見 Home 元件。如果不是的話,什麼都看不到(Route 會渲染 null)。

下面將加入更多的路由:

import React, { Component } 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 Topics = () => (
  <div>
    <h2>Topics</h2>
  </div>
)
class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <Route path='/' component={Home} />
          <Route path='/about' component={About} />
          <Route path='/topics' component={Topics} />
        </div>
      </Router>
    )
  }
}
export default App
複製程式碼

如上,如果想在應用中加入更多的路由,只需要渲染更多的 Route 元件。如果你還沒有忘記靜態路由,可能會對渲染路由這種事情感到奇怪。

你只需要記住 Route 只是一個具有渲染方法的普通 React 元件,該渲染方法渲染元件還是 null 取決於 path 是否匹配。因為在上面的例子中,要麼渲染元件,要麼渲染 null

在上面程式碼中有一個點需要注意:執行這個程式的時候,前往 about 路徑,About 元件和 Home 元件都會被渲染。這是因為即使 / 不是完全匹配的,但仍舊會認為是部分匹配,所以 Home 元件也會被渲染。為了解決這種問題,需要在 /Route 中新增一個 exact 的 prop,來確保只有完全匹配的時候才會渲染。

<Route exact path='/' component={Home} />
複製程式碼

現在我們通過應用的 location 在動態的渲染 UI,下一件事情就是如何去改變應用的 location。這正是 Link 元件所要做的事情,它是一個允許使用者宣告性的瀏覽應用的元件。現在,使用 Link 新增一個簡單的導航吧。

render() {
  return (
    <Router>
      <div>
        <ul>
          <li><Link to='/'>Home</Link></li>
          <li><Link to='/about'>About</Link></li>
          <li><Link to='/topics'>Topics</Link></li>
        </ul>
        <Route path='/' component={Home} />
        <Route path='/about' component={About} />
        <Route path='/topics' component={Topics} />
      </div>
    </Router>
  )
}
複製程式碼

事實上現在已經介紹完了 React Router 4 的基本操作。我們基於應用的 location 使用 Route 去渲染不同的元件,並且通過 Link 元件來更改應用的 location。

更深一點,一起看看巢狀的路由。巢狀路由是 React Router 之前版本的一個基礎功能,今天它仍然也是。與之前版本相比,最大的區別就是現在建立巢狀路由的方式。在之前的版本中,只需要在路由配置中巢狀的使用路由,但今天有雨 React Router 4 是動態路由,所以這樣做是行不通的。但是就我而言,覺得 React Router 4 的巢狀路由比之前版本的更加直接。再次強調一下:忘記之前你對靜態路由瞭解的一切。

再看一下我們的例子,如果我們想要 Topics 元件渲染一個巢狀的 導航和其他的一些路由該怎麼做呢?不需要很複雜,就像巢狀一個 div 一樣,你只需要巢狀使用 Route

const Topic = () => {
  <div>
    <h3>TOPIC</h3>
  </div>
}
const Topics = () => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`/topics/rendering`}>
          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`/topics/components`}>
          Components
        </Link>
      </li>
      <li>
        <Link to={`/topics/props-v-state`}>
          Props v. State
        </Link>
      </li>
    </ul>
    <Route path={`/topics/rendering`} component={Topic} />
    <Route path={`/topics/components`} component={Topic} />
    <Route path={`/topics/props-v-state`} component={Topic} />
  </div>
)
複製程式碼

現在當使用者導航到 /topics 時,將看到一個巢狀的導航欄,UI 也會隨著 location 的變化而自動改變。唯一的區別是我們現在正在通過 React Router 在一個元件內部渲染 navbarRoute

你可能會注意到我們都是在硬編碼 URL,而不是通過當前巢狀的位置來動態建立。React Router 在渲染一個元件的時候,它會傳遞三個東西:matchlocationhistory。在這個例子中,我們想要的是 match.url,它會給我們當前 URL 中匹配的部分(在我們的例子中,//topics)。所以在任何我們不好硬編碼 /topic 的地方,都可以使用 match.url 替換。

const Topic = () => {
  <div>
    <h3>TOPIC</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}/rendering`} component={Topic} />
    <Route path={`${match.url}/components`} component={Topic} />
    <Route path={`${match.url}/props-v-state`} component={Topic} />
  </div>
)
複製程式碼

還有另外一件事情你可能會注意到:即使渲染相同的元件,我們也在渲染三個不同的 Route,他們之前唯一的區別是巢狀的 URL。下面是使用 url 引數的經典例子。

const Topics = ({ match }) => (
  <div>
    ...
    <Route path={`${match.url}/:topicId`} component={Topic} />
  </div>
)
複製程式碼

React Router 渲染 Topic 元件時,使用了之前介紹過的 match 屬性,與此類似,還可以使用 match.params 下的 topicId

const Topic = ({ match }) => (
  <div>
    <h3>{match.params.topicId}</h3>
  </div>
)
複製程式碼

最後,當我們處於 /topics 路由時,如果某個主題還未被選中,我們想渲染一個文字,比如:Please select a topic。我們可以建立一個渲染文字的元件或者使用 Routerender 屬性:

<Route 
  exact 
  path={match.url} 
  render={() => ( <h3>Please select a topic.</h3> )}
/>
複製程式碼

就這樣,我們酷酷的程式碼會長得像這樣:

import React, { Component } 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>
)
class App extends Component {
  render() {
    return (
      <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 App
複製程式碼

React Router 是一個以 component 為 API 的 Router,是一個真正意義上的 React Router。我相信 React 會讓你成為更好的 JavaScript 開發者,而 React Router 4 會讓你成為更好的 React 開發者。

想要與作者交流?點選 這裡檢視原文 瞭解更多

相關文章