为什么需要路由?
站点的构成
微博是新浪的一个产品,也是一个站点,一个站点下包含很多个功能块,比如微博包括:新闻、短视频等,而功能块之间多多少少有点联系。一个功能块可能就是一个单页面应用
无论是使用vue还是react,开发的单页应用程序,可能只是该站点的一部分(某一个功能块)做成一个
一个单页应用里,可能会划分为多个页面(几乎完全不同的页面效果)(组件)
如果要在单页应用中完成多个页面(组件)的切换,需要实现如下两个功能:
- 根据不同的页面地址,展示不同的组件(这是最重要的)
- 完成无刷新的地址切换(更好的用户体验)
React Router
- react-router:路由核心库,包含诸多和路由功能相关的核心代码
- react-router-dom:利用路由核心库,结合实际页面,实现跟页面路由密切相关的功能
如果是在页面中实现路由,需要安装 react-router-dom 库
两种路由模式
路由:根据不同的页面地址,展示不同的组件
url地址组成
例:https://www.react.com:443/news/1-2-1.html?a=1&b=2#abcdefg
- 协议名(schema):https
- 主机名(host):www.react.com
- ip 地址
- 预设值:localhost
- 域名:www.react.com
- 局域网中计算机名
- 端口号(port):443
- 若协议为http,则端口号默认为80,且可以省略;https的端口号默认为443,也可省略
- 路径(path):/news/1-2-1.html
- 第一个 / 代表根路径
- 地址参数(search/query):?a=1&b=2
- 附带的数据
- 格式:属性名=属性值&属性名=属性值…
- 哈希(hash/锚点):#abcdefg
如何获取hash值呢?
const urlHash = location.hash
Browser History Router 浏览器历史记录路由
HTML5 新增了 History API,自此,浏览器拥有了 改变url的路径(path)而不刷新页面的方式
History表示浏览器的历史记录,使用栈存储
window.history
- history.length:栈中的数据量(当前页面。新开的页面肯定不算啊,那都不是单页面了)
- history.pushState:向当前历史记录中加入一条新的记录
- 参数1:附加的数据,自定义的数据,可以是任何类型
- 参数2:页面标题,目前大部分浏览器不支持!
- 参数3:url的路径path,新的地址
- history.replaceState:将当前指针指向的历史记录,替换为某个记录。参数同上
- history.go/back/forward
根据页面的路径(url中的path那一部分)决定渲染哪个组件
路由组件
Router组件
- 它本身不做任何展示,仅提供路由模式配置。
该组件会产生一个上下文,上下文中会提供一些实用的对象的和方法,供其他相关组件实用
HashRouter:该组件使用hash模式匹配
- BrowserRouter:该组件使用BrowserHistory模式匹配
通常情况下,Router组件只有一个,要将该组件包裹整个页面(一般包裹住App.js的根元素即可)
Route组件
根据不同的地址,展示不同的组件
Route组件的重要属性:
- path:匹配的目录路径(默认情况下,不区分大小写)
- 默认不区分大小写,可以设置sensitive属性为true,来区分大小写
- 默认会匹配初始目录的路径上的所有组件,比如path=”/a/b”就会把A和B组件都匹配到,加上 exact,才会只匹配到组件B
- 如果想要任意目录都匹配到组件,则不写 path 属性即可
- component:匹配成功后要显示的组件
- children:
- 传递React元素,无论是否匹配,一定会忽略component属性?还是说,只是一定会加载children。匹配到path才会显示children元素
- 传递一个函数,该函数有多个参数,这些参数来自于上下文,该函数返回react元素。无论当前地址和path路径匹不匹配,都将会执行children对应的函数。
- 上述都是在非Switch包裹下才会按照那样的规则
TO DO:上述有待实践考察。!!!!
Route组件可以写到任意地方,只需保证是Router组件的后代元素即可
children是Route组件的这三个属性中优先级最低的,在Route组件中, component , render ,children是可以同时存在的。同时存在的条件下有这样一个优先级顺序:component > render > children…
Switch组件
写到Switch中的Route组件,只要匹配到第一个Route组件后,就会立即停止匹配!
Switch组件内部只能写Route组件。
Switch会掌控Route的渲染
由于Switch组件会循环所有子元素,然后让每个子元素依次去完成匹配,若匹配到,则渲染对应组件,然后立即停止循环。因此,不能在Switch的子元素中使用除Route组件外的组件
import React, { Component } from "react";
// import Test from './components/CheckBoxGroup/Test';
// import Test from "./Comp1";
// import Test from "./FuncDefault.js"
import { BrowserRouter, Route } from "react-router-dom";
function A(){
return (
<h1>组件A</h1>
)
}
function B(){
return (
<h1>组件B</h1>
)
}
function C(){
return (
<h1>组件C</h1>
)
}
function D(){
return (
<div>
<h1>找不到页面</h1>
<Route exact path="/abc" component={E}></Route>
</div>
)
}
function E(){
return (
<span>E组件</span>
)
}
export default class App extends Component {
render() {
return (
<BrowserRouter>
{/* <Switch> */}
<Route path='/a' component={A} >
<h1 style={ { color: "red" } }>匹配到a就出现!无论是否匹配到a都会忽略A</h1>
</Route>
<Route sensitive path='/a/b' component={B}/>
<Route exact path='/a/c' component={C} />
<Route component={D}></Route>
{/* </Switch> */}
</BrowserRouter>
);
}
}
搭建后台管理模版
路由信息
Router组件会创建一个上下文,并且,向上下文中注入一些信息
该上下文对开发者是隐藏的,Route组件若匹配到了地址,则会将这些上下文中的信息作为属性传入对应的组件
三个上下文中的属性分别为:history、location、match。如今有第四个了,staticContext
history
它并不是window.history对象,是经过处理封装的。我们利用history对象实现无刷新跳转
- push:将某个新的地址入栈,props.history.push(‘/a’); 就会跳到组件A
- 参数一:新的path
- 参数二:可选,附带的状态数据,在props.history.location.state中。一般不用它
- replace:将某个新的path替换掉当前栈中的path
- pop、go、forward、back与window.history用法一致
为什么没有直接使用window.history对象?
- React-Router中有两种模式:Hash、History,如果使用了window.history则只能使用History模式
- 当使用window.history.pushState方法时,没有办法收到任何通知,将导致React无法知晓地址发生的变化,结果导致无法重新渲染组件。
location
props.location与props.history.location完全一致,是同一个对象,但是与window.location不同!
location对象中记录了当前地址的相关信息,hash,pathname,search,state。
我们通常使用第三方库 query-string 用于解析地址栏中的数据
match
该对象中保存了,路由匹配的相关信息:isExact, params, path, url
- isExact:表示 实际上,目前的真实路径和路由组件的路径是否是真正完全匹配的。注:并不取决于exact
- params:见下述
- path是匹配规则,此处url是最终真实的整个链接中的path部分的值
向某个页面传递数据的方式
- 使用state:在push页面时,加入state
- 利用search:把数据填写到地址栏中的?后,这个常用一些
- /new/?year=2021&month=9&day=7
- 利用hash:把数据填写到hash后面
- params:把数据填写到路径中
- /new/2021/9/7
实际上,在书写Route组件的path属性时,可以书写一个 string pattern 字符串正则,类似正则表达式
// 不一定用/分割,用-等都行,冒号后就是表示一个变量,问号表示可传可不传,
// 括号中就是对前面变量的约束,使用正则表达式。*代表后面必须跟任意字段
<Route path="/news?/:year?/:month?/:day/*" componen={News} />
<Route path="/news-:year(\d+)-:month(\d+)-:day(\d+)" componen={News} />
//path可以是数组的形式
<Route path={["/news", "/news?/:year?/:month?/:day/*"]} componen={News} />
//匹配的年月日就在params中
props.match.params
React-Router是利用Path-to-Regexp,将一个字符串正则转换成一个真正的正则表达式
非路由组件如何获取路由信息
由于某些组件,并没有直接放到Route中,而是嵌套在其他普通组件中,因此,它的props中没有路由信息,如果这些组件需要获取到路由信息,可以使用下面两种方式:
- 将路由信息在组件阶层中层层传递下去,
- 使用React-Router提供的高阶组件 withRouter,包装要使用的组件,该高阶组件会返回一个新组件,新组件中会注入路由信息
function withRouter(Comp){
return function RouterWrapper(props){
// 获取上下文中的路由信息, 我们拿不到,但react拿得到
return <Comp {...props} history={上下文中的history} />
}
}
其他组件
已学习:
- Router:BrowserRouter、HashRouter
- Route
- Switch
- 高阶函数:withRouter
Link
生成一个无刷新跳转的a元素 ```javascript // Link.js function Link(props){ // 原理性模拟 return ( // staticContext不能放在真实dom上,所以这控制台会报红色警告
) }<a href={props.to} {...props}
onClick={(e)=>{
e.preventDefault();
props.history.push(props.to);
}}
>{props.children}</a>
export default withRouter(Link) ```
- to属性
- 字符串,跳转的目标地址
- 对象:pathname,search,hash,state
- replace属性:布尔值,表示是否替换当前地址。默认是false,即push方式跳转
innerRef属性:可以将内部的a元素的ref附着在传递的对象或函数参数上
activeClassName:匹配到时,使用的类名,需要我们自己给这个类写css
- activeStyle:匹配到时,使用的内联样式
- exact:是否精确匹配
- sensitiive:匹配时是否区分大小写
-
Redirect
重定向组件,当加载到该组件时,会自动跳转(无刷新)到另外一个地址
一般可以配合Switch使用,让Redirect兜底 to属性:表示要跳转到的地址,字符串/对象
- push:默认为false,表示跳转使用替换的方式
- from:当要跳转到的地址匹配from地址规则时才进行重定向跳转
- 还能把它匹配的结果放进to中,注意变量名称需要一致
- exact、sensitive、strict
vue与react router简单对比
vue-router 一般是静态的配置(当然也可以动态的配置)
react-router v4之前 也是静态的配置
react-router v4及之后,动态的组件,非常的灵活