UDN-企业互联网技术人气社区

板块导航

浏览  : 607
回复  : 2

[React] 理解 React 中的前端路由

[复制链接]
泡泡兔的头像 楼主
发表于 2017-1-4 15:22:01 | 显示全部楼层 |阅读模式
  前端很火,既然我选择了这条路,就应该放下心来,认真的学习基础,2016 就快结束了,迎接崭新的 2017,毕竟我还没实现我的小目标呢!

  前面已经写过一篇关于webpack 的入门,以官方和阮大大为参考,干货不是很多,或者说就没干货。这篇文章干货应该也不是很多,也是以官方的教程为参考,唯一的干货可能就是 react-router 的例子吧。

  gulp 还是 webpack ?

  到底是 gulp 还是 webpack,现在网上帖子对于这方面的疑问也很多,这两个工具在某些功能上是有重合的,甚至有人还提出了这样的问题:“有哪些功能是 webpack 取代不了 gulp 的”。我觉得,要真正的理解这两者的取舍,还是要实际地去用,因为只有你使用了,才能做出正确的判断。

  gulp 是一个任务化工具,擅长流程化的一切事情,webpack 更像是一个模块化管理工具,擅长模块化资源的打包。但是 gulp 也可以弄模块,webpack 也可以搞流程,甚至还有人推荐 gulp-webpack 这样的 task。

  这里面的林林总总,还是你自己去体会吧。今天撇开 gulp,来用 webpack 打造一个 react 的开发流程。

  理解 react 路由

  这篇文章要做到的是实现一个基本的前端路由 react-router,大概的页面如下
2.png

1.png

  这是一个多页 app,Home 是首页,导航栏另外有两个页面分别是 About 页面和 Repos 页面,点开之后 URL 会从 / 变到 /about 和 /repos 。

  webpack-dev-server

  webpack-dev-server 是 webpack 轻量级服务器, npm i webpack-dev-server --save 安装,可以通过命令行直接来运行,也可以把它写到 package.json 的 script 里,这样子比较方便,有如下的配置参数 webpack-dev-server --devtool eval --progress --colors --hot ,分别表示:
  1. –devtool eval 当程序运行出错的时候,方便查找到出错的位置和原因
  2. –progress 打包应用的进度
  3. –colors 命令行日志变的有颜色
  4. –hot 热更新
复制代码

  把它写到 package.json 里面:
  1. "script": {
  2.   "start": "webpack-dev-server --devtool eval --progress --colors --hot"
  3. }
复制代码

  webpack.config.js 的配置

  基本的文件结构目录如下:
  1. public // 生成文件夹
  2. - index.html
  3. - index.css
  4. - bundle.js
  5. modules // 路由文件夹
  6. - App.js
  7. - Home.js
  8. - ...
  9. webpack.config.js
  10. index.js // webpack 入口文件
  11. package.json
复制代码

  然后就是最重要的 webpack 配置文件:
  1. var webpack = require('webpack')
  2. module.exports = {
  3.   // 入口文件
  4.   entry: './index.js',
  5.   // 输出文件
  6.   output: {
  7.     filename: 'bundle.js',
  8.     path: 'public',
  9.     publicPath: '/'
  10.   },
  11.   // babel 的加载,react 必须
  12.   module: {
  13.     loaders: [
  14.       { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader?presets[]=es2015&presets[]=react' }
  15.     ]
  16.   },
  17.   // 设置 product 环境变量,用于压缩文件
  18.   plugins: process.env.NODE_ENV = 'production'?[
  19.     new webpack.optimize.DedupePlugin(),
  20.     new webpack.optimize.OccurrenceOrderPlugin(),
  21.     new webpack.optimize.UglifyJsPlugin({
  22.       minimize: true,
  23.       compress: {
  24.         warnings: false,
  25.       },
  26.     })
  27.   ]:[]
  28. }
复制代码

  在配置文件中设置了 product 环境变量,开启的话需要使用 NODE_ENV=production npm start ,window 用户需要设置 SET "NODE_ENV=production" && npm start 。

  什么是前端路由

  对于路由,其实并不陌生,如果对 url 解析,就是 pathname,我们所要访问那个资源文件。写过 nodejs 服务器的同学都知道,后端路由的实现,就是解析 pathname,找到那个资源,简单点的,直接把 html 文件 res.send 给请求者,复杂点的,是通过模版进行 html 构造来返回,如果找不到或权限其他原因,返回 404/error 码。

  前端路由和后端路由实现技术不一样(可能前端文件只有一个 index.html),但是原理是一样的,而且前端路由往往都是通过 hash 来实现了。比如要访问 about 页面,可能是这样一个 url: 127.0.0.1:8080/#/about ,因为对于浏览器来说,# 后面的内容是不会被解析到,而前端 JS 可以通过 window.location.hash 读取到,并通过一系列前端路由规则,对不同的路径进行不同的处理。

  到了 H5 之后,事情出现了变化。H5 对于 history 有了新的 API,会话历史是可以操作的,不用 # 也可以通过 JS 操作前端路由。比如还是访问 about 页面,它实际上: 127.0.0.1:8080/about 。

  首先从体验上来说,前端控制路由,不用向服务器发送请求,访问速度肯定会有非常大的提升,延迟就是前端处理的延迟。虽然有时候新打开的页面会向服务器请求一些 ajax,但页面的响应速度真的是非常的快,我只想说,这种感觉棒棒的。

  但是有一个问题,前端路由控制的 about 页面,其实后端是不存在的,如果我们要直接访问这个页面,或者在这个页面刷新一下,会出现 404 的情况。这个问题也很好解决,服务器后端让 /about 资源请求返回的资源是 / 资源即可,前端路由通过 url 判断 /about 在前端跳转到这个页面。即无论你访问哪个页面,只要是我前端路由的范围之内,我都给你返回 index.html 。

  如果用的是 express 服务器,可以这样:
  1. import { match, RouterContext } from 'react-router'

  2. var app = express()

  3. app.get('*', (req, res) => {
  4.   match({ routes: routes, location: req.url }, (err, redirect, props) => {
  5.     // in here we can make some decisions all at once
  6.     if (err) {
  7.       // there was an error somewhere during route matching
  8.       res.status(500).send(err.message)
  9.     } else if (redirect) {
  10.       // we haven't talked about `onEnter` hooks on routes, but before a
  11.       // route is entered, it can redirect. Here we handle on the server.
  12.       res.redirect(redirect.pathname + redirect.search)
  13.     } else if (props) {
  14.       // if we got props then we matched a route and can render
  15.       const appHtml = renderToString(<RouterContext {...props}/>)
  16.       res.send(renderPage(appHtml))
  17.     } else {
  18.       // no errors, no redirect, we just didn't match anything
  19.       res.status(404).send('Not Found')
  20.     }
  21.   })
  22. })

  23. function renderPage(appHtml) {
  24.   return `
  25.     <!doctype html public="storage">
  26.     <html>
  27.     <meta charset=utf-8/>
  28.     <title>My First React Router App</title>
  29.     <link rel=stylesheet href=/index.css>
  30.     <div id=app>${appHtml}</div>
  31.     <script src="/bundle.js"></script>
  32.    `
  33. }
复制代码

  全是 ES6 的写法。

  hash 路由

  先来介绍一下 hash 路由,现在还能看到很多 hash 路由的页面,速度很快。

  在根目录下的 index.js 中,建立一个基本的 render 组件:
  1. import App from './modules/App'
  2. import About from './modules/About'
  3. import Repos from './modules/Repos'
  4. import { Router, Route, hashHistory } from 'react-router'

  5. render((
  6.   <Router history={hashHistory}>
  7.     <Route path="/" component={App}>
  8.       <Route path="/repos" component={Repos}/>
  9.       <Route path="/about" component={About}/>
  10.     </Route>
  11.   </Router>
  12. ), document.getElementById('app'))
复制代码

  在 App.js 中,从 react-router 引入 Link
  1. import React from 'react'
  2. import { Link } from 'react-router'

  3. export default React.createClass({
  4.   render() {
  5.     return (
  6.       <div>
  7.         <h1>React Router Tutorial</h1>
  8.         <ul role="nav">
  9.           <li><Link to="/about">About</Link></li>
  10.           <li><Link to="/repos">Repos</Link></li>
  11.         </ul>
  12.       </div>
  13.     )
  14.   }
  15. })
复制代码

  About 和 Repos 页面返回一个 React Component ,这样子可以访问通过主页访问这两个页面。可以参考 reactjs 的 github 源文件 。

  浏览器历史记录

  前面配置的路由是 hash,使用的是 react-router 的 hashHistory,而并非 H5 的浏览器历史记录。

  那么,如何使用浏览器的历史记录呢。万能的 react-router 提供了 browserHistory ,把 hashHistory 替换成 browserHistory 即可:
  1. import { Router, Route, browserHistory, IndexRoute } from 'react-router'

  2. render((
  3.   <Router history={browserHistory}>
  4.     {/* ... */}
  5.   </Router>
  6. ), document.getElementById('app'))
复制代码

  在 webpack-dev-server 中需要添加参数 --history-api-fallback ,但因为 webpack-dev-server 是一个轻量级的服务器,如果使用可能还需要用 Express 这样的服务器,前面也介绍了如何配置。配置好之后,就可以直接通过 url: 127.0.0.1:8080/about 来访问这个关于页面。

  为这个前端路由添加功能
现在路由的功能是有了,要达到效果,还是需要进一步美化的,在美化的过程中,还是可以学到一些知识的。

  目前这个路由很乱,我们需要重新建立一个 Routes 文件,用来作为路由文件,以后修改的时候,也方便一些,毕竟我们是用 webpack 打包,文件多,不用怕!

  修改根目录下 index.js 文件:
  1. import routes from './modules/routes'

  2. render(
  3.   <Router routes={routes} history={browserHistory}/>,
  4.   document.getElementById('app')
  5. )
复制代码

  在 modules 目录下添加 routes.js 文件,其实就是把之前 index.js 少的东西复制过去:
  1. module.exports = (
  2.   <Route path="/" component={App}>
  3.     <IndexRoute component={Home}/>
  4.     <Route path="/repos" component={Repos}>
  5.       <Route path="/repos/:userName/:repoName" component={Repo}/>
  6.     </Route>
  7.     <Route path="/about" component={About}/>
  8.   </Route>
  9. )
复制代码

  这里呢,我们在 repos 下又添加了一个 repo 文件,用来读取 pathname,算是一个小扩展吧,使用上需要对路径名前加个冒号,表示调用,然后在 repo.js 文件:
  1. import React from 'react'

  2. export default React.createClass({
  3.   render(){
  4.     return (
  5.       <div>
  6.         // 这里通过 params 获取定义的路径名
  7.         <h2>{this.props.params.repoName}</h2>
  8.       </div>
  9.     )
  10.   }
  11. })
复制代码

  还可以向路由中动态添加 router,
  1. React.createClass({
  2.   contextTypes:{
  3.     router: React.PropTypes.object
  4.   },

  5.   handSubmit(event){
  6.     event.preventDefault();
  7.     const userName = event.target.elements[0].value
  8.     const repo = event.target.elements[1].value
  9.     const path = `/repos/${userName}/${repo}`
  10.     console.log(path)
  11.     this.context.router.push(path)
  12.   },
  13.   render(){
  14.     <form onSubmit={this.handSubmit}>
  15.       <input type="text" placeholder="userName"/> / {' '}
  16.       <input type="text" placeholder="repo"/>{' '}
  17.       <button type="submit">Go</button>
  18.     </form>
  19.   }
  20. })
复制代码

相关帖子

发表于 2017-1-4 15:22:30 | 显示全部楼层
经常看到”泡泡兔“发帖,辛苦了
使用道具 举报

回复

发表于 2017-1-5 08:37:32 | 显示全部楼层
其实js如果遇到web安全类的项目,那就又要做牺牲了,无法读写文件,访问受限。。。哎。。。
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部