Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue-Router实现原理 #25

Open
janeLLLL opened this issue Oct 20, 2020 · 0 comments
Open

Vue-Router实现原理 #25

janeLLLL opened this issue Oct 20, 2020 · 0 comments

Comments

@janeLLLL
Copy link
Owner

janeLLLL commented Oct 20, 2020

Vue Router实现原理

Vue Router实现原理

基础回顾

Ⅰ、创建路由

vue-router创建

  1. 注册路由插件,制定路由规则

  2. 创建router对象,导出

  3. 注册router对象。创建Vue实例的时候,传入router对象,Vue实例中被注入两个属性:

    $route:路由规则

    $router:路由相关的方法

  4. 创建路由组件的占位<router-view/>

  5. 创建链接<router-link/>

Ⅱ、动态路由传参

  • 路由懒加载
{
    path:'xxx/:id',
	//props:true,
    //开启props,会把URL中的参数传递给组件
    //在组件中通过props来接收URL参数
    name:'xxx',
    //命名式导航
	commponent:() => import('../xxx/xxx.vue')
}
  1. 通过当前路由规则,获取数据。强依赖路由。

    {{$route.params.id}}
    
  2. 父子组件传值的方式:路由规则中开启props传参。不依赖路由。

    {{id}}
    export default {
        props: ['id']
    }

Ⅲ、嵌套路由

layout.vue嵌套到index.vue/ details.vue中输出,使之拥有相同的头和尾

//index.js
//配置路由

import Layout from '@/commponents/Layout.vue'

const routes = [
    //嵌套路由:先加载Layout组件,再加载Index/Detail组件
    {
        path:'/',
        component:Layout,
        children: [
            //首页
            {
            	name: 'index',
            	path: '',//相对路径'/'
            	component: Index
            },
            //详情页
            {
                name: 'detail',
                path: 'detail/:id',
                props: true,
                component: () => import('@/views/Detail.vue')//'/Detail.vue'
            }
        ]
    }
]

Ⅳ、编程式导航

this.$router.push('/')
this.$router.push({name:'Home'})
this.$router.replace('/login')
//不会记录当前历史
this.$router.go(-2)

Hash模式和History模式

  • 都是客户端路由的实现方式

Ⅰ、区别

表现形式:

  • Hash:http://xxx.com/#/xxx?id=11很丑
  • History:http://xxx.com/xxx/222

原理:

  • Hash:基于锚点,以及onhashchange事件

    调用push方法会先判断当前浏览器是否支持window.pushState,再调用pushState改变地址栏;

    否则通过window.loaction改变地址栏

  • History:基于HTML5中的HistoryAPI

    • history.pushState()IE10以后才支持
    • history.replaceState()

    此模式下调用router.push(url)方法时,push方法内部会直接调用window.history.pushState,把url设置到浏览器的地址栏

    window.history.pushState不能触发popstate事件,当历史状态被激活的时候才会触发popstate

两者都是客户端修改URL,性能相差不大

Ⅱ、History模式

  • History模式是基于浏览器的History API实现的
  • 需要服务器支持
  • 单页应用中,服务端不存在http://www.xx.com/login会返回找不到页面
  • 在服务端应该除了静态资源外都返回单页面应用的index.html
//index.js
const routes = [
    {
        path:'*',
        name:'404',
        compontent: () => import('../xxx/404.vue')
    }
]

const router = new VueRouter({
    mode: 'history',
    routes
})

Ⅲ、node.js服务器配置

node配置的服务器app.js

//app.js
const path = require('path')//可以合并两个路径
//导入处理hitory模式的模块
const history = require('connect-history-fallback')
//导入express
const express = require('express')

const app = express()
//注册处理history模式的中间件
//app.use(history())
//处理静态资源的中间件,网站根目录../web
app.use(express.static(path.join(__dirname,'../web')))//处理静态资源

//开启服务器,端口是3000
app.listen(3000, () => {
    console.log("服务器开启,端口:3000")
})

中间件?

  • 服务端不存在http://www.xx.com/login会返回找不到页面,所以要在服务端配置支持
app.use(history())

node app.js

Ⅳ、nginx服务器配置

(不能有中文)

start nginx
nginx -s reload
nginx -s stop

实现自己的Vue Router

  • 前置知识:插件、混入、Vue.observable()、插槽、render函数、运行时和完整版的Vue

Vue.observable():可以使用Vue.observable()把任意对象转换成响应式对象,Vue.observable()返回的对象没有办法在模板中直接使用,但可以在h()函数中使用

Ⅰ、实现原理

