Vue Router
基本使用
引入vue插件:
vue add router
Vue.use(router)
配置router.js:
//routes
{
path: '/', name: 'home', component: Home, children : [] //children中配置嵌套路由
path: '/about', name: 'about', component: About
}
new Router(routers)
路由出口:
<router-view />
导航链接:
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
添加到vue选项:
// main.js
new Vue({
router,
render: h => h(App)
}).$mount('#app')
vue-router源码中到底做了什么事情呢?
分析:
1.首先要定义一个Router类,在这个类中,要解析我们的routes配置,生成对应的map,以便导航栏切换了url后,可以很方便的找到要渲染的组件;其次是要对url做响应式处理,并监听浏览器url变化并添加监听函数处理响应逻辑;同时我们在全局中可以使用router-link和router-view,所以还需要全局注册这两个组件。
2.我们使用Vue.use(router)添加router,说明router是一个插件,调用use方法时,会调用插件的静态方法install,所有还需要在类中实现install的静态方法,然后mixin到Vue中,并挂载全局对象$router。
开始写源码
//导出我们自己实现的router
export default class VueRouter{
constructor(options){
this.$options = options //保存我们的参数,后续要用
this.routerMap = {} //保存地址和组件之间的映射关系
this.app = new Vue({ //对url作响应式处理
data:{ url:'/' }
})
}
//初始化router的函数,new Vue的时候调用
init(){
//监听浏览器的url变化,这里我们使用hash模式(带#号的)
this.bindEvents()
//生成routerMap映射关系
this.createRouterMap()
//全局注册router-link,router-view
this.initComponents()
}
bindEvents(){
window.addListener('hashChange',this.onHashChange.bind(this))
window.addListener('load',this.onHashChange.bind(this))
}
onHashChange(e){
//这个一变,用到url的地方就会变
this.app.url = window.location.hash.slice(1) //#index,只需要#后面的内容
}
createRouterMap(){
//递归函数
function getMap(routes){
routes.forEach(item=>{
this.routeMap[item.path] = item
if(item.children){ //嵌套路由
getMap(item.children)
}
})
}
getMap(this.$options.routes)
}
initComponents(){
//全局注册router-link
Vue.component('router-link',{
//有一个属性to
props:{
to:String
}
//实则是一个a标签
render(h){
// return <a href={this.to}>{this.$slots.default}<a>
// h就是createELement函数,参数是tag,data,children
return h('a',{attrs:{href:'#'+this.to}},[this.$slots.default])
}
})
//全局注册router-view
Vue.component('router-view',{
render(h){
//拿出要渲染的组件,url变化router-view就会变化
const component = this.routerMap[this.app.url].component
return h(component)
}
})
}
//实现插件,即实现install的静态方法,Vue.use的时候调用,参数是Vue的构造函数
VueRouter.install = function(Vue){
//混入到Vue的生命周期中,执行初始化router并挂载$router
Vue.mixin({
//该生命周期比较早,且只会在new Vue的时候调用
beforeCreate(){
if(this.$options.router){
//vue的实例就可以使用vue.$router
Vue.prototype.$router = this.$options.router
//初始化router
this.$options.router.init()
}
}
})
}
}
嵌套路由的解决方式
以上代码并没有解决rouer-view嵌套问题,源码中的实现方式是在router-view的render函数中将data的routerView属性设为true,然后去找父级中的data属性中找存不存在routerView=true的情况,找到了就将其属性depth加1,然后根据depth去route中match对应的组件并渲染。
React Router
基本使用
react-router中奉⾏⼀切皆组件的思想,路由器Router、链接Link、路由Route、独占路由Switch、重定向Redirect都以组件形式存在。
Route渲染优先级:children>component>render
创建RouterPage.js:
import React, { Component } from "react";
import { BrowserRouter, Link, Route } from "react-router-dom";
import HomePage from "./HomePage";
import UserPage from "./UserPage";
export default class RouterPage extends Component {
render() {
return (
<div>
<h1>RouterPage</h1>
<BrowserRouter>
<nav>
<Link to="/">⾸⻚</Link>
<Link to="/user">⽤户中⼼</Link>
</nav>
//根路由要添加exact,实现精确匹配,动态路由使⽤:id的形式定义动态路由
<Route exact path="/" component= {HomePage} />
<Route path="/user" component= {UserPage} />
</BrowserRouter>
</div>
);
}
}
嵌套路由
Route组件嵌套在其他⻚⾯组件中就产⽣了嵌套关系
Detail.js:
function Detail() {
return (
<div>
<h1>Detail</h1>
</div>
);
}
嵌套了Detail.js的Search.js:
function Search({ match, history, location }) {
const { id } = match.params;
return (
<div>
<h1>Search: {id}</h1>
<nav>
<Link to="/search/add">新增</Link>
<Link to={"/search/detail/" + id}>详情</Link>
</nav>
<Route path="/search/add" component={()=> <h1>add</h1>} />
<Route path={"/search/detail/:" + id} component={Detail} />
</div>
);
}
404⻚⾯
设定⼀个没有path的路由在路由列表最后⾯,表示⼀定匹配
<Switch> //独占路由表示只会匹配一个
{/* 根路由要添加exact,实现精确匹配 */}
<Route exact path="/" component={HomePage} />
<Route path="/user" component={UserPage} />
<Route path="/search/:id" component={Search} />
<Route component={() => <h1>404</h1>} />
</Switch>
react-router源码中到底做了什么事情呢?
分析:
react-router秉承⼀切皆组件,因此实现的核⼼就是BrowserRouter、Route、Link
实现BrowserRouter
BrowserRouter:历史记录管理对象history初始化,及向下传递,location变更监听,本身
无内容,负责显示子组件的内容即可(组件复合,或者叫插槽)。
import { createBrowserHistory} from 'history'; //切换组件的库,保存着我们的路由状态
const RouterContext = React.createContext() //用于多层级组件之间的传值
export class BrowserRouter extends React.Component{
construtor(pros){
super(props)
this.history = createBrowserHistory(props)
//响应式数据,location改变时渲染对应的组件
this.state = { location : this.history.location }
}
//监听history中location改变事件
this.unListen = this.history.listen(location=>{
this.setState({ location })
})
//组件卸载时移除监听
componentWillUnmount(){
this.unListen && this.unListen()
}
}
render(){
return (
//要向下级传值
<ReactContext.Provider
//传值的对象
children = {this.props.children || null }
value = {{
history:this.history,
location:this.state.location
}}
>
)
}
实现Route
路由配置,匹配检测,内容渲染
//这里我们使用函数式组件
export function Route(props){
const ctx = useContext(ReactContext) //函数式组件中获取context的方法
const {path,components:Cmp} = props //获取参数
//这个组件负责渲染,所以需要根据location去获取对应的组件
const { location } = ctx
let match = path == location.pathname
//匹配到了就返回该组件
return match ? <Cmp />:null
}
实现Link
link负责跳转链接,本质是a标签,需要禁用a标签的默认行为。
//这里我们使用类组件
export class Link extends Component{
constructor(props){
super(props)
this.props = props
}
handleClick(event,history){
//先阻止a标签的默认行为,防止浏览器刷新
event.preventDefault()
//切换组件,会引发history.listen的回调事件触发,进而改变location,渲染对应的Route
history.push(this.prosps.to)
}
render(){
//<link to='/'>首页</Link>,to=/,children=首页
const {to,children} = this.props
return <ReactContext.Consumer> //类组件使用Consumer获取Context中的参数
context=>{
return <a href={to}
onclick={event=>this.handleClick(event,context.history)}>
{children}
</a>
}
</ReactContext.Consumer>
}
}