這是Webpack+React系列配置過程記錄的第二篇。其他內(nèi)容請參考:
- 第一篇:使用webpack、babel、react、antdesign配置單頁面應(yīng)用開發(fā)環(huán)境
- 第二篇:使用react-router實(shí)現(xiàn)單頁面應(yīng)用路由
- 第三篇:優(yōu)化單頁面開發(fā)環(huán)境:webpack與react的運(yùn)行時打包與熱更新
- 第四篇:React配合Webpack實(shí)現(xiàn)代碼分割與異步加載
- 第五篇:分離Webpack開發(fā)環(huán)境與生產(chǎn)環(huán)境的配置
- 第六篇:在React中使用Redux
本文接著介紹如何實(shí)現(xiàn)單頁面應(yīng)用。
實(shí)際上,單頁面應(yīng)用利用js實(shí)現(xiàn)了頁面的動態(tài)化,用戶使用時基本感知不到網(wǎng)頁是如何實(shí)現(xiàn)的,開發(fā)也不應(yīng)該讓用戶感知到。為了實(shí)現(xiàn)這一步,react提供了一個官方的路由組件react-router,讓你的單頁面應(yīng)用支持頁面的跳轉(zhuǎn)以及動態(tài)渲染。
使用react-router的前端實(shí)現(xiàn)
安裝react-router
npm install --save react-router-dom
我使用的是最新版本4.1.1,這個版本的react-router針對瀏覽器、原生應(yīng)用等環(huán)境已經(jīng)做了區(qū)分。我做的是基于web的單頁面應(yīng)用,因此安裝的時候我使用的是react-router-dom這個依賴。
使用react-router
還是基于上一篇文章搭建的開發(fā)環(huán)境,我們使用官方示例簡單入門對react-router的使用方法。
回顧一下上一篇文章的開發(fā)環(huán)境:

這里主要需要修改index.js,其內(nèi)容改為官方示例代碼:
import React from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
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>
)
const Home = () => (
<div>
<h2>Home</h2>
</div>
)
const About = () => (
<div>
<h2>About</h2>
</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 Topic = ({ match }) => (
<div>
<h3>{match.params.topicId}</h3>
</div>
)
ReactDOM.render(<BasicExample/>, document.getElementById('main'));
注意到上面的代碼引入了react-router的BrowserRouter(重命名為Router),Route,Link三個組件。新版本的react-router提出了一切都以Component實(shí)現(xiàn)的原則,因此這些組件實(shí)際上都是React.Component的實(shí)現(xiàn)。
BrowserRouter是一個容器Component,其內(nèi)部實(shí)現(xiàn)了路由跳轉(zhuǎn)的邏輯。舊版本直接使用Router就可以,新版本根據(jù)環(huán)境不同有不同的實(shí)現(xiàn),Web環(huán)境使用BrowserRouter。
Route也是一個Component,配置了頁面路徑以及當(dāng)用戶輸入的路徑命中時需要渲染的組件。
Link用于跳轉(zhuǎn),有點(diǎn)像HTML的a標(biāo)簽。不一樣的是Link是需要在后端進(jìn)行編譯,最終可能以a標(biāo)簽的形式呈現(xiàn)給用戶。
驗(yàn)證react-router
使用下面命令編譯并運(yùn)行應(yīng)用:
npm run build
npm start
訪問http://localhost:2000
可以看到頁面已經(jīng)可以正常訪問,且點(diǎn)擊可以實(shí)現(xiàn)頁面跳轉(zhuǎn)。

如果你細(xì)心一點(diǎn),你會發(fā)現(xiàn)剛剛訪問的頁面鏈接是使用a標(biāo)簽出發(fā)了頁面的跳轉(zhuǎn)。但是跳轉(zhuǎn)后頁面并沒有向后端發(fā)送頁面請求。事實(shí)上,這就是單頁面應(yīng)用為了解耦前后端實(shí)現(xiàn)的目標(biāo)之一。

我在index.js中使用Link組件,編譯后在瀏覽器上以a標(biāo)簽呈現(xiàn)給用戶。而react-router連接了這些a標(biāo)簽的事件處理,動態(tài)的實(shí)現(xiàn)組件的渲染和瀏覽器地址欄內(nèi)容的修改。因此這個過程不需要重新加載內(nèi)容也不會向后端發(fā)出網(wǎng)絡(luò)請求。
使用react-router的后端實(shí)現(xiàn)
單頁面是為了實(shí)現(xiàn)前后端的業(yè)務(wù)解耦,但絕對的解耦是不存在的。有兩個方面佐證這個觀點(diǎn)。首先是api協(xié)議的約定,這必須要前端與后端的同時支持;其次就是本文的主要內(nèi)容——路由實(shí)現(xiàn)。
缺少后端支持的問題
通過上面內(nèi)容我們知道單頁面應(yīng)用的路由是通過js實(shí)現(xiàn)的,相對于傳統(tǒng)網(wǎng)站而言是“偽跳轉(zhuǎn)”。那么如果用戶直接在瀏覽器上輸入http://localhost:2000/about
然后回車會有什么效果。
使用我的demo環(huán)境,會看到下圖:

果斷的404響應(yīng)。因?yàn)槲业姆?wù)器根本沒有/about這個目錄或者文件。
增加后端支持
為了解決這個問題,我需要增加這樣一個依賴:
npm install --save connect-history-api-fallback
并且在項(xiàng)目根目錄的index.js(注意不是public目錄的index.js,這里應(yīng)該重新命名一下的,當(dāng)前將就一下)。
內(nèi)容改為:
var express = require('express');
var app = express();
app.use('/', require('connect-history-api-fallback')()); // Add
app.use('/', express.static('public'));
var server = app.listen(2000, function() {
var port = server.address().port;
console.log('Open http://localhost:%s', port);
});
另外需要修改一下public目錄下的index.html文件的內(nèi)容。這是使用相對路徑獲取js文件,屬于上次留下來的bug,不改的話會存在問題。內(nèi)容如下:
<html>
<head>
</head>
<body>
<p>Hello world</p>
<div id='main'></div>
<script src="/out.js"></script><!-- 改動行 -->
</body>
</html>
這樣就可以解決問題了。重新在瀏覽器輸入http://localhost:2000/about
可以看到頁面顯示正常了。
需要注意增加的代碼必須放在express.static行的上方。這涉及的express框架的原理,不在這里深究,只要知道connect-history-api-fallback組件根據(jù)配置,幫我們把public目錄下的index.html作為所有不存在的路徑的請求的響應(yīng)返回給瀏覽器。
本文來自作者同步博客