路由配置
在开发中如果你接触到node或者vue那肯定听到过路由的概念,如果对这个概念理解不够本节就继续给大家讲解路由的概念,以及react中路由的配置和使用。
路由器在生活中经常出现,路由器的功能用一句话概括就是:数据从一个网络到另一个网络就是靠路由来完成的[当然路由器的功能不仅仅于此]。
我们说的程序开发中的路由不是指路由器和网络协议中的路由,但是基本思想是一样的。而路由又可以分为前端路由和后端路由。
接下来我们用一个流程图来给大家介绍路由的概念:
后端路由
在学习nodejs开发的时候我们就自己封装过路由,将不同的资源和不同的访问地址对应起来,通过路由来进行分发。比如我们的访问地址为:
http://example:com/index.html
http://example:com/details/show.html
http://example:com/login
当我们访问http://example:com/index.html地址的时候,服务器根据这个地址将请求分发给对应的程序来处理。
router.get('/', function(req, res, next) {
console.log("主页", req.session);
res.render('index', { title: 'Express' });
});
router.post('/login', function (req, res, next) {
const obj = req.body;
res.send({success:isLogin,msg:"登录成功"});
});
不同的请求地址对应不同的路由解析。
前端路由
前端路由和后端路由的实现原理是差不过,实现技术上有一些差别,H5 的 history Api 之前,前端的路由功能都是使用通过 hash「散列值得」 来实现的。 hash 能兼容低版本的浏览器。比如:
http://example:com/#/index.html
http://example:com/#/web/show.html
http://example:com/#/java.html
由于 web 服务不会解析 # 后面的东西,可是 js 是可以拿到 # 后面的东西的,有一个方法就是 window.location.hash 来读取,通过这个方法来匹配不同的功能上。
浏览器访问地址:
http://example:com/#/home
js代码解析
window.location.hash ----> #home
通过hash获取到的就是#后面的访问路径。这个hash只会和浏览器进行交互,不会发送给服务器。
接下来我们通过hash的方式来自己设计一个路由。
<h3>自定义路由测试</h3>
<nav>
<ul>
<li><a href="#hash1">#hash1</a></li>
<li><a href="#hash2">#hash2</a></li>
<li><a href="#hash3">#hash3</a></li>
<li><a href="#hash4">#hash4</a></li>
</ul>
</nav>
<div id="myhash" style="color:blue">
显示hash的地方
</div>
页面布局设计,我们有4个超链接,点击过后分别跳转不同的地址。
<script type="text/javascript">
window.addEventListener("hashchange", function() {
//变化后输出当前地址栏中的值
document.getElementById("myhash").innerHTML = location.hash;
console.log(window.location.hash);
});
</script>
我们给window对象绑定了一个hashchange事件,只要页面里面的值发生变化我们就可以获取到hash值显示到容器里面。
自己来完成一个导航路由的切换
-
定义导航的样式
<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>
在上面的路由里面,每个超链接的href就要跳转的地址。
-
封装路由对象,完成函数的设计
//自定义一个路由规则 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); } }
在CustomRouter对象中,我们定义了route函数用于匹配访问路径和函数,后续要设置路由那我们就可以调用route将访问的路由和执行的函数绑定在一起。
-
实现路由的切换
//使用路由规则 var R = new CustomRouter(); R.init(); var res = document.getElementById('result'); R.route('/index.html', function() { res.style.height = '150px'; res.style.width = '300px'; res.style.background = 'green'; res.innerHTML = '首页'; }); 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 = '更多服务'; });
首先创建一个路由对象,调用route方法,将匹配的路径和function传递过去。一旦访问的地址和route里面定义的一致,那就执行function函数完成逻辑处理。
除了上面的hash可以实现路由的跳转和设计,window 的 history也 提供了对浏览器历史记录的访问功能,并且它暴露了一些方法和属性,让你在历史记录中自由的前进和后退,并且在 H5 中还可以操作历史记录中的数据。
常见的方法如下:
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);
其中pushState可以改变网址(存在跨域限制)而不刷新页面,这个强大的特性后来用到了单页面应用如:vue-router,react-router-dom中。
注意:仅改变网址,网页不会真的跳转,也不会获取到新的内容,本质上网页还停留在原页面!
语法为:
window.history.pushState(data, title, targetURL);
@状态对象:传给目标路由的信息,可为空
@页面标题:目前所有浏览器都不支持,填空字符串即可
@可选url:目标url,不会检查url是否存在,且不能跨域。如不传该项,即给当前url添加data
接下来重点说 h5 的 pushState,完全代替 hash 并且更优雅。
-
先定义布局结构
<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>
每个超链接就是一个导航,点击后进行模块加载。
-
路由脚本的设计
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); }
popstate事件会在点击后退、前进按钮(或调用history.back()、history.forward()、history.go()方法)时触发。当点击每一个链接过后我们执行对应的函数完成逻辑跳转。
react路由
React 官方没有给出一个明确的组件,推荐使用三方的一个叫 React Router 的组件。「当然我们不使用 React Router 也可以完成路由功能,比如传统的 hash 功能,没有问题,但是用了它就非常方便和好用」。
React Router也是一个组件,在代码中引入来完成路由跳转。接下来我们就基于 React Route来完成一个简单的SPA应用。访问地址为:https://reactrouter.com/web/api/Redirect
-
先下载路由
npm install react-router-dom yarn add react-router-dom
以上两种方式都可以在项目中下载路由。
-
在项目中引入react-router-dom来完成,打开App.jsx组件
import {Route, Link, BrowserRouter, Switch,Redirect } from 'react-router-dom'; import Home from "./component/Home" import About from "./component/About"
在项目中引入react-router-dom,并将需要的组件结构出来。比如Route,Link,BrowserRouter等等,在后续我们会详细给大家介绍这些组件的作用。
-
在render方法里面设置路由跳转链接
render(){ return ( <BrowserRouter> <div> <h1>App</h1> <ul> <li><Link to="/home">首页</Link></li> <li><Link to="/about">关于我</Link></li> </ul> </div> </BrowserRouter> ) }
在上面的代码中我们在render方法里面定义了一个BrowserRouter组件,这个一个路由器,必须放在最外面,在路由器里面我们使用Link标签来定义了导航的链接。其实在页面上最终渲染出来的结果就是两个a标签。
点击超链接后我们要进行相应的路由跳转,接下来就配置路由匹配组件
<BrowserRouter> <div> <h1>App</h1> <ul> <li><Link to="/home">首页</Link></li> <li><Link to="/about">关于我</Link></li> </ul> </div> <Switch> <Redirect exact from="/" to="/home"></Redirect> <Route path={"/home"}> <Home/> </Route> <Route path={"/about"}> <About/> </Route> </Switch> </BrowserRouter>
Switch是路由匹配组件,就类似于js中的switch语句,进行判断,你浏览器访问的地址如何和Route的path属性重叠,那我们就可以进行路由匹配,加载对应的组件。
其中Redirect组件代表重定向,如果用户第一次访问的路径为/,那就默认定位到/home路径。exact关键字代表精确匹配,这个不能省略,不然你无法匹配到Route。
页面加载完毕后,你默认看法哦的访问结果就是/home,这是因为我们有重定向操作。
案列我们演示完毕后,接下来就要给大家去解析以下我们刚刚用到的组件分别有什么作用,React Router中的组件主要分为三类:
1. 路由器,例如<BrowserRouter>和<HashRouter>
2. 路由匹配器,例如<Route>和<Switch>
3. 导航,例如<Link>,<NavLink>和<Redirect>
路由器
每个React Router应用程序的核心应该是路由器组件。对于Web项目,react-router-dom提供和路由器。两者之间的主要区别在于它们存储URL和与Web服务器通信的方式。我们来列举以下区别:
- 使用常规URL路径。 这些通常是外观最好的网址,但它们要求您的服务器配置正确。 具体来说,您的Web服务器需要在所有由React Router客户端管理的URL上提供相同的页面。Create React App在开发中即开即用地支持此功能,并附带有关如何配置生产服务器的说明。
- 将当前位置存储在URL的哈希部分中,因此URL看起来类似于http://example.com/#/your/page。 由于哈希从不发送到服务器,因此这意味着不需要特殊的服务器配置。
要使用路由器,只需确保将其渲染在元素层次结构的根目录下即可,比如我们上面的案列中,在App.jsx组件里面我们先加载了BrowserRouter组件。
<BrowserRouter>
<Switch>
<Redirect exact from="/" to="/home"></Redirect>
<Route path={"/home"}>
<Home/>
</Route>
<Route path={"/about"}>
<About/>
</Route>
</Switch>
</BrowserRouter>
也就说你要使用Switch,Route这些组件,那就必须在外面包含BrowserRouter路由器。
当然我们也可以使用HashRouter来实现路由跳转
render() {
return (
<div>
<HashRouter>
<ul>
<li><Link to="/home">首页</Link></li>
<li><Link to="/about">关于我</Link></li>
</ul>
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
</Switch>
</HashRouter>
</div>
)
}
使用了HashRouter来控制路由跳转,在页面上显示的效果如下:
在浏览器地址里面,你会发现有#的存在。证明我们的路由就是使用了hash模式。
路径匹配器
有两个路径匹配组件:Switch和Route。渲染
如果没有匹配,则
Switch在匹配的过程中需要注意,一旦匹配成功那就不会在继续匹配后面的路由,比如:
<Switch>
<Route path="/" component={Home}></Route>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
</Switch>
如果我们的路径为:http://127.0.0.1:3000/about那匹配到的组件永远是Home,因为这个路径已经匹配成功,后续就不再匹配。从这个结果中我们也能发现Switch是模糊匹配。如果要精确匹配到指定的路由组件,我们可以如下操作:
-
将常的路由放在最前面
<Switch> <Route path="/home" component={Home}></Route> <Route path="/about" component={About}></Route> <Route path="/" component={Home}></Route> </Switch>
这样就能先匹配/home在匹配/about最后在匹配/
-
当然我们也可以在Route上面加上关键字exact来代表精确匹配
<Switch> <Route exact path="/" component={Home}></Route> <Route path="/home" component={Home}></Route> <Route path="/about" component={About}></Route> </Switch>
以上就是我们的Switch匹配规则。
对于Route来说,我们使用他来匹配我们想要的路径,要渲染的组件可以有两种方式
方式一:
<Route exact path="/" component={Home}></Route>
方式二:
<Route exact path="/" >
<Home/>
</Route>
两种方式都可以加载组件。
导航
React Router提供了一个组件来在您的应用程序中创建链接。 无论在何处渲染,锚点都将渲染在HTML文档中。
是的一种特殊类型,当其prop与当前位置匹配时,可以将其自身设置为“active”。最终导航都被渲染为超链接;
我们在代码中使用NavLink来设置导航,并添加默认样式:
<HashRouter>
<ul>
<li><NavLink to="/home" activeClassName="hurray">React</NavLink></li>
<li><NavLink to="/about" activeClassName="hurray">关于我</NavLink></li>
</ul>
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
</Switch>
</HashRouter>
NavLink提供了一个activeClassName属性,对应的值是一个字符串,这是一个class选择器,你可以自己添加样式
.hurray {
color: red;
}
带来的效果就是你被选中的导航默认会显示为红色,点击切换颜色也会变化。
组件就是设置重定向的组件,你可以指定一个匹配路径,在定义一个要默认跳转的路径.
<Redirect exact from="/" to="/home"></Redirect>
代表如果请求路径是/那就默认跳转到/home路径.
当然我们在代码中,如果要设置代码跳转,除了使用Link或者NavLink以外,我们还可以自定义脚本来实现.
import {withRouter,BrowserRouter, Switch} from 'react-router-dom'
import React from "react"
class WithRouterComp extends React.Component{
forward = ()=>{
//经过widthRouter包装之后,该组件就具有了history属性
// this.props.history.push('/login');
console.log(this.props);
}
render(){
return(
<div>
<button onClick={this.forward}>点我跳转到登录</button>
</div>
)
}
}
export default WithRouterComp
高阶组件中的withRouter, 作用是将一个组件包裹进Route里面, 然后react-router的三个对象history, location, match就会被放进这个组件的props属性中.
基本的实现原理如下:
// withRouter实现原理:
// 将组件包裹进 Route, 然后返回
// const withRouter = () => {
// return () => {
// return <Route component={Nav} />
// }
// }
// 这里是简化版
const withRouter = ( Component ) => () => <Route component={ Component }/>
我们可以在react中使用button,span等等标签来进行跳转.
react路由懒加载
基于组件来开发,我们在使用路由完成加载的组件的时候目前是一次性就将跟组件里面需要引用的组件加载进来.接下来我们基于案列来演示以下:
-
定义Main组件,加载其他子组件
import Home from "./Home" import About from "./About" export default class Main extends Component { render() { return ( <BrowserRouter> <div> <h1 className="active">App</h1> <ul> <li><Link to="/home">首页</Link></li> <li><Link to="/about">关于我</Link></li> </ul> </div> <Switch> <Route path={"/home"}> <Home/> </Route> <Route path={"/about"} component={About}> {/* <About/> */} </Route> </Switch> </BrowserRouter> ) } }
在上面代码中,我们直接引入Home组件和About组件在启动就会加载。
-
子组件的内容
import "../css/fundsMarket.css" export default class Home extends Component { render() { return ( <div> <h2 className="active">Home</h2> <img src="img/login-bg.jpg" alt=""/> </div> ) } }
在Home组件中我们引入了一个css样式,里面包含一个字体颜色。
.active { color: red; }
这个字体颜色我们在Home组件里面使用了,也在Main组件里面也用到了这个颜色。启动项目的时候我们可以看到Main组件里面也作用了这个样式。
在显示页面里面,你会发现App这个文字变成红色,说明加载Main组件的时候就已经加载了Home组件。如果我们的组件越来越多,那你第一次加载的时候就非常慢。
路由懒加载的目的就是为了让我们操作没有链接的时候在去加载对应的样式。React Loadable 是一个轻量级的代码分割组件,它简单到令人难以置信。
Loadable 是一个告诫组件 (一个创建并返回组件的函数),它能让你的应用程序在渲染之前动态的加载任何模块,
-
下载路由懒加载插件
npm install react-loadable yarn add react-loadable
-
在配置路由的时候,把我们要用于配置在component属性上面的组件用react-loadable包装一次即可
import Loadable from "react-loadable" const Home = Loadable({ loader:()=>import("./Home"), loading:()=><div>加载中。。。</div> }) const About = Loadable({ loader:()=>import("./About"), loading:()=><div>加载中。。。</div> }) return ( <BrowserRouter> <div> <h1 className="active">App</h1> <ul> <li><Link to="/home">首页</Link></li> <li><Link to="/about">关于我</Link></li> </ul> </div> <Switch> <Route path={"/home"}> <Home/> </Route> <Route path={"/about"} component={About}></Route> </Switch> </BrowserRouter> )
在上面的代码中我们使用了Loadable对组件进行了包装,当点击超链接的时候才会去加载组件。基于这种方式来实现路由的懒加载。当点击了/home链接过后我们才会加载Home这个组件。
-
路由练习
react实现同页面二级跳转路由布局,基于目前我们学到的react组件、route路由缓存信息。
-
一级路由和组件的设计
import React, { Component } from 'react' import {Route,Link,HashRouter} from "react-router-dom" import "../css/mainPage.css" import Loadable from "react-loadable" const Index = Loadable({ loader:()=>import("./Index"), loading:()=><div>加载中。。。</div> }) const Video = Loadable({ loader:()=>import("./Video"), loading:()=><div>加载中。。。</div> }) const Workplace = Loadable({ loader:()=>import("./Workplace"), loading:()=><div>加载中。。。</div> }) export default class MainPage extends Component { state = { routeConfig: [ { path: '/', title: '信息栏目', exact: true, component: Index }, { path: '/video', title: '入门教程', exact: false, component: Video }, { path: '/workplace', title: '学习技巧', exact: false, component: Workplace }, ] } render(){} }
我们在代码中使用了Loadable设计了组件懒加载,在组件中定义了routeConfig数组来定义动态数组,在render里面我们就可以动态渲染导航组件。
render() { return ( <div> <HashRouter> <div className='mainDiv'> {/* 左侧导航部分 */} <div className='leftNav'> <h3>一级导航</h3> <ul> {/* 渲染动态路由 */} { this.state.routeConfig.map((item, index) => { return ( <li key={index}> <Link to={item.path}>{item.title}</Link> </li> ) }) } </ul> </div> {/* 右侧显示部分 */} <div className='rightMain'> { this.state.routeConfig.map((item, index) => { return ( <Route key={index} exact={item.exact} path={item.path} component={item.component} /> ) }) } </div> </div> </HashRouter> </div> ) }
在上面的代码中,我们使用map来对数据进行遍历,然后渲染到页面上。左侧部分是导航信息使用Link来渲染,右侧部分是Route组件加载部分。
-
二级路由和组件的设计
在一级路由中我们引入了Index、Video、Workplace三个组件,其中Workplace组件中我们需要引入二级路由来完成组件加载
export default class Workplace extends Component { render() { return ( <div> <div className='topNav'> <ul> <li><Link to='/workplace/know/'>从入门到精通</Link></li> <li><Link to='/workplace/fail/'>从入门到放弃</Link></li> </ul> </div> <div className='videoContent'> <div> <h3>学习技巧</h3> <Route path='/workplace/know/' component={WorkplaceKonw} /> <Route path='/workplace/fail/' component={WorkplaceFail} /> </div> </div> </div> ) } }
一级导航路径为:/workplace,二级导航路径为:/workplace/know/和/workplace/fail/
渲染的结果为:
<form action="Demo02有语义.html" method="POST">
<input type="text" placeholder="请输入用户名"><br/><br/>
<input type="text" autofocus><br/><br/>
<input type="file" multiple><br/><br/>
<input type="text" autocomplete="on" name="username" placeholder="填写内容">
<!-- 必填项 -->
<input type="text" required><br/><br/>
<button type="submit">提交</button>
</form>