Hash模式:

  1. URL中#后面的内容作为路径地址
  2. 监听hashchange事件
  3. 根据当前路由地址找到对应组件重新渲染

History模式:

  1. 通过history.pushState()方法改变地址栏
  2. 监听popstate事件
  3. 根据当前路由地址找到对应组件重新渲染

Ⅱ、分析

类图:

Ⅲ、代码实现

install

//./vuerouter/index.js
let _Vue = null

export default class VueRouter {
    static install() {
        if(VueRouter.install.installed){
            return 
        }
        VueRouter.install.installed = true//当前插件被安装了
        
        _Vue = Vue
        
        //_Vue.prototype.$router = this.$options.router
        //混入
        _Vue.mixin({
            beforeCreate(){
                //组件orvue实例
               if(this.$options.router) {
                  _Vue.prototype.$router = this.$options.router//只需要执行一次
                   this.$options.router.init()
               }
            }
        })
    }
}

/**
 * 1.判断当前插件是否重复被安装
 * 2.install静态方法接收了静态函数,将来要在vue的实例方法中使用这个函数,要把vue构造函数记录到全局变量中
 * 3.把创建vue实例时候传入的router对象注入到vue实例上
 */

构造函数

constructor (options) {
    this.options = options
    this.routeMap = {}
    this.data = _Vue.observable({
        current: '/'//存储当前路由地址
    })
    //三个所需要的属性
}

createRouteMap:把构造函数传过来的选项中的rules(路由规则)转换为键值对的形式存储到routerMap.

routerMap中存储的键的值就是路由中对应的组件。如果路由发生变化,很容易根据地址在routeMap中找到组件,并且渲染到视图上。

createRouteMap() {
    // 遍历所有的路由规则,把路由规则解析成键值对的形式,存储到routeMap中
    this.options.routes.forEach(route => {
        this.routeMap[route.path] = route.component
    })
}

initComponents

  1. 实现router-link组件
//参数vue为了减少和外部的依赖
initComponents (Vue) {
    vue.component('router-link',{
        props: {
            to: String,
        },
        template: '<a :href = "to"><slot></slot></a>'
    })
}

init:包装initComponentscreateRouteMap方法让外部使用方便

init() {
	this.createRouteMap()
    this.initComponents(_Vue)//传入vue的构造函数
}
  • 完整版本的Vue

Vue的构建版本

运行时版:不支持template模板,需要打包的时候提前编译

完整版:包含运行时和编译器,体积比运行时版大10K左右,程序运行的时候把模板转换成render函数

完整版性能不如运行时版本

vuecli默认使用运行时版本

Vue-cli中方法,runtimeCompiler:是否使用包含运行时编译器的Vue构建版本。设置为true后可以在Vue组件中使用template选项

创建./vue.config.js

module.exports = {
    runtimeCompiler: true,
}
  • 运行版本的Vue

关注:render函数怎么写

因为运行版本不支持template选项,需要修改initComponents

initComponents(Vue){
    Vue.component('router-link', {
        props: {
            to: String
        }
    },
    //h创建虚拟DOM
    render (h) {
        return h('a', {
            attrs: {
                herf: this.to//history模式
            }//设置DOM属性
        }, [this.$slots.default])
        //获取默认插槽设置到a标签里
    }
    //template:...
    )
}
  1. 实现router-view组件,并且使路由转跳都在客户端操作,页面不刷新,但地址栏发生变化
initComponents (Vue) {
    Vue.component('router-link', {
        props: {
            to: String
        }
    },
    render (h) {
        return h('a', {
            attrs: {
                herf: this.to
            },
            on: {
                click: this.clickHandler
            },//给a标签注册一个跳转功能
        }, [this.$slots.default])
        methods:{
            clickHander (e) {
                //1.改变浏览器的地址栏
                history.pushState({}, '', this.to)
               //2.把当前路径记录到data.current里
                this.$router.data.current = this.to
                e.preventDefault()
                //事件处理函数,调用默认行为
            }
        }
    }
    )
    
    const self = this
    Vue.component('route-view', {
        render (h) {
            //在render内部首先要找到路由地址,再根据当前路由的地址,在routeMap对象中找到路由地址对应的组件,再调用h函数帮这个组件转换成虚拟DOM,然后返回
            const component = self.routeMap[self.data.current]//获取路由地址
            return h(component)
        }
    })
}

initEvent:当地址变化的时候,改变页面元素的逻辑

init() {
	this.createRouteMap()
    this.initComponents(_Vue)
    this.initEvent()
}

initEvent () {
    window.addEventListener('popstate', ()=> {
        this.data.current = window.location.pathname
    })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant