1、概念
路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动.
路由的概念出现最早是在后端路由中实现的,原因是web的发展主要经历了这样一些阶段:
1.1、后端路由阶段;
早期的网站开发整个HTML页面是由服务器来渲染的.
服务器直接生产渲染好对应的HTML页面, 返回给客户端进行展示.
1.1.1、服务器处理多个页面的步骤:
1、一个页面有自己对应的网址, 也就是URL.
2、URL会发送到服务器, 服务器会通过正则对该URL进行匹配, 并且最后交给一个Controller进行处理.
3、Controller进行各种处理, 最终生成HTML或者数据, 返回给前端.
4、这就完成了一个IO操作.
1.1.2、上面的这种操作, 就是后端路由.
1、当我们页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户顿.
2、这种情况下渲染好的页面, 不需要单独加载任何的js和css, 可以直接交给浏览器展示, 这样也有利于SEO的优化.
1.1.3、后端路由的缺点:
1、一种情况是整个页面的模块由后端人员来编写和维护的.
2、另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码.
3、而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情.
1.2、前后端分离阶段
分析:
1、每次请求涉及到的静态资源都会从静态资源服务器获取, 这些资源包括HTML+CSS+JS,然后在前端对这些请求回来的资源进行渲染;
2、客户端的每一次请求,都会从静态资源服务器请求文件;
3、 同时可以看到,和之前的后端路由不同,这时后端只是负责提供API了;
总结前后端分离思想:
1、随着Ajax的出现, 有了前后端分离的开发模式,
2、后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中
1.3、单页面富应用(SPA)
单页面富应用的英文是single-page application,简称SPA,整个Web应用只有实际上只有一个页面,当URL发生改变时,并不会从服务器请求新的静态资源,而是通过JavaScript监听URL的改变,并且根据URL的不同去渲染新的页面。
前端路由实现:
1、前端路由维护着URL和渲染页面的映射关系
2、路由可以根据不同的URL,最终让我们的框架(比如Vue、React、Angular)去渲染不同的组件
3、最终我们在页面上看到的实际就是渲染的一个个组件页面;
2、前端路由的实现
2.1、前端路由的原理
1、监听URL的改变
1)、 通过URL的hash改变URL
2)、可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
URL的hash也就是锚点(#), 本质上是改变window.location的href属性
注意点:
hash的优势就是兼容性更好,在老版IE中都可以运行、但是缺陷是有一个#,显得不像一个真实的路径;
<body>
<div id="app">
<a href="#/home">首页</a>
<a href="#/about">关于</a>
<div class="router-view"></div>
</div>
<script>
// 获取routerview docm
const routerViewEl = document.getElementsByClassName("router-view")[0];
// 监听url改变
window.addEventListener("hashchange", () => {
console.log(routerViewEl);
switch(location.hash) {
case "#/home":
routerViewEl.innerHTML = "首页";
break;
case "#/about":
routerViewEl.innerHTML = "关于";
break;
default:
routerViewEl.innerHTML = "";
break;
}
})
</script>
</body>
</html>
2)、通过HTML5中的history模式修改URL,它有l六种模式改变URL而不刷新页面
<body>
<div id="app">
<a href="/home">首页</a>
<a href="/about">关于</a>
<div class="router-view"></div>
</div>
<script>
const routerViewEl = document.getElementsByClassName("router-view")[0];
// 监听url改变
const aEls = document.getElementsByTagName("a");
for (let el of aEls) {
el.addEventListener("click", e => {
e.preventDefault();
const href = el.getAttribute("href");
history.pushState({}, "", href);
urlChange()
// history.go()
// history.forward()
})
}
window.addEventListener("popstate", urlChange);
// window.addEventListener("go", urlChange);
function urlChange() {
switch (location.pathname) {
case "/home":
console.log("首页")
routerViewEl.innerHTML = "首页";
break;
case "/about":
console.log("关于")
routerViewEl.innerHTML = "关于";
break;
default:
console.log("default")
routerViewEl.innerHTML = "";
}
}
</script>
</body>
2、当监听到URL发生变化时,我们可以通过自己判断当前的URL,决定到底渲染什么样的内容。
3、react-router
3.1、react-router介绍
1、目前前端流行的三大框架, 都有自己的路由实现:
Angular的ngRouter、React的react-router、Vue的vue-router
2、react-router的代码组成
- react-router是router的核心部分代码
- react-router-dom是用于浏览器的
- react-router-native 是用于原生应用的
3、安装react-router:
安装react-router-dom会自动帮助我们安装react-router的依赖;
安装命令yarn add react-router-dom
3.2、react-router基本使用
react-router最主要的API是给我们提供的一些组件:
1、BrowserRouter或HashRouter
- Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件
- BrowserRouter使用history模式
- HashRouter使用hash模式
import React, { PureComponent } from 'react'
import { BrowserRouter, Link, Route, NavLink, Switch, withRouter } from 'react-router-dom'
import About from './pages/about'
import Home from './pages/home'
import Profile from './pages/profile'
class App extends PureComponent {
static propTypes = {}
constructor(props) {
super(props)
}
render() {
return (
<BrowserRouter>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/profile">我的</Link>
<Route exact={true} path="/" component={Home}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/about" component={About}></Route>
</BrowserRouter>
)
}
}
2、Link和NavLink:
- 通常路径的跳转是使用Link组件,最终会被渲染成a元素
- NavLink是在Link基础之上增加了一些样式属性(后续学习)
- to属性:Link中最重要的属性,用于设置跳转到的路径
3、Route
- Route用于路径的匹配
- path属性:用于设置匹配到的路径
- component属性:设置匹配到路径后,渲染的组件
- exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件
3.3、NavLink的使用
NavLink可以改变选中时对应的a元素变为红色
NavLink的三个属性:
1、activeStyle:活跃时(匹配时)的样式、
2、activeClassName:活跃时添加的class;(在默认匹配成功时,NavLink就会添加上一个动态的active class)
3、exact:是否精准匹配;
<NavLink exact to="/" activeClassName={"link-active"}>首页</NavLink>
<NavLink to="/about" >关于</NavLink>
<NavLink to="/profile" >我的</NavLink>
对应的.css
a.active {
color: red;
font-size: 30px;
margin: 10px;
text-decoration: line-through;
}
3.4、Switch使用
当匹配到某一个路径时,会发现有一些问题,比如/about路径匹配到的同时,/:userid也被匹配到了,并且最后的一个NoMatch组件总是被匹配到
原因是默认情况下,react-router中只要是路径被匹配到的Route对应的组件都会被渲染;
如果希望只匹配到了第一个,后面的就不继续匹配了,可以使用Switch
<Switch>
<Route exact={true} path="/" component={Home}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/about" component={About}></Route>
<Route path="/user" component={User}></Route>
<Route path="/login" component={Login}></Route>
<Route path="/product" component={Product}></Route>
<Route path="/detail/:id" component={Detail}></Route>
<Route path="/detail2" component={Detail2}></Route>
<Route path="/detail3" component={Detail3}></Route>
<Route component={NoMatch}></Route>
</Switch>
3.5、Redirect
Redirect用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Redirect } from 'react-router-dom'
class User extends PureComponent {
static propTypes = {}
constructor(props) {
super(props)
this.state = {
isLogin: false
}
}
render() {
return (
this.state.isLogin ? (<div>
<h2>Login</h2>
<h2>coder why</h2>
</div>) : (<Redirect to="/login"/>)
)
}
}
export default User
3.6、手动路由跳转
通过JavaScript代码进行跳转有一个前提:必须获取到history对象,如果该组件是通过路由直接跳转过来的,那么可以直接获取history、location、match对象
index.js文件
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
App.js文件
class App extends PureComponent {
static propTypes = {}
constructor(props) {
super(props)
this.state = {
links: [
{to: "/", title: "首页"},
{to: "/about", title: "关于"},
{to: "/profile", title: "我的"}
]
}
}
render() {
const id = "abc"
const info = {name: "codey", age: 18};
return (
<div>
{/* {
this.props.links.map((item, index) => {
return (
<div key={item.title} className={classNames({"active": index === this.state.currentIndex})}>
<Link to={item.to}>{item.title}</Link>
</div>
)
})
} */}
<NavLink exact to="/" activeClassName={"link-active"}>首页</NavLink>
<NavLink to="/about" >关于</NavLink>
<NavLink to="/profile" >我的</NavLink>
<NavLink to="/abc" >abc</NavLink>
<NavLink to="/user" >user</NavLink>
<NavLink to="/login" >login</NavLink>
<NavLink to={`/detail/${id}`} activeClassName={"link-active"}>详情</NavLink>
<NavLink to={`/detail2?name=cody&age=10`} activeClassName={"link-active"}>详情2</NavLink>
<NavLink to={{
pathname: "/detail3",
search: "?names=abc",
state: info
}} activeClassName={"link-active"}>详情3</NavLink>
<button onClick={e => this.jumpProduct()}>商品列表</button>
{/*switch的作用*/}
<Switch>
<Route exact={true} path="/" component={Home}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/about" component={About}></Route>
<Route path="/user" component={User}></Route>
<Route path="/login" component={Login}></Route>
<Route path="/product" component={Product}></Route>
<Route path="/detail/:id" component={Detail}></Route>
<Route path="/detail2" component={Detail2}></Route>
<Route path="/detail3" component={Detail3}></Route>
<Route component={NoMatch}></Route>
</Switch>
{ (routes)}
</div>
)
}
// 跳转商品列表
jumpProduct() {
this.props.history.push("/product")
}
}
export default withRouter(App)
3.7、参数传递
参数传递方式:1、search传递参数,2、Link中to可以直接传入一个对象
<NavLink to={`/detail2?name=cody&age=10`} activeClassName={"link-active"}>详情2</NavLink>
<NavLink to={{
pathname: "/detail3",
search: "?names=abc",
state: info
}} activeClassName={"link-active"}>详情3</NavLink>
参数接收
<h2>Detail2 : {this.props.location.search}</h2>
4、react-router-config
安装react-router-configyarn add react-router-config
配置路由映射的关系数组
const routes = [
{
path: "/",
exact: true,
component: Home
},
{
path: "/about",
component: About,
routes: [
{
path: "/about",
exact: true,
component: AboutHistory
}
]
},
{
path: "/profile",
component: Profile
},
{
path: "/",
component: User
}
]
export default routes;
renderRoutes源码分析
function renderRoutes(routes, extraProps, switchProps) {
if (extraProps === void 0) {
extraProps = {};
}
if (switchProps === void 0) {
switchProps = {};
}
return routes ? React.createElement(reactRouter.Switch, switchProps, routes.map(function (route, i) {
return React.createElement(reactRouter.Route, {
key: route.key || i,
path: route.path,
exact: route.exact,
strict: route.strict,
render: function render(props) {
return route.render ? route.render(_extends({}, props, {}, extraProps, {
route: route
})) : React.createElement(route.component, _extends({}, props, extraProps, {
route: route
}));
}
});
})) : null;
}
网友评论