react-router是react的一个强大的路由库
react-route可以是UI和路由同步。简单的API却有非常强大的功能:延迟加载、动态路由匹配、路径转换权限处理。让你的url变得更加直观。
通过下面几个例子来进行学习:
一 创建一个项目
环境要求:node,npm
git clone https://github.com/reactjs/react-router-tutorial
cd react-router-tutorial
cd lessons/01-setting-up
npm install
npm start
查看 http://localhost:8080。项目已经启动了。
二、 渲染一个路由
1、渲染一个组件
从本质来讲,react-rout就是一个组件。如下:
render(<Router/>, document.getElementById(‘app‘))
要讲这个组件显示出来,我们需要配置一个router
在index.
1. 引入Router和Rout
2. 渲染router
// ...
import { Router, Route, hashHistory } from ‘react-router‘
render((
<Router history={hashHistory}>
<Route path="/" component={App}/>
</Router>
), document.getElementById(‘app‘))
开启服务,查看 http://localhost:8080。虽然页面没有发生变化。
You should get the same screen as before, but this time with some junk in the URL. We’re using hashHistory–it manages the routing history with the hash portion of the url. It’s got that extra junk to shim some behavior the browser has natively when using real urls. We’ll change this to use real urls later and lose the junk, but for now, this works great because it doesn’t require any server-side configuration.
2、创建几个新的组件
- modules/About.js
- modules/Repos.js
// modules/About.js
import React from ‘react‘
export default React.createClass({
render() {
return <div>About</div>
}
})
// modules/Repos.js
import React from ‘react‘
export default React.createClass({
render() {
return <div>Repos</div>
}
})
在app中通过相对路径引入
import About from ‘./modules/About‘
import Repos from ‘./modules/Repos‘
render((
<Router history={hashHistory}>
<Route path="/" component={App}/>
{/* add the routes here */}
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Router>
), document.getElementById(‘app‘))
查看 http://localhost:8080/#/about and http://localhost:8080/#/repos
三、 创建导航
象上面那样,我们直接访问/#/about 可以获取并渲染对应的About组件,现在我们在首页中添加导航,只需点击导航,我们就可以访问对应的页面
在主页面添加导航
// modules/App.js
import React from ‘react‘
import { Link } from ‘react-router‘
export default React.createClass({
render() {
return (
<div>
<h1>React Router Tutorial</h1>
<ul role="nav">
<li><Link to="/about">About</Link></li>
<li><Link to="/repos">Repos</Link></li>
</ul>
</div>
)
}
})
通过在app.js中添加导航,我们可以在首页直接点击对应的按钮,即可进入对应的组件。这样的方法在之前,我们都是通过来实现的。
四、嵌套路由
我们在app.js中创建的导航,应该显示在各个页面。虽然我们可以将ul标签中的内容包含到各个组件中,但是react-router给我们提供了可以嵌套路由的方法。
嵌套UI和嵌套路由
在页面布局中,我们的页面文档结构如下
<App> {/* / */}
<Repos> {/* /repos */}
<Repo/> {/* /repos/123 */}
</Repos>
</App>
我们的页面显示如下:最上面的导航栏,下方的左边是(文章)列表,右边显示对应的(文章)
+-------------------------------------+
| Home Repos About | <- App
+------+------------------------------+
| | |
Repos -> | repo | Repo 1 |
| | |
| repo | Boxes inside boxes |
| | inside boxes ... | <- Repo
| repo | |
| | |
| repo | |
| | |
+------+------------------------------+
react-router顺应这种结构,让我们可以向文档结构和UI一样来嵌套路由
共享的导航栏
我们将About和Repos这两个组件嵌套在App中,这样我们就可以在所有页面共享我们的导航栏。
1、将App route 包含 about 和 repos 这两个路由。
// index.js
// ...
render((
<Router history={hashHistory}>
<Route path="/" component={App}>
{/* make them children of `App` */}
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Route>
</Router>
), document.getElementById(‘app‘))
2、将子组件在App中渲染出来
// modules/App.js
// ...
render() {
return (
<div>
<h1>Ghettohub Issues</h1>
<ul role="nav">
<li><Link to="/about">About</Link></li>
<li><Link to="/repos">Repos</Link></li>
</ul>
{/* add this */}
{this.props.children}
</div>
)
}
// ...
现在再次点击导航,进入about页面时,我们会发现,包含导航的App组件任然显示着,而子路由对应的组件({this.props.children})被包含在App中。
react-router将UI组织成如下形式
// at /about
<App>
<About/>
</App>
// at /repos
<App>
<Repos/>
</App>
将每件小事做好,组合起来就成就了大事
The best way to build large things is to stitch small things together.
这就是react-router真正的力量,每一个路由都可以作为一个单独的应用被开发(渲染),你可以如你所愿地通过你的路由配置将这些应用组合到一起。应用嵌套应用,盒子嵌套盒子。
如果我们希望将About放到app外面,我们应该怎么做。
五、动态链接
react-rout和a标签不同的另外一个地方就是,react-rout知道自己当前是否是激活状态。所以我们可以将其自定义其显示样式。
1、动态的样式
// modules/App.js
<li><Link to="/about" activeStyle={{ color: ‘red‘ }}>About</Link></li>
<li><Link to="/repos" activeStyle={{ color: ‘red‘ }}>Repos</Link></li>
2、动态的类名
<li><Link to="/about" activeClassName="active">About</Link></li>
<li><Link to="/repos" activeClassName="active">Repos</Link></li>
在App中引入css文件
<link rel="stylesheet" href="index.css" />
内容如下:
.active {
color: green;
}
由于不能自动加载入口文件index.html.需要刷新浏览器
导航链接的封装
// modules/NavLink.js
import React from ‘react‘
import { Link } from ‘react-router‘
export default React.createClass({
render() {
return <Link {...this.props} activeClassName="active"/>
}
})
Now you can go change your links to NavLinks.
// App.js
import NavLink from ‘./NavLink‘
// ...
<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to="/repos">Repos</NavLink></li>
六、URL参数
想一想,下面这些链接,我们改如何操作
/repos/reactjs/react-router
/repos/facebook/re
他们对应的路由如下
/repos/:userName/:repoName
这些以:开头的是url参数,被解析出来后,作为变量被路由组件使用,如:this.props.params[name]
增加带参数的路由
应用该如何渲染如下的url:/repos/:userName/:repoName.
首先我们需要一个可以被改路由渲染出来的组件,如下创建一个
// modules/Repo.js
import React from ‘react‘
export default React.createClass({
render() {
return (
<div>
<h2>{this.props.params.repoName}</h2>
<h2>{this.props.params.userName}</h2>
</div>
)
}
})
点击 repos可以看到下方的列表,点击react-router可以看见 react-router 和reactjs分别作为repoName 和 userName 输出来了。
七、更多的构建
如果我们点击一个repo的链接,进入到一个repo中,这个时候我们想进入另外一个repo.我们需要返回去。我们是否可以将repos中的链接如导航栏一下,显示在各个repo中呢?
首先,我们将Repo Route 放到 Repos Route的里面,然后将repo作为this.props.children在repos中渲染出来。
// index.js
// ...
<Route path="/repos" component={Repos}>
<Route path="/repos/:userName/:repoName" component={Repo}/>
</Route>
// Repos.js
// ...
<div>
<h2>Repos</h2>
<ul>
<li><Link to="/repos/reactjs/react-router">React Router</Link></li>
<li><Link to="/repos/facebook/react">React</Link></li>
</ul>
{/* will render `Repo.js` when at /repos/:userName/:repoName */}
{this.props.children}
</div>
八、index路由
哈,终于到这一节了,从一开始我就在纳闷,我的首页只有一个导航,当我要在首页放入其它的内容是,我到了About和Repos,这些内容也会呈现出来,现在我们可以创建一个单独的Home组件来解决这个问题。
// modules/Home.js
import React from ‘react‘
export default React.createClass({
render() {
return <div>Home</div>
}
})
一种方式是,判断是否用子组件,如果没有就调用Home,否则,调用子组件
// App.js
import Home from ‘./Home‘
// ...
<div>
{/* ... */}
{this.props.children || <Home/>}
</div>
//...
这样实现看起来也不错,但是,我们希望Hoem组件也能够给像About和Repos一样同路由关联起来,这是因为一下几点:
1.数据的抽象化获取依赖匹配的路由和其组件
2.OnEnter 钩子
3.代码分离
并且,将App与Home进行解耦,用路由来配置决定那个子组件将被渲染,这样更好。要记住,我们希望在小的组件中构建更小的组件,而不是一个很笨重的家伙。
所以,我们需要在index.js中添加新的路由
// index.js
// new imports:
// add `IndexRoute` to ‘react-router‘ imports
import { Router, Route, hashHistory, IndexRoute } from ‘react-router‘
// and the Home component
import Home from ‘./modules/Home‘
// ...
render((
<Router history={hashHistory}>
<Route path="/" component={App}>
{/* add it here, as a child of `/` */}
<IndexRoute component={Home}/>
<Route path="/repos" component={Repos}>
<Route path="/repos/:userName/:repoName" component={Repo}/>
</Route>
<Route path="/about" component={About}/>
</Route>
</Router>
), document.getElementById(‘app‘))
我们发现,Indexroute 没有 path参数,当父级没有其它参数时并被路由命中时,它成为 this.props.children。
Index route有时候会让人感觉很绕,但用的多了,理解也会更深刻。你可以这样想,如果你访问了一个网站的根目录,/index.html。这个时候,他就回去找 index route
九、Index 链接
如果你发现我们没有一个链接可以回到首页,那么现在我们开始解决这个问题。
按照之前的做法,你也许会这样做
// in App.js
// ...
<li><NavLink to="/">Home</NavLink></li>
// ...
回到浏览器,你高兴的发现链接有了,而且点击后也可以渲染 Home 组件,但奇怪的是,为什么不论当时是哪个页面,Home都是激活状态呢,根据之前学的我们知道,子级路由激活后,父级路由就会激活,但是,/是所有组件的父级。
对应该链接,我们希望它只在Home组件被渲染时,才显示为激活状态,只有当Index路由被渲染时,我们需要让路由组改变index route的状态。两种办法如下:
IndexLink
首先,我们可以使用IndexLink
// App.js
import { IndexLink, Link } from ‘react-router‘
// ...
<li><IndexLink to="/" activeClassName="active">Home</IndexLink></li>
如此,我们修复了在其它页面 Index route也被激活的问题。
onlyActiveOnIndex Property
我们可以使用一个参数来标示,如下
<li><Link to="/" activeClassName="active" onlyActiveOnIndex={true}>Home</Link></li>
之前我们用了NavLink来封装,这里我们也可以这样使用
<li><NavLink to="/" onlyActiveOnIndex={true}>Home</NavLink></li>
十、用Browser History是urls更简洁
我们应用中的URLs使用了hash技术,这是默认的配置,但是我们有更好的选择。
现在的浏览器可以用Javascript来操作url而不不用发送请求,所以我们不需要依赖哈希(#)散列来实现路由,但是这有一个问题(稍后会讲到)
##配置 BrowserHistory
打开index.js 导入 browserHistory 替换掉 hashHistory
// index.js
// ...
// bring in `browserHistory` instead of `hashHistory`
import { Router, Route, browserHistory, IndexRoute } from ‘react-router‘
render((
<Router history={browserHistory}>
{/* ... */}
</Router>
), document.getElementById(‘app‘))
回到浏览器,现在可以看到你的URLs变得更简洁了,但是,刷新一下浏览器,Oh,what happened!
你看到应该和下面一样
Cannot GET /repos
Configuring Your Server
无论你传递了什么url,你的服务器都需要传递给你的app,因为你的应用,一直在操纵浏览器中的URLs.但是当前的服务器去不知道如何处理这些URL.
Webpack-dev-server有一个配置选项,打开package.json 增加 –history-api-fallback.
"start": "webpack-dev-server --inline --content-base . --history-api-fallback"
改变一下index.html文件中的相对路径为绝对路径,
<!-- index.html -->
<!-- index.css -> /index.css -->
<link rel=stylesheet href=/index.css>
<!-- bundle.js -> /bundle.js -->
<script src="/bundle.js"></script>
重启 npm start。现在的URLs依然很简洁,而且刷新也没问题了
十一、Production-ish Server
这一章似乎和React-router没什么关系,但是既然我们在讨论web服务器,我想让情景变得更真实。在下一张,我们也会用到服务端渲染。由于,Webpack-dev-server不是一个真正的生产环境的server,我们需要搭建一个生产环境的sever,并且了解一些环境配置的知识,然后我们就可以开启生产服务器了。
首先,需要安装一些模块儿。
npm install express if-env compression --save
首先,我们需要在package.json中配置一下 if-env.如下:
// package.json
"scripts": {
"start": "if-env NODE_ENV=production && npm run start:prod || npm run start:dev",
"start:dev": "webpack-dev-server --inline --content-base . --history-api-fallback",
"start:prod": "webpack && node server.js"
},
这样,当我们启动服务,npm start时,会进行判断,如果 NODE_ENV=production 则会执行npm run start:prod,否则 npm run start:dev
我们需要用Express来创建一个服务。如下:
// server.js
var express = require(‘express‘)
var path = require(‘path‘)
var compression = require(‘compression‘)
var app = express()
// serve our static stuff like index.css
app.use(express.static(__dirname))
// send all requests to index.html so browserHistory in React Router works
app.get(‘*‘, function (req, res) {
res.sendFile(path.join(__dirname, ‘index.html‘))
})
var PORT = process.env.PORT || 8080
app.listen(PORT, function() {
console.log(‘Production Express server running at localhost:‘ + PORT)
})
启动服务
Now run:
NODE_ENV=production npm start
# For Windows users:
# SET NODE_ENV=production npm start
恭喜,你可以随便点击一下,我们的后端服务也已经搭好了。
访问一下 http://localhost:8080/package.json。你会发现,这样的文件我们是不希望被访问到的。所以我们需要配置一下哪些目录是可以访问的。
- 创建一个 public 文件夹
- 将index.html和index.css放到这个目录里面
然后再sercer.js中配置静态资源文件的目录
// server.js
// ...
// add path.join here
app.use(express.static(path.join(__dirname, ‘public‘)))
// ...
app.get(‘*‘, function (req, res) {
// and drop ‘public‘ in the middle of here
res.sendFile(path.join(__dirname, ‘public‘, ‘index.html‘))
})
在webpack配置文件中,添加如下:
// webpack.config.js
// ...
output: {
path: ‘public‘,
// ...
}
在启动文件中增加如下:
"start:dev": "webpack-dev-server --inline --content-base public --history-api-fallback",
现在我们根目录不再是一个公共的目录,我们再添加一些压缩代码的功能
// webpack.config.js
// make sure to import this
var webpack = require(‘webpack‘)
module.exports = {
// ...
// add this handful of plugins that optimize the build
// when we‘re in production
plugins: process.env.NODE_ENV === ‘production‘ ? [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin()
] : [],
// ...
}
// server.js
// ...
var compression = require(‘compression‘)
var app = express()
// must be first!
app.use(compression())
现在启动服务
NODE_ENV=production npm start
可以发现,UglifyJs打印的日志,而且输出文件已经被压缩了。
未完待续。。。。(后面的部分我自己还没看懂)
翻译的是github上的入门讲解,详见https://github.com/reactjs/react-router-tutorial