版权声明:本文为博主原创文章,未经博主容许不得转载。javascript
PS:转载请注明出处 做者:TigerChain 地址:http://www.jianshu.com/p/b55cf53e633a 本文出自 TigerChain简书css
React 教程系列html
教程简介前端
本篇教程适合初学者,老鸟直接略过,若是有误,欢迎指出,谢谢。java
二、教程难度node
初级react
三、Demo 地址:github.com/githubchen0…webpack
四、正文git
路由是什么咱们可能不太理解,可是我说一个东西咱们必定知道,就是"路由器",路由器的功能用一句话归纳就是:数据从一个网络到另外一个网络就是靠路由来完成的[固然路由器的功能不只仅于此]。github
咱们说的程序开发中的路由不是指路由器和网络协议中的路由,可是基本思想是同样的。而路由又能够分为前端路由和后端路由。
咱们来看一个路由的简易图吧,有了这个图,你们对路由就有一个大体的了解了。
一、后端路由
举个栗子,分配一个站点,服务器地址是: http://192.168.1.200:8080 ,在这个网站中提供了三个界面,分别是:
http://192.168.1.200:8080/index.html
http://192.168.1.200:8080/about/aboutus.html
http://192.168.1.200:8080/feedback.html
复制代码
当咱们在浏览器中敲入 http://192.168.1.200:8080/index.html 来访问页面时,web 服务器接收到这个请求,而后把 /index.html 解析出来,而后找到 index.html 并展现出来,这就是路由分发,路由的分发是经过路由来完成的。
二、前端路由
虽然前端路由和后端路由在实现技术上差异,可是原理都 TM 的同样。在 H5 的 history Api 以前,前端的路由功能都是使用经过 hash「散列值得」 来实现的。 hash 能兼容低版本的浏览器。好比:
http://192.168.1.200:8080/#/index.html
http://192.168.1.200:8080/#/about/aboutus.html
http://192.168.1.200:8080/#/feedback.html
复制代码
因为 web 服务不会解析 # 后面的东西,但是 js 是能够拿到 # 后面的东西的,有一个方法就是 window.location.hash 来读取,经过这个方法来匹配不一样的功能上。
PS 咱们进一步来讲,举个例子,有一个地址为:
http://www.xxx.com/path/a/b/c.html?key1=Tiger && key2=Chain && key3=fuck#/path/d/e.html
复制代码
http:协议
www.xxx.com:域名
/path/a/b/c.html:路径,即服务器上的资源
?key1=Tiger && key2=Chain && key3=fuck:这个很好理解 Get 请求的参数
#/path/d/e.html:hash 也叫散列值,也叫锚点
复制代码
上面的 hash 是和浏览器交互的,其它的都是和服务器进行交互的。
先看咱们要完成的效果:
咱们看到了 hash 的改变不会致使浏览器的刷新,而且咱们使用原生的 JS 定义了一个简单的 SPA 「单页应用」。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title> HashDemo </title>
</head>
<body>
<h3>我是 Hash 的一个 Demo</h3>
<hr />
<a href="#hash1">#hash1</a>
<a href="#hash2">#hash2</a>
<a href="#hash3">#hash3</a>
<a href="#hash4">#hash4</a>
<p/>
<div id = "show-hash-result" style="color:blue">
点击上面连接,并观察浏览器
</div>
<h4>自定义一个简单的单面路由页</h4>
<div id="nav">
<ul>
<li><a href="#/index.html">首页</a></li>
<li><a href="#/server">服务</a></li>
<li><a href="#/mine">个人</a></li>
</ul>
</div>
<div id="result"></div>
<style> #nav { margin: 0; border: 0; height: 40px; width: 300px; border-top: #060 2px solid; margin-top: 10px; border-bottom: #060 2px solid; background-color: #690; } #nav ul { margin: 0; border: 0; list-style: none; line-height: 40px; } #nav li { display: block; float: left; } #nav a { display: block; color: #fff; text-decoration: none; padding: 0 20px; } #nav a:hover { background-color: #060; } </style>
<script type="text/javascript"> window.addEventListener("hashchange", function(){ //变化后输出当前地址栏中的值 document.getElementById("show-hash-result").innerHTML = location.hash; console.log(window.location.hash) ; }); </script>
<script type="text/javascript"> //自定义一个路由规则 function CustomRouter(){ this.routes = {}; this.curUrl = ''; this.route = function(path, callback){ this.routes[path] = callback || function(){}; }; this.refresh = function(){ this.curUrl = location.hash.slice(1) || '/'; if(this.curUrl.indexOf('/')!=-1){ //这里粗略的把 hash 过滤掉 this.routes[this.curUrl](); } }; this.init = function(){ window.addEventListener('load', this.refresh.bind(this), false); window.addEventListener('hashchange', this.refresh.bind(this), false); } } //使用路由规则 var R = new CustomRouter(); R.init(); var res = document.getElementById('result'); R.route('/hash1',function () { document.getElementById("show-hash-result").innerHTML = location.hash; }) R.route('/index.html', function() { res.style.height='150px'; res.style.width='300px'; res.style.background = 'green'; res.innerHTML = '<html>我是首页</html>'; }); R.route('/server', function() { res.style.height='150px'; res.style.width='300px'; res.style.background = 'orange'; res.innerHTML = '我是服务页面'; }); R.route('/mine', function() { res.style.background = 'red'; res.style.height='150px'; res.style.width='300px'; res.innerHTML = '个人界面'; }); </script>
</body>
</html>
复制代码
代码没有整理和抽取,感兴趣的能够再深层次的去改 「改到你满意为止」。
window 的 history 提供了对浏览器历史记录的访问功能,而且它暴露了一些方法和属性,让你在历史记录中自由的前进和后退,而且在 H5 中还能够操做历史记录中的数据。
history 的 API 以下:
interface History {
readonly attribute long length;
readonly attribute any state;
void go(optional long delta);
void back();
void forward();
//h5 引进如下两个方法
void pushState(any data, DOMString title, optional DOMString? url = null);
void replaceState(any data, DOMString title, optional DOMString? url = null);
};
复制代码
back()
:在历史记录是后退history.back() ;
复制代码
forward()
:在历史记录中前进history.forward();
复制代码
go()
:移动到指定的历史记录点history.go(-1)
复制代码
length
:为 history 的属性,显示 history 的长度关于 h5 的 history 推荐看这里,详细的讲解了 history,很是值得的看。
这里重点说 h5 的 pushState,彻底代替 hash 而且更优雅。
废话很少说,直接上效果图
以上咱们实现了一个很是简单的 SPA ,咱们能够看到咱们使用 histoy pushState 和 onpopstate 实现和上面 hash 同样的功能。
源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title> H5 HistoryDemo </title>
</head>
<body>
<ul>
<li><a onclick="home()">首页</a></li>
<li><a onclick="about()">关于</a></li>
<li><a onclick="mine()">个人</a></li>
</ul>
<div id="showContent" style="height:250px;width:200px;background:green">home</div>
<script> function home() { history.pushState({name:'home',id:1}, null, "?page=home"); showCard("home"); } function about() { history.pushState({ id:2, name:"about" },null,"?page=about") ; showCard("about"); } function mine() { history.pushState({ id:3, name:"mine" },null,"?name=chen&age=30") ; showCard("mine"); } function showCard(name) { document.getElementById("showContent").innerHTML = name; } //点击返回键盘的时候显示历史 window.onpopstate = function (event) { var content = ""; if(event.state) { content = event.state.name; } showCard(content); } </script>
</body>
</html>
复制代码
PS:基于 h5 history 实现的路由和最初的路由基本上一致。
http://192.168.1.200:8080/index
http://192.168.1.200:8080/about/aboutus
http://192.168.1.200:8080/feedback
复制代码
从性能上来讲,后端路由每次向 server 发起一个请求,server 要解析响应,就会有延时,而前端路由只是变换了一下路径,没有网络延时这一说,因此性能「用户体验」是大大的提高了。
通过以上咱们对路由有了一个大致的理解,咱们接下来讲咱们的重点 React 的路由功能。
React 官方没有给出一个明确的组件,推荐使用三方的一个叫 React Router 的组件。「固然咱们不使用 React Router 也能够完成路由功能,好比传统的 hash 功能,没有问题,可是用了它就很是方便和好用」。
咱们都是知道 React 玩的就是组件化,因此 React Router 也是一个组件。
一、废话很少说,咱们直接写一个简单的 SPA Demo 感觉一下
效果以下:
从图能够看到咱们使用 React router 实现一个简单的 SPA 应用。若是你们细心看了前面的知识点,这个没有什么好说的。
二、下面咱们撸码,经过代码来直观的感觉一下 「此次我使用的是 windows 开发,不过不影响,你能够直接在 mac 开发」。
好比:
D:\study\react-study\lesson03\reactrouter\reactrouterdemo
复制代码
进入到 reactrouterdemo 目录中建立项目
yarn init // 一路回车便可
yarn add react react-dom webpack webpack-dev-server babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0 --dev //安装依赖的插件,不清楚,能够看 webpack 这一节
复制代码
而后每一个文件中对应的内容
.babelrc
# .babelrc
{
"presets": ["react", "es2015","stage-0"]
}
复制代码
webpack.config.js
# webpack.config.js
module.exports = {
entry: __dirname + "/app/main.js",//已屡次说起的惟一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
},
devServer:{
contentBase: "./public",//本地服务器所加载的页面所在的目录
// colors: true,//终端中输出结果为彩色
historyApiFallback: true,//不跳转
inline: true//实时刷新
},
//新增长部分
module:{
loaders:[
//babel配置
{
test:/\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
}
复制代码
index.html
# index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ReactRouter Demo</title>
</head>
<body>
<div id="container"></div>
<script src="bundle.js"></script>
</body>
</html>
复制代码
这没有什么好说的,若是看过 webpack 等章节,咱们就很快的建立这些东西。
接下来咱们安装 react-router
yarn add react-router --dev
复制代码
# main.js
import React from 'react' ;
import ReactDOM from 'react-dom' ;
import { Router, Route, Link ,hashHistory} from 'react-router';
/**
* 定义 Home 组件
* @type {String}
*/
class Home extends React.Component{
render(){
return(<div>
Home
</div>) ;
}
}
/**
* 定义 About 子页面组件
* @type {String}
*/
class About extends React.Component{
render(){
return(
<div>About</div>
) ;
}
}
class App extends React.Component {
render() {
return (
<div>
<h1>App</h1>
<ul>
<li><Link to="/home">首页</Link></li>
<li><Link to="/about">关于我</Link></li>
</ul>
</div>
);
}
}
ReactDOM.render((
<Router>
<Route path="/" component={App}/>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</Router>
), document.getElementById('container'));
复制代码
最后配置 package.json 文件的脚本,添加下面一句。
package.json
"scripts": {
"start": "webpack-dev-server --progress --port 8888"
}
复制代码
好了,到此为止咱们基本上写完一个简单的 SPA 页面了,让咱们来验证一下吧,咱们在命令行中输入 yarn start,一切 ok ,咱们打开浏览器输入 localhost:8888,若是没有什么大问题,你毛也看不到,这个时候不要慌,打开 chrome 调试工具 「别给我说你没有使用 chorme 调试 web 页面,若是没有本身去面面壁」。结果以下
What The Fuck ,什么鬼吗,按照文档 「这个翻译文档过期了」来的竟然出错了,好吧,解决问题,把上面框出来的问题到 Google 上一搜,答案在这里,来吧上一张图
大致意思是说,若是你使用的 react-router v4 版本 「上一个稳定版本是 2.8.1」,那么就要使用 react-router-dom,而且这是 v4 版本中的更新 「 v4 中有不少更新」。啥是react-router-dom 呢,你只要知道通常咱们前端就用这个包来进行WEB开发了,这一篇文章详细的介绍了,也就是使用 react-router-dom 彻底能够替代 react-router 。
PS: 若是你的项目中已经在使用react-router以前的版本,那必定要慎重的更新,由于新的版本是一次很是大的改动,若是你要更新,工做量并不小。关于 React-router 2.8 左右的版本能够看阮一峰的:www.ruanyifeng.com/blog/2016/0… 文章,很是好。
上面说了,react-router-dom 能够迭代 react-router ,那么咱们先把 react-router 卸载了,而后安装 react-router-dom
yarn remove react-router //卸载 react-router
yarn add react-router-dom --dev
复制代码
而后修改 main.js,这里只修改一句话:
将 mport { Router, Route, Link } from 'react-router'; 修改成
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
复制代码
而后看结果,仍是出不来,看 chorme 的输出台,这下变了,不是上面的错误了,而是
大概意思就是 标签只能有一个子元素,这好办,加一个 div 不就搞定了。
修改地方是 :
而后再看结果,神奇般出来了咱们开头的效果,真不容易呀。
总结:咱们在开发 web 页面的时候就使用 react-router-dom 来代替之前的 react-router 就能够了。他们的用法稍微有不一样:
以上咱们就简单的开发一个 React-router 的 SPA ,咱们对 React-router v4 也有了一个大概的解,下面咱们具体说说 React-router v4 的一些用法吧。
到此为止,咱们对 react router 有一个大致的认识,下一节们再详细的说说 react-router v4 的一些用法。
一、react-router v4 没有嵌套之用法了
二、传递参数「url 传参」
通过上面的了解咱们知道,React-router v4 中,咱们在 Router 组件中能够写任意的标签,可是只能有一个根标签。
url 传参,咱们使用通配符 :id ,下面咱们结合代码一块儿看看。
# main.js
import React from 'react' ;
import ReactDOM from 'react-dom' ;
import {
BrowserRouter as Router,
Route,
HashRouter,
Link
} from 'react-router-dom';
/**
* 定义 Home 组件
* @type {String}
*/
class Home extends React.Component{
render(){
return(<div>
Home
</div>) ;
}
}
/**
* 定义 About 子页面组件
* @type {String}
*/
class About extends React.Component{
render(){
return(
<div>About</div>
) ;
}
}
class App extends React.Component {
render() {
return (
<div>
<h1>App</h1>
<ul>
<li><Link to="/home">首页</Link></li>
<li><Link to="/about">关于我</Link></li>
<li><Link to="/haha">haha</Link></li>
</ul>
</div>
);
}
}
// 在 react-router v4.0 中获取 接收参数使用 this.props.match.params.属性名字
class Haha extends React.Component{
render(){
return(
<div>
<h3> ID: {this.props.match.params.id}</h3>
match.url: {this.props.match.url}
</div>
) ;
}
}
// 无状态的组件
// const Haha = ({ match }) => (
// <div>
// <h3>ID: {match.params.id}</h3>
// </div>
// )
ReactDOM.render((
<HashRouter>
<div>
<Route path="/" component={App}/>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Route path="/:id" component={Haha}/>
</div>
</HashRouter>
), document.getElementById('container'));
复制代码
经过上图咱们能够看到 :id 匹配的是 home,about,haha,咱们再看 chorme 调试工具,很清楚的看到 li 标签中的 link 其实就是一个 a 标签,to 就至关于 href 属性。
细习的朋友可能发现了,我这里使用 HashRouter 代替了 Router,那么这个有什么做用呢,答案就是在上面的运行结果中,咱们看到点击首页等连接的时候,地址栏中自动加了一个 # ,也就说 HashRouter 是前面的说的 hash 的方式实现路由。
如何取得 id 的参数呢,咱们经过
his.props.match.params.id
复制代码
来取得,这里的 id 就是你 :id 中的 id ,名字能够本身改。
一、本实例咱们基于 ant design 来仿一下 ant design「如下简称 antd」 的网站「体验一下 spa 应用中 react-router的应用,本次选用 react-router@2.8.1 来开发」
对 ant design 不了解的能够看看官方:ant.design/index-cn,它是一个 UI 设计语言相似于 bootstrap ,而且它和 react 无缝结合
首先咱们来看一下 ant design 的官网界面以下:
再来看看,咱们仿的界面:
怎么样,大致上仍是很像的吧,固然我只是为了演示 react-router 在 spa 应用中的应用,仿的确定和原生没有百分百同样「css 若是要作的好,就要花费大量的时间和精力,本人的 css 能力仍是很薄弱的」
二、好我废话很少,咱们直接开始撸代码吧「在 mac 环境下,win 下是同样的」
mkdir antddemo
cd antddemo
yarn init
复制代码
而后一路回车便可
├── index.html
├── package.json
├── src
│ ├── imgs
│ ├── js
│ │ ├── component
│ │ └── index.js
│ └── styles
├── webpack.config.js
├── yarn-error.log
└── yarn.lock
复制代码
PS: index.js 就咱们的入口 js 文件,imgs 用来存放本地图片,styles 用来放 css 文件,component 用来存放编写的 react 组件的
yarn add react react-dom //回车,继续安装
yarn add babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0 webpack webpack-dev-server style-loader url-loader css-loader antd react-hot-loader react-router@2.8.1 --dev
复制代码
而后回车没有什么问题即就显示安装成功,我这里一口气所须要的依赖都安装好了,你也能够分别来安装。
{
"presets":["react","stage-0","es2015"],
"plugins": [
["import", { "libraryName": "antd", "style": "css" }]
]
}
复制代码
其中的 plugin 是配置 antd 的,具体能够看官网,这里大概说一下,根据前面的经验咱们知道一个 css 样式是能够被 import 进来的,若是咱们添加了 css-loader 和 style-loader 的话,若是 antd 想被 import 引入来使用的话,咱们还须要安装一个插件 babel-plugin-import「也是官司方推荐的」,具体能够看官方的按须要加载这一小结:ant.design/docs/react/…
下面咱们安装 babel-plugin-import
yarn add babel-plugin-import --dev
复制代码
ps:.babelrc 里面的 plugin 就是 配置 babel-plugin-import 这玩意的
# webpack.config.js
module.exports = {
entry: __dirname + "/src/js/index.js",
output: {
path: __dirname + "/src/", //打包后文件地址
publicPath:"/src/", //命令行模式下,必定要配置output.publicPath来指定编译后的包(bundle)的访问位置.
filename: "bundle.js" //打包后文件
},
module: {
loaders: [
//babel配置
{
test:/\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
// 为了避免和 antd css 样式冲突,咱们
{
test: /\.css$/,
loader: 'css-loader?sourceMap&modules&localIdentName=[local]___[hash:base64:5]!!',
exclude: /node_modules/
},
// style-loader css-loader 配置
{
test: /\.css$/,
loader: 'style-loader!css-loader'
},
//加载本地图片 配置 url-loader
{test:/\.(png|jpg)$/,loader:"url-loader?limit=8192&name=img/[name][hash:8].[ext]"
}
]
},
devServer:{
hot:true,
historyApiFallback: true,//不跳转
inline: true//实时刷新
}
}
复制代码
PS:这里要注意一点,由咱们安装了 css-loader 和 antd ,咱们须要对 antd 单独配置一下,就是把 node_modules 下的文件都 exclude 掉,不要让它走 css-modules,不然 css-loader 会把 antd 的 css 样式所有冲掉「你引入的 antd 样式死活都加载不出来,切记、切记、切记」,具体能够看这里:github.com/ant-design/…
以上咱们只是搭建了一个架子,那么既然 react 牛 b 的地方就是组件化,咱们就把 antd 网页划分一下组件吧,由 antd 官网咱们能够看到,上面底部和底部是不变的,咱们能够分别抽出一个组件来,具体组件划分以下:
由上图咱们看到 antd 大致来讲就分为这四个组件。并用只有内容区域是变化的,其它组件不变
# index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="src/styles/pc.css">
<link rel="stylesheet" href="src/styles/main.css">
<link rel="stylesheet" href="src/styles/common.css">
</head>
<body>
<div id="container"></div>
<script type="text/javascript" src="./src/bundle.js"> </script>
</body>
</html>
复制代码
和咱们之前的 index.html 基本上没有什么区别,只不过咱们引入几个 css 样式而已
# index.js
import React,{Component} from 'react' ;
import ReactDOM from 'react-dom' ;
// 这里咱们使用 react-router 2.8.1
import {hashHistory,Router,Route,IndexRoute,Link} from 'react-router' ;
//引入 pc 主界面组件
import PCIndex from './component/pc_index.js' ;
import AntdofReact from './component/antdofreact.js' ;
// 引入快速上手组件
import QuickStart from './component/quick_start.js' ;
//引入项目实践组件
import ProjectPractice from './component/projectpractice.js' ;
//引入在 react 中使用组件
import UseInReact from './component/useinreact.js' ;
//引入自定义主题组件
import CustomTheme from './component/customtheme.js' ;
//引入更新日志组件
import UpDateLog from './component/updatelog.js' ;
class Main extends React.Component {
render(){
return(
// 定义路由
<Router history={hashHistory}>
<Route path="/" component={PCIndex}>
<IndexRoute component={AntdofReact}></IndexRoute>
<Route path="react/getting-started-cn" component={QuickStart}></Route>
<Route path="react/practical-projects-cn" component={ProjectPractice}></Route>
<Route path="react/use-with-create-react-app-cn" component={UseInReact}></Route>
<Route path="react/customize-theme-cn" component={CustomTheme}></Route>
<Route path="react/changelog-cn" component={UpDateLog}></Route>
</Route>
</Router>
) ;
}
}
ReactDOM.render(
<Main />,document.getElementById('container')
) ;
复制代码
在这里咱们路由规则都定定出来了。
import React, { Component, PropTypes } from 'react';
// 导入头部组件
import PCHeader from './pc_header.js' ;
//导入主体内容组件
import PCMAIN from './pc_main.js' ;
//导入底部组件
import PCFooter from './pc_footer.js' ;
//首页 包含公共的头部和底部
export default class PCIndex extends Component {
constructor(props) {
super(props);
}
render() {
//取得 PCIndex中的子组件 下句就至关于 const children = this.props.children
const {children} = this.props
return (
<div> <PCHeader /> <PCMAIN maincontent={children}/> <PCFooter /> </div> ); } } PCIndex.propTypes = { }; 复制代码
咱们拿一个图来直观的看一下 pc_index.js 组件的组合做用
import React, { Component, PropTypes } from 'react';
import PCMenu from './pc_menu.js' ;
// 内容组件
export default class PCMain extends Component {
constructor(props) {
super(props);
}
render() {
const {maincontent} = this.props;
return (
<main className="main"> {/* 菜单 */} <div className="menu"> <PCMenu /> </div> {/* 内容 */} <div className="content"> {maincontent} </div> </main>
);
}
}
PCMain.propTypes = {
};
复制代码
pc_main.js 组件也就是组合了菜单组件,和内容「也能够定义成一个组件,本身试试吧,React 中一切皆组件」
此时变成这样了,以下图所示:
好了,以上就是核心代码,若是一个个 js 去说没有什么意义,浪费时间,能够自行下载 demo 体验一下
PS:后记,咱们仿 antd 网站使用是的 React-router v2.8 来作的「让你们体验一下"曾今"(固然如今也很流行)流行的路由」,后面抽出时间,改为 React-router v4 版本。
demo地址:github.com/githubchen0…
听说每一个勤奋、努力、想成为牛 b 的人会点一个喜欢的