路由
- yarn add react-router-dom
使用HASH路由,HashRouter
- 页面中所有内容都用他包起来
- 并且内容只有一个根节点
使用Route
- 设置路由规则的,基于不同规则渲染不同组件
- 行间属性
path="地址"匹配地址- 行间属性
component={组件}显示组件- 行间属性
render={()=>{}}当地址匹配到后,执行这个render函数,函数返回什么就渲染什么,这样我们在函数中可以做路由权限校验- 行间属性
exact精准匹配
Switch
- 只显示一个组件
Redirect
- 重定向,行间加上to代表重定向的地址
- 也可以加上path,代表匹配地址的重定向
二级路由
- 在哪用就在哪里写,不过不用HashRouter了,直接Switch就可以
跳转
- 分为Link和NavLink两种
NavLink
- 行间属性to 代表点击跳转的位置,
- to直接写字符串就是地址,写个对象,那对象内的pathname是地址,search就是路由传参
- 会渲染成a标签
- NavLink
优势,会拿页面路由地址和NavLink中的to来进行匹配,如果匹配上就给谁添加一个active样式类名,我们可以针对这个类名写样式
Link
- 用法与NavLink一样,只是不会默认加active样式类名了
受路由管控组件与非受控路由管控组件
受路由管控组件
- 受:
根据路由Route规则校验后进行渲染的组件,render函数处理的不算默认属性(props)中有三项值的,history、location、match
history
- go、goBack(-1)、goForward(相当于go(1))、push都是实现路由跳转的
location
- 存储了当前路由跳转时候传递的一些信息
search是问号传参的信息pathname是地址state是隐式传参的信息,如果刷新就会丢失,为undefined
match
- 存储
路由规则解析出来的信息- params
非受路由管控的组件
- 非受:
不是根据路由进行渲染的组件- 属性中是
没有history、match、location这些东西的- 如果
想使用的话,可以根据withRouter高阶函数处理,使用的时候在export default的时候执行withRouter(组件名),那么props里面就有了
页面跳转的两大类方式
根据Link和NavLink
- 基于标签进行跳转
- 行间传递to,可以传字符串也可以传对象
- 对象模式:
pathname是地址,search是问号传参,是显示传参,state是隐式传参,路由内部接收到,但是不会在url中显示问题:隐式传递的页面刷新就没有了,所以说隐式传参只在路由跳转的那一会儿有用- 问号传参&隐式传参的信息
都可以基于location获取到
根据this.props.history里面的方法,也称为编程式导航
例如this.props.history.push('/home/add')push跳转也可以写成对象格式,语法与Link里面的to一样
路径参数
- 将参数信息当做URL路径的一部分,类似与vue中的动态路由
- 例如
<Route path="/home/:id/:name" component={CustomList}></Route>- 在
props里面的match里面的params可以拿到id与name
使用 react-router 的好处
- 常用的两种路由方式是 hash 和 history
- 举例说明,直接刷新页面,他的 js 会重新加载,使用 hash 和 history 后,页面没有刷新,所以,他的静态资源不会刷新,也就没有了那么长等待时间,用户体验会好一点
尝试一下
- 常见的跳转页面的方式,
- 网址框输入 url,回车 会导致刷新页面,静态资源重新请求
- location.href 会导致刷新页面,静态资源重新请求
- location.hash 更改 hash,静态资源不会重新请求,没有刷新页面
- history.go 或 history.pushState 等方法,静态资源不会重新请求,没有刷新页面
History.pushState()
- 作用:在不刷新页面的情况下,实现页面切换
- 三个参数
- window.history.pushState(state, title, url)
- state 是 object 类型,跳转后会将 state 存储,可以通过 history.state 拿到,不传可以为 null,页面刷新不丢失,页面关闭丢失
- title 是 string 类型,作用为页面 title,可以为 null,目前浏览器不认可这个值,写了也没用
- url 是 string 类型,代表要跳转的路径
- 注意:hashchange 事件无法监听到他切换
- popstate 事件也无法监测到
popstate 事件
- 用来监听 history 部分事件
- 为什么说是部分事件,因为 history.pushState 和 replace 事件监听不到,
- 但是 go,back,和浏览器自带回退前进都可以监听到(前提是 history 触发过了),否则监听不到
location.hash = "#/1";
history.go(-1); //监听不到
history.pushState(null, null, "#/1");
//监听不到
history.go(-1); //监听到了
路由受控组件和非路由受控组件
- 个人觉得判断是否是路由受控组件的标准就是在他的 props 里面是否有父级传递过来的 history,location,和 match 集合
- 常见的几种路由受控组件:Route 匹配到的组件,withRouter 处理过的组件
- 将非受控组件变为受控组件的方法,withRouter(组件)
Route
- 我们配置路由的时候,根据他的 path 来渲染组件
- 他会将 path 匹配到的 route 全部渲染,匹配规则是包含关系,如/a 包含/
- exact=true 就是绝对相等,才会渲染
Switch
- 因为 Route 默认会将 path 匹配到的组件全部展示,但是往常我们只需要一个页面匹配一个组件,公有的写在外面即可
- 作用:在所有 Route 外包一层 Switch,即可只渲染匹配到的第一个
Link
- 作用:跳转
- 为什么我们有 a 标签了,还要用 Link 呢?
- 因为 a 标签会刷新页面
- 那如果使用 history.pushState 来刷新页面,不是也能达到想要的效果吗?
- 如果 pathname 改变是会走渲染逻辑的,但是如果是问号传参的更改,history.pushState 并不会刷新视图,
- 这个时候我们可以使用 props 里的 history.push 或者是 Link 来实现
- 参数:to
- 可以是一个对象,也可以是一个字符串
Redirect
- 作用:重定向
- 参数:
from 选填,string,不填的时候默认是匹配到所有路径,填上就是只匹配对应路径才跳转
to 要跳转的页面 string
- 应用场景:用户进到我们没有处理的页面,让他跳到首页
- 他用来 history.replace 来实现的,因为使用 history.back 会回退到上上一个
两种路由方法
- HashRouter
- BrowserRouter
- 区别:一个是 hash,一个是 history
- 个人感觉到的区别:
- hash 带个#,有些丑
- hash 是相对于 pathname 的,而 history 是相对于根路径的(所以使用 history 可能会导致一种情况,页面刷新后,找不到页面了)
粗略实现下两种路由
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./index.css">
<script src="./index.js"></script>
<style>
#root {
width: 100vw;
height: 100vh;
}
</style>
<title>hash</title>
</head>
<body>
<div id="root">
<a href="/myHash/index.html">原地转圈(刷新页面请求资源)</a>
</div>
</body>
</html>
<script>
const locationClick = (e) => {
e.preventDefault();
location.href = "/myHash/index.html";
}
const RootDom = document.querySelector('#root');
const mapList = {
type: 'history',
append: function () {
this.domList.forEach(item => {
RootDom.appendChild(item);
})
},
pond: {},
domList: [],
remove: function () {
let {
domList
} = this;
if (domList.length === 0) return;
domList.forEach(item => {
RootDom.removeChild(item);
});
this.domList = [];
},
addPond: function (params) {
const {
path,
component,
exact
} = params;
this.pond[path] = {
component: component && component(),
exact: exact || false
}
},
init: function (pathname) {
const pond = this.pond;
const pathList = Object.keys(pond);
this.remove();
pathList.forEach(item => {
const reg = new RegExp(`^${item}`);
if (reg.test(pathname)) {
const dom = pond[item]['component'];
if (dom) {
this.domList.push(dom);
}
}
});
this.append();
}
};
const HashRouter = () => {
mapList['type'] = 'hash';
};
const BrowserRouter = () => {
mapList['type'] = 'history';
};
const historyClick = (e) => {
e.preventDefault();
const href = e.target.history;
history.pushState(null, null, href);
mapList.init(href);
}
//Link标签
const Link = (params) => {
let {
chidren,
href
} = params;
let A = document.createElement('a');
if (mapList['type'] === 'hash') {
href = '#' + href;
A.setAttribute('href', href);
} else {
A.onclick = historyClick;
A.history = href;
}
A.innerHTML = chidren;
RootDom.appendChild(A);
};
/*
path 路径 string
component 渲染 function
*/
const Route = (params) => {
mapList.addPond(params);
};
HashRouter();
Link({
href: '/',
chidren: '我是/'
});
Link({
href: '/1',
chidren: '我是/1'
});
Route({
path: '/',
component: () => {
let Box = document.createElement('div');
Box.innerHTML = '我是/';
return Box;
},
});
Route({
path: '/1',
component: () => {
let Box = document.createElement('div');
Box.innerHTML = '我是/1';
return Box;
},
});
const change = () => {
let pathname = '';
if (mapList['type'] === 'hash') {
pathname = location.hash.slice(1);
} else {
pathname = location.pathname;
}
mapList.init(pathname);
}
change();
window.addEventListener('hashchange', change, false);
window.addEventListener('popstate', change, false);
</script>