iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 24
0

你要去哪裡:React Router

不知道你們還記不記得這個綜藝節目?「你要去哪裡?」,雖然不知道是不是事先喬好的,但我記得我那個時候很愛看,就看著他們每次都出乎意料地到各個不同的地方冒險。如果你記得這節目,那應該有勾起你的一些回憶。如果你不記得,那也就算了,反正這也不是這篇文章的重點。

之前我們有講到說假如你要寫 SPA 的話,一堆麻煩的事情都要自己做,而這些麻煩的事情就包含路由,Router。路由就是決定什麼路徑要做什麼事情。

為了怕你忘記,我們回顧一下我們之前在 Express 是怎麼寫的:

app.get('/', function (req, res) {
  res.send('hello world');
})

app.get('/hello', function (req, res) {
  res.send('你好');
})

app.get('/hello/:name', function (req, res) {
  res.send('你好, ' + req.params.name);
})

上一章的範例裡面,我們用 React 做出了一個非常簡單的小程式,但接下來我們想要挑戰更複雜的,例如說可以在多個頁面之間切換。假設我們像以往一樣用 server side 去做的話,就是像上面那樣,用 Express 寫路由然後 render 出相對應的 view。但是我們現在的目標是前後端完全分離,所以前端要寫一個 SPA,讓前端變成一個完整的 App。

因此,我們就要在原有的基礎上再加更多功能,才能讓 React 小程式變成一個完整的 App。想加上路由的功能,我們需要 react-router,它的用法也十分簡單,就跟我們之前用 component 差不多:

ReactDOM.render(
  (
    <Router history={hashHistory}>
      <Route path="/" component={App}>
        <Route path="about" component={About}/>
        <Route path="users/:userId" component={Users} />
      </Route>
    </Router>
  ),
  document.getElementById('root')
);

history有分成兩種:browserHistoryhashHistory,這兩種有什麼差別呢?前者的網址長得會像 http://localhost:3000/users/2 ,後者的會是:http://localhost:3000/#/users/2 ,其實就是hashHistory多了一個 hash,#的符號。

為什麼要分這兩種呢?首先,一定是第一種的可讀性比較好嘛,因為看起來沒有任何異狀,就跟你現在瀏覽任何網頁一樣。只是第一種如果必須搭配 nginx 或是其他程式才能夠順利運行。

例如說你現在放你的 index.html 到 http://example.com ,你在網址列上面打:http://example.com ,瀏覽器嘗試開啟預設的 index.html,找到之後就可以連上了。但如我你在網址列上面打:http://example.com/users ,對「伺服器」來說,這個網址並沒有對應到任何檔案,所以會返回 404 not found。也就是說,你不能直接打這個網址連進去那個頁面。

那你要怎麼成功看到 http://example.com/users ?你要先到 http://example.com ,再從頁面上面「點擊前往 users 的按鈕」才行。因為你在這個頁面上點擊,這個事件會先被 react router 攔截著,知道你要切換頁面,於是就把後面的控制權全部都搶去,自己處理之後的事情。所以雖然你網址上看到的是 http://example.com/users ,但這完全是前端 JavaScript 自己處理的結果,你實際上發這個 request 給 server 的話,是找不到任何東西的。

有 hash 的話,情況就不一樣了。因為 http://localhost:3000/#/users/2 這個網址,其實會跟 server 拿到的檔案還是 http://localhost:3000/index.html#之後的那些參數都是給瀏覽器這邊看的而已。

所以我們在這邊先選擇 hashHistory

(不過如果你實際嘗試上面那一段,會發現無論用哪一種,都可以順利連到,那是因為我們使用的 create-react-app 這套 library 在 run 的時候有自動內建了一個 Express server。)

好,讓我們再把重點切換回最重要的 Router 的部分,再看一次程式碼:

ReactDOM.render(
  (
    <Router history={hashHistory}>
      <Route path="/" component={App}>
        <Route path="about" component={About}/>
        <Route path="users/:userId" component={Users} />
      </Route>
    </Router>
  ),
  document.getElementById('root')
);

你比對一下大概就知道是什麼意思了,就只是在宣告說哪個路徑要渲染哪一個 Component 而已。這邊比較需要注意的是巢狀關係,about 跟 users 是被包在 App 裡面的。讓我們來看一下 App 這個 component 會長怎樣:

class App extends React.Component {
  render () {
    return (
      <div>
        <h1>App</h1>
        <ul>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/users/1">Users 1</Link></li>
          <li><Link to="/users/2">Users 2</Link></li>
        </ul>
        {this.props.children}
      </div>
    );
  }
}

重點在{this.props.children},這個是什麼呢?這個就是 react-router 會傳給你的 props,例如說你現在的網址是/,這個東西就會是空的。如果你現在的網址是/aboutthis.props.children就會是你在上面指定好的About這個 component。總之就是匹配到哪一個,就會傳入那一個相對應的組件。

為了方便起見,在這邊我們把所有組件的程式碼都寫在一起:

import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Link, hashHistory } from 'react-router'
import './index.css';

class App extends React.Component {
  render () {
    return (
      <div>
        <h1>App</h1>
        <ul>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/users/1">Users 1</Link></li>
          <li><Link to="/users/2">Users 2</Link></li>
        </ul>
        {this.props.children} 
      </div>
    );
  }
}

class About extends React.Component {
  render () {
    return (<h1>About</h1>);
  }
}

class Users extends React.Component {
  render () {
    const id = this.props.params.userId;
    return (
      <div>
        user id: {id}
      </div>
    )
  }
}


ReactDOM.render(
  (
    <Router history={hashHistory}>
      <Route path="/" component={App}>
        <Route path="about" component={About}/>
        <Route path="users/:userId" component={Users} />
      </Route>
    </Router>
  ),
  document.getElementById('root')
);

Users裡面,this.props.params.userId對應到的就是你在 Route 裡面宣告的 <Route path="users/:userId" component={Users} />。於是就可以成功的取到網址上面的參數。

上面是一個非常簡單的範例,但我認為已經能夠大致上說明 react router 的用法,你仔細研究一下之後應該就能搞懂這個到底在幹嘛。如果你想知道更多,可以去看官方說明文件或是參考其他教學,最後附上上面那段程式碼跑出來的結果:


上一篇
換一種思考方式:React
下一篇
如果有一天我變得更複雜:Redux
系列文
Half-Stack Developer 養成計畫30

尚未有邦友留言

立即登入留言