router

橙三吉。 提交于 2020-03-06 17:21:13
### Vue.js 

## 一、 课堂目标

1. 掌握前端单页应用的原理
2. 掌握vue-router工作机制
3. 手写vue-router


## 二、 知识点

### 2.1 什么是路由?前端有哪些路由?他们有 哪些特性?
#### 2.1.1 History API

#### 2.1.2 hash 模式
hash 路由模式是这样的:http://xxx.abc.com/#/xx。 有带#号,后面就是 hash 值的变化。 改变后面的 hash 值,它不会向服务器发出请求,因此也就不会刷新页面。并且每次 hash 值发生改变的时候,会触发 hashchange 事件。因此我们可以通过监听该事件,来知道 hash 值发生了哪些变化。比如我们可以如下简单的监听:

function hashAndUpdate () { // todo 匹配 hash 做 dom 更新操作 }window.addEventListener('hashchange', hashAndUpdate);

我们先来了解下 location 有哪些属性,如下

* location.href  
 完整的 url
* location.protocol  
 当前 URL 的协议,包括 :; 比如 https: 
* location.host    
 主机名和端口号,如果端口号是 80(http)或 443(https), 那就会省略端口号,比如www.baidu.com:8080  

* location.hostname   
 主机名:比如:www.baidu.com 
* location.port  
 端口号;比如 8080
* location.pathname  
url 的路径部分,从 / 开始; 比如 https://www.baidu.com/s?ie=utf-8,那么 pathname = '/s'了
* location.search  
查询参数,从?开始;比如 https://www.baidu.com/s?ie=utf-8 那么 search = '?ie=utf-8'
* location.hash  
 hash 是页面中的一个片段,从 # 开始的,比如 https://www.baidu.com/#/a/b 那么 返回值就是:"#/a/b"

改变 hash 不会触发页面跳转,因为 hash 链接是当前页面中的某个片段,所以如果 hash 有变化,那么页面将会滚动到 hash 所连接的位置。但是页面中如果不存在 hash 对应的片 段,则没有任何效果。比如 a 链接。这和 window.history.pushState 方法类似,都是不 刷新页面的情况下更改 url。如下也可以看到操作并没有刷新 url,如下演示:


#### 2.1.3 hash 和 pushState 对比有如下缺点:

1. hash 只能修改 url 的片段标识符的部分。并且必须从#号开始,但是 pushState 且能修 改路径、查询参数和片段标识符。pushState 比 hash 更符合前端路由的访问方式,更加优 雅(因为不带#号)。 
2. hash 必须和原先的值不同,才能新增会话浏览历史的记录,但是 pushState 可以新增相 同的 url 的记录,如下所示:

#### 2.1.4 如何实现简单的 hash 路由?

实现 hash 路由需要满足如下基本条件: 
1. url 中 hash 值的改变,并不会重新加载页面。 
2. hash 值的改变会在浏览器的访问历史中增加一条记录,我们可以通过浏览器的后退,前进按钮控制 hash 值的切换。 
3. 我们可以通过 hashchange 事件,监听到 hash 值的变化,从而加载不同的页面显示。 

触发 hash 值的变化有 2 种方法: 

第一种是通过 a 标签,设置 href 属性,当点击 a 标签之后,地址栏会改变,同时会触发 hashchange 事件。比如如下 a 链接: <a href="#/test1">测试 hash1</a> 

第二种是通过 js 直接赋值给 location.hash,也会改变 url,触发 hashchange 事件。 location.hash = '#/test1';

### 2.1 新建工程
// vue create RMrouter   vue-cli 4 
vue init webpack RMrouter  vue-cli 2

### 2.2 熟悉vue自身插件的使用方式

### 2.3 了解访问器属性

```javascript
var obj = {};
obj._name='1'; //内部属性,不能被改变
Object.defineProperty(obj,'currentPath',{
    get(){
        return this._name
    },
    set(val){
        this._name = val
    }
})  
```
### 2.3 了解构造函数的静态属性

```javascript

class Laney{ 
    static test=1; 
    static fun1(){ console.log('opp')}
}
console.log(Laney.test)
Laney.fun1()

```

### 2.4 了解createElement创建虚拟dom

```javascript

//第三种写法:render模式,性能最优
     new Vue({
        el:'#app',
        router:VueRouter,
        data:{
            hello:'hello',
            msg: 'Welcome to ruanmou'
        },
        //创建虚拟Dom,不用组件
        // render(createElement){
        //     return createElement('div',{
        //         id: "app1",
        //         style:{
        //             color:'red'
        //         }
        //         },[
        //             createElement('h1',this.msg),
        //             createElement('span','world')
        //         ])
        // },
        //使用组件, 利用render函数渲染
        render:function(h){
                return h(App);
         },
        //  render:h => h(App)
        mounted(){
            console.log(this);
        }
    });
    
```

### 2.5了解编写vue插件机制

Vue.use
用户执行Vue.use的时候,实际执行的是模块的install方法,会把Vue的实例传递进去,比如咱们整个字符串
```javascript

let Vue;
class RMRouter {
    static install(_Vue){
        Vue=_Vue;
        Vue.mixin({
            beforeCreate() {
                const options=this.$options;
                console.log(options)
                Vue.prototype.$rmrouter='晚上好'
            }
        })
    }
 }

export default RMRouter;

```

### 2.4 单页页面
#### 2.4.1 hash模式
使用url#后面的锚点来区分组件,hash改变的时候,页面不会重新加载,只会触发onhashchange事件

#### 2.4.2 hishtory模式

hash的url略丑,使用mode:history,这种模式充分利用了html5 history interface 中新增的 pushState() 和replaceState() 方法。这两个方法应用于浏览器记录栈,
在当前已有的 back、forward、go 基础之上,它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的 URL ,但浏览器不会立即向后端发送请求。

### 2.5 上代码

```javascript

//封装插件
let Vue;
// var obj = {};
// obj._name='1'; //内部属性,不能被改变
// Object.defineProperty(obj,'currentPath',{
//     get(){
//         return this._name
//     },
//     set(val){
//         this._name = val
//     }
// })  
class RMRouter {
    static install(_Vue){
        Vue = _Vue; 
        Vue.mixin({
            beforeCreate() {
                Vue.prototype.$brouter = '我是路由,我来了'
                if(this.$options.router) {
                    //启动路由
                    //this 是Vue的实例化对象
                    Vue.prototype.$krouter = this.$options.router;
                    this.$options.router.init()
                }
            }
        })
    }
    constructor(options){
        // 方式一:
        // options._curTest = '/';
        // Object.defineProperty(options,'current',{
        //     get(){  
        //         return this._curTest
        //     },
        //     set(val){
        //         this._curTest = val
        //     }
        // });

        this.$options = options;
        this.routerMap={};
        //  路由  --  组件
        // /test1   -  test1.vue
        // 使用vue相应机制,路由切换的时候, 做一些相应
         
        //方式一:
        this.app = new Vue({
            data(){
                return {
                    //当前路由
                    current:'/'
                }
            }
        })  
         //方式二:
        this.$options.current = '/';

    }
    init(){
        //1. 监听hashchange事件
            this.initEvents();
        // 2. 处理路由 routerMap
           this.createRouterMap();
        // 3. 初始化组件 router-view, router-link
            this.initComponent();
        // 4. 路由守卫
    }
     //1. 监听hashchange事件
    initEvents(){
        window.addEventListener('hashchange',this.onHashChage.bind(this),false)
        window.addEventListener('load',this.onHashChage.bind(this),false)
    }
    onHashChage(e){
        // this -- 
        console.log(e);
        console.log('我监听到路由了');
       //获取当前的hash
        let hash = window.location.hash.slice(1) || '/';
        this.app.current = hash;    //方式一:
        // this.$options.current = hash;   //方式二:
  
    }
    createRouterMap(){
        this.$options.routes.forEach(item=>{
            this.routerMap[item.path] = item.component;
        })
    }
    // 3. 初始化组件 router-view, router-link
    initComponent(){
        Vue.component('router-view',{ 
            render:h=>{
                console.log('this.app.current',this.app.current);  //方式一:
                // console.log('this.$options.current',this.$options.current);  //方式二:
                // 只要当前的路由hash变化, 这里可以坚挺到
                const component = this.routerMap[this.app.current]; //方式一:
                // const component = this.routerMap[this.$options.current].component;  //方式二:
                return h(component);
            }
        });
        Vue.component('router-link',{
            // props:['to'],
            props:{
                to:String
            },
            render:function(h){
                // h-- createElemnt
                //  3个参数
                //  dom节点名
                //  属性
                //  子元素
                return h('a',{
                    attrs:{
                        href:'#'+this.to
                    }
                },[this.$slots.default])
            }

             // template最终也是转换成render来执行
             // 需要compile
             // template:"<a :href='to'><slot></slot></a>"

        });
    }
}

export default RMRouter;

```
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!