React Router 入门

前言

这两天在学react-router, 之前有了解过, 但没有系统地学, 觉得比较简单, 会用就行. 但当昨天知道react-router结合webpack可以做到按需加载时(点击查看例子), 顿时来了兴趣, 于是就跟着react-router-tutorial上的教程敲了一遍(代码在这里, 还是有不少收获的, 下面我将说明我教程中较重要的点.

温馨提示

教程一共14个lesson, 每一个lesson里面都会有readme.md, 记得要看着readme.md来做.

如果想直接看源码. 那么要注意: 除了第1个lesson以外, 每个lesson的代码其实在下一个lesson文件夹中. 也就是说, 第2个lesson的完整代码, 在第3个lesson中, 需要你看READEME.md自己实现. 一开始我不知道, 还以为教程里的文档与代码不一致, 囧…

嵌套路由

以下是嵌套的路由

1
2
3
<Route path="/" component={App}>
<Route path="index" component={Index}/>
</Route>

则父组件可以通过使用{this.props.children}来显示子路由里的组件, 子组件之间的切换对页面而言是局部刷新, 不用重载整个页面.

1
2
3
4
5
6
7
const App = ({children}) = (
<div>
<h1>Hello React Router</h1>
{/*这显示Index组件*/}
{children}
</div>
)

browserHistory

教程里一开始是使用hashHistory的, 后来有一章内容专门把它替换成browserHistory, 二者的区别主要如下:

  • hashHistory url以#开头, 后面带有一大串奇怪的符号, 用于存储location state, browserHistory 则是平时浏览器浏览网页的url
  • hashHistory 不需要对服务器作额外的配置, 可用于快速上手或演示demo, browserHistory 需要修改服务器配置
  • browserHistory 是基于HTML5 History API的, 在IE8/IE9等不支持API的浏览器中, browserHistory 局部刷新的能力将消失, 退化成整个页面重载(full page reload), 但 hashHistory 仍拥有局部刷新的能力
  • browserHistory 支持服务端渲染(server-side rendering)

下面是使用browserHistory时, 需要的配置:

  • 如果是webpack-dev-server, 命令行后面要加选项 --history-api-fallback, 如package.json里这样写:
1
"start": "webpack-dev-server --inline --content-base . --history-api-fallback"
  • 如果是使用express, 需要设置服务器接收到任何正常请求, 都返回index.html:
1
2
3
4
5
6
var express = require('express'),
app = express()
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'))
})

<! –

  • index.html中引用资源需要变使用绝对路径
1
2
3
4
5
6
7
8
9
<!doctype html>
<html>
<head>
<link rel=stylesheet href=/index.css>
</head>
<body>
<div id=app></div>
<script src="/bundle.js"></script>
</body>

–>

对于路径, 最好在 Route, Link 等组件中定义的路径都是绝对url, 也即斜杠(/)开头, 不然有可能会出现bug. 具体可以查看代码:
路径不是斜杠开头产生的bug

发布生产

1. npm script

生产脚本一般要传环境变量: NODE_ENV='production'

  • 教程是直接是使用if-env, 在npm script内部区分生产与开发:
1
"start": "if-env NODE_ENV=production && npm run start:prod || npm run start:dev"

这种方案的缺点在于, 环境变量需要运行脚本时手动设置, 最终运行命令为:

1
2
3
NODE_ENV=production npm start
# For Windows users:
# SET "NODE_ENV=production" && npm start
  • 考虑到不想手动设置环境变量, 且想跨平台的话, 可以使用cross-env, 上面的npm script可变成:
1
"start-prod": "cross-env NODE_ENV=production npm run start:prod"

相应shell命令为:

1
npm start-prod
  • 但对我而言, 程序开发好了, 只是为了写脚本, 又需要下载依赖包, 我有点不愿意. 而且, 我觉得根本不用照顾Windows用户, 因为服务器一定是Unix-like的, Windows机跑生产脚本机率并不大. 所以我的npm script是这样子的:
1
"start-prod": "export NODE_ENV=production && npm run start:prod"

// 或下面更简洁的写法

1
"start-prod": "NODE_ENV=production npm run start:prod"

2. webpack.config.js

使用优化插件. 最常用的当然是UglifyJsPlugin, 可以混淆代码并减小输出的js的体积

其他插件, 请看官方文档

1
2
3
4
5
plugins: process.env.NODE_ENV === 'production' ? [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin()
] : [],

3. express

express可以开启gzip压缩选项

1
2
3
4
5
6
// compression 这个模块是下载express依赖包就有的
var compression = require('compression')
// must be first!
if(process.env.NODE_ENV === 'production')
app.use(compression())

Server Rendering

如何判断一个基于react的webapp是否进行了服务端渲染呢? 方法很简单, 打开网页, 右键->显示网页源码.

也可以命令行

1
curl http://domain/index.html

如果显示的是下面那样, 说明是客户端渲染, 服务端只返回最初的DOM结构, #app的子节点都由react在客户端生成:

1
2
3
4
5
6
7
8
<html>
<head>
<meta charset=utf-8/>
<title>My First React Router App</title>
<link rel="stylesheet" href="/index.css">
</head>
<div id=app></div>
<script src="/bundle.js"></script>

如果是是服务端渲染, 则返回是完整的html, 如下面的例子:

1
2
3
4
5
6
<html>
<meta charset=utf-8/>
<title>My First React Router App</title>
<link rel=stylesheet href=/index.css>
<div id=app><div data-reactid=".17pmsylxdkw" data-react-checksum="422886483"><h1 data-reactid=".17pmsylxdkw.0">Hello, React Router!</h1><ul data-reactid=".17pmsylxdkw.1"><li data-reactid=".17pmsylxdkw.1.0"><a href="/fuck" data-reactid=".17pmsylxdkw.1.0.0">index link</a></li><li data-reactid=".17pmsylxdkw.1.1"><a href="/about" class="active" data-reactid=".17pmsylxdkw.1.1.0">about</a></li><li data-reactid=".17pmsylxdkw.1.2"><a href="/repos" data-reactid=".17pmsylxdkw.1.2.0">repos</a></li><div class="container" data-reactid=".17pmsylxdkw.1.3"><div data-reactid=".17pmsylxdkw.1.3.0">About</div></div></ul></div></div>
<script src="/bundle.js"></script>

在这个章节, 我遇到了很奇怪的问题. 直到博客写到这里, 看到上面代码, 我才知道问题在哪里, 并把它解决了. 哈哈,看来写博客还是有用的.

这个问题就是: 我发现按照官网的例子实现后, 访问根路径时, 页面是由客户端渲染的, 而别的路径的页面才是服务端渲染的.

为什么会这样呢?

注意到上面客户端渲染的例子是有head的, 那是我自己建的index.html; 而服务端渲染的例子是没有head的, 那是教程里的代码. 我再去对比教程里的源码, 发现它的public目录下是没有html文件的. 于是乎, 我把自己建的index.html删除掉, 问题就解决了, 访问根路径时呈现的页面, 也是服务端渲染的了. 也就是说, 如果静态文件夹里有相应的静态文件(index.html), 则express就直接返回该文件(index.html), 不会再去动态生成了.

则要注意的是, 想开启服务端渲染, 项目下面不能有index.html

所以说, 站在岸上学不会游泳. 学习新技术, 就算觉得简单, 仍要动手实践一下才行.

教程到此就终止了.
它指出: server rendering 是最前沿的技术, 目前还没有所谓的”最佳实践”, 因此剩下的路需要自己去探索

参考资料

Fork me on GitHub