本系列文章将由浅慢慢深入,一步步带你领略react和vue的同工异曲之处,让你左手react,右手vue无忧。
前提要顾: 点击查看该系列专栏
众所周知,路由是前端必不可少的一部分,在实际业务中也是我们接触最多的一个模块。那其实不论 Vue 还是 React,他们实现路由的原理都大同小异,既通过 hash 和 history 这两种方式实现。
那我们简单了解一下他的实现原理吧。
hash 模式的原理主要是基于 onhashchange 事件去做文章:
window.onhashchange = () => {
let hash = location.hash
console.log(hash)
}
上图的 hash
打印出来的是 #
后面的路径(包括#
)。
由于 hash 的变化都会被浏览器记录下来,使得浏览器的前进后退都可以使用,将页面状态和 url 关联起来,尽管没有请求服务器,这就是路由的最初模样。SPA(单页面应用) 的标配。
hash 模式下,发起的请求也不会被 hash 值影响(http请求中),不会重新加载页面。
history 模式下的 url 就是正常的 url 比 hash 模式下的好看,但这也需要后台配置支持。
在 history 模式下用到了 onpopstate 这个事件:
window.onpopstate = function(event) {
alert("location: " + document.location + ", state: " + JSON.stringify(event.state))
}
通过浏览器提供的 history api,url 更加好看了,但是取而代之的是刷新时,如果服务器中没有相应的资源就可能会报 404,这是因为刷新了又去请求了服务器。
对原理有了简单的了解之后,我们来简单的看看 Vue router 吧。
作为当前国内最火热的框架 Vue,大家当然再熟悉不过了,他的基本使用我就不做过多叙述了。
在上面我们了解了路由的基本原理,我们可以通过这个原理来实现一个简单的 Vue 路由,所以我们简单梳理一下我们要做的功能点:
明确目标后,我们先新建一个路由类,并且将传进来的 options (路由配置)保存起来然后初始化:
class vueRouter {
constructor(options) {
// 配置选项
this.$options = options
// 路由映射关系
this.routeMap = {}
// 将 Vue 实例储存起来用于响应式
this.app = new Vue({
data() {
return {
current: '/'
}
}
})
}
}
vueRouter.install = (_Vue) => {
// 将 Vue 类存起来
Vue = _Vue
// 注册全局混入将该生命周期混入到全局用于初始化
Vue.mixin({
created() {
if(this.$options.router) {
this.$router = this.$options.router
// init 初始化路由
// this.$options 是 new Vue() 时传入的参数
this.$options.router.init()
}
}
})
}
初始化做完后,需要监听对应的回调事件用于响应我们的路由,并且注册对应的组件。
initEvent() {
// 监听浏览器的hashchange和load事件,使用bind改变this指向
window.addEventListener('hashchange', this.handleHashChange.bind(this))
window.addEventListener('load', this.handleHashChange.bind(this))
}
handleHashChange() {
// 获取#后面的部分赋值给app的current
this.app.current = location.hash.slice(1)
}
initRouteMap() {
// 路由映射关系对应
this.$options.routes.forEach(item => {
this.routeMap[item.path] = item
})
}
registerComponents() {
// router-link 用于点击跳转
Vue.component('v-router-link', {
props: {
to: String
},
render: function (h) {
return h('a', { attrs: { href: `#${this.to}` } }, this.$slots.default)
}
})
// router-view 展示当前路由对应组件即可
Vue.component('v-router-view', {
render: h => {
const com = this.routeMap[this.app.current].component
return h(com)
}
})
}
// 初始化方法
init() {
this.initEvent()
this.initRouteMap()
this.registerComponents()
}
// main.js
import Vue from 'vue'
import vueRouter from './vueRouter'
import xxx from './xxx'
import home from './home'
Vue.use(vueRouter)
export default new vueRouter({
routes:[{
path: '/home',
component: home
},
{
path: '/xxx',
component: xxx
}]
})
// home.vue
<template>
<div id='app'>
<v-router-link to='/xxx'>xxx</v-router-link>
<v-router-view />
</div>
</template>
我们知道 React 做为一个开放式的框架(不像Vue那样 Vue Router、Vuex等捆绑在一起),自由度是比较高的,没有像 Vue 那样教科书一般的配置,需要我们自己选择插件。这其实不乏有优秀的插件(这里只说路由相关插件)如:React router、react-router-dom等等。
由于笔者使用 react-router-dom 比较多,这里拿他来做文章。
react-router-dom 是利用了 Context API,通过上下文对象将当前路由信息对象注入到<Router>
组件,所以<Router>
组件渲染的内容就是 Context API 提供的 Provider,然后接收<Router>
组件中的当前路由信息对象。
与 Vue Router 相似我们同样需要监听 url 的变化在对应回调中拿到相应的数据。不同的是 react-router-dom 中需要创建上下文对象来供我们全局使用,通过 Context 来传递我们想要的数据,简单梳理一下:
创建一个上下文并导出。
//context.js
import React from "react"
export default React.createContext()
将当前路由注入上下文,并监听 url 控制渲染。
// hashRouter.js
import React, { Component } from "react"
import Context from "./context" // 引入上下文对象
export default class hashRouter extends Component {
constructor(props) {
super(props)
// 将状态存入 state 中方便修改。
this.state = {
location: {
// 获取浏览器地址栏中的hash值,如果不存在则默认为"/"
pathname: window.location.hash.slice(1) || "/",
query: undefined
}
// 用于实现当前路由的切换
history: {
push: (to) => {
if (typeof to === "object") {
// 如果参数是对象的情况:
let { pathname, query } = to
window.location.hash = pathname // 更新浏览器hash值,触发浏览器hashchange事件
this.state.location.query = query // 更新query
} else {
// 如果参数是路径:
window.location.hash = to // 更新浏览器hash值
}
}
}
}
}
componentDidMount() {
window.addEventListener("hashchange", () => { // 监听浏览器地址栏hash值变化
this.setState({ // 当hash值变化后更新当前路由信息, HashRouter组件内的子组件Route将会重新渲染
location: {
...this.state.location,
pathname: window.location.hash.slice(1) // 更新pathname
}
})
})
}
render() {
// 当前路由对象
const currentRoute = {}
return (
// 使用 Provider 组件将当前路由信息对象注入上下文中,以便其 Route 等子组件能够获取到当前路由信息
<Context.Provider value={currentRoute}>
{this.props.children}
</Context.Provider>
)
}
}
在上述代码完成之后,我们就可以开始封装 Route 组件了,通过 Context 获取当前路由信息,将匹配路径的对应组件渲染出来。
// Route.js
import React, { Component } from "react"
import context from "./context"
export default class Route extends Component {
static contextType = context
render() {
const currentRoutePath = this.context.location.pathname // 从上下文中获取到当前路由的路径
const { path, component:Component } = this.props // 获取给 Route 传递的 props 属性
const props = {
...this.context
}
// 匹配路径
if (currentRoutePath === path) {
return (
<Component {...props}></Component>
)
}
return null
}
}
// router.js
import Route from './Route.js'
const router = (
<Route
path='/'
component = () => import('./home.jsx')
/>
<Route
path='/xxx'
component = () => import('./xxx.jsx')
/>
)
export default router
// main.js
import React from 'react'
import router from './router.js'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
// init something ...
}
}
render() {
<div>
{router}
</div>
}
}
以上的 Vue 和 React 实现的 router 只是最基本的路由功能,如 Vue Router 中的 keepalive、路由守卫等一些不错的功能没有去叙说。如 react-router-dom 中的 Route 组件的 exact(精确匹配)、Link(类似a标签)、Redirect(重定向)等一些不错的辅助功能也没有叙说,且只说了 hash 模式(history 模式就是监听另一个事件,逻辑都差不多)
都看到这里了,不点个赞再走吗?
欢迎在下方给出你的建议和留言。
关注公众号:饼干前端,获取更多前端知识~
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有