天天看點

簡易Vue-Router源碼實作

Hash模式

1、hash即URL中#後面的部分。

2、如果網頁URL帶有hash,頁面會定位到id與hash一樣的元素的位置,即錨點

3、hash的改變時,頁面不會重新加載,會觸發hashchange事件,而且也會被記錄到浏覽器的曆史記錄中

4、vue-router的hash模式,主要就是通過監聽hashchange事件,根據hash值找到對應的元件進行渲染(源碼裡會先判斷浏覽器支不支援popstate事件,如果支援,則是通過監聽popstate事件,如果不支援,則監聽hashchange事件)

hash模式界面跳轉不重新整理

根據http協定所示,url中hash改變請求是不會發送到服務端的,不管你怎麼location跳轉,或者url上直接加上hash值回車,他都一樣固執,就是不和伺服器老大說。

但是我們的系統裡又引入了vue-router,其中hashchange這個事件監聽到了hash的變化,進而觸發了元件的更新,也就繪制出了相應的頁面

History模式

1、通過history.pushState修改頁面位址

2、當history改變時會觸發popstate事件,是以可以通過監聽popstate事件擷取路由位址

3、根據路由位址找到對應元件進行渲染

history模式,每次點選菜單導航會界面重新整理?

看一下正确的history模式下,首頁重新整理到顯示的整體流程:

1.将這個完整的url發到伺服器nginx

2.ngix需要配置用這個uri在指給前端index.html(因為根本沒有任何一個伺服器提供了這個url路由,如果直接通路的話就是404,是以就要指回給前端,讓前端自己去根據path來顯示)
location / {
   root   /usr/share/nginx/html/store;//項目存放的位址
   index  index.html index.htm;
   try_files $uri $uri/ /index.html;//history模式下,需要配置它
}
 是以try_files $uri $uri/的意思就是,比如http://test.com/example先去查找單個檔案example,如果example不存在,則去查找同名的檔案目錄/example/,如果再不存在,将進行重定向index.html(隻有最後一個參數可以引起一個内部重定向)
 凡是404的路由,都會被重定向到index.html,這樣就顯示正确了
 
3.此時nginx将這個請求指回了前端的index.html,index.html中開始加載js,js中已有vue-router的代碼,vue-router自動觸發了popstate這個事件,在這個事件回調中,繪制了這個path下對應的頁面元件
           

vue-router使用

1.注冊vue-router插件

import VueRouter from 'vue-router'
// 注冊VueRouter插件
// Vue.use方法 
// 1、如果傳入的是方法,則調用傳入的方法
// 2、如果傳入的是對象,則會調用插件的install靜态方法,并傳入Vue構造函數
Vue.use(VueRouter)
           

2.建立Router執行個體

// 建立路由表
const routes = [
  {
    path: '/home',
    component: Home,
  },
  {
    path: '/about',
    component: About,
  },
]
// 執行個體化路由對象
const router = new VueRouter({
  mode: 'hash',
  routes
})
           

3.Vue執行個體上挂載router執行個體

// 執行個體化Vue時,在執行個體上注冊router對象
new Vue({
  render: h => h(App),
  router
}).$mount('#app')
           

元件中使用

<router-view></router-view>
<router-link to="/home"><route-link>
// 通過this.$router擷取路由對象
           

總結,VueRouter需要做以下這些事情

1、實作install靜态方法

2、根據傳入的路由配置,生成對應的路由映射

3、給Vue執行個體挂載router執行個體

4、注冊全局元件和, router-view元件通過目前url找到對應元件進行渲染,并且url改變時,重新渲染元件,router-link則渲染為a标簽

5、通過currentUrl變量儲存目前url,并使資料變為響應式

6、監聽hashchange或popState事件,浏覽器記錄改變時重新渲染router-view元件

代碼實作

//1.實作一個插件
//2.要求必須要是一個install方法,将來會被vue調用
//3.兩個元件 router-link router-view
let Vue; //儲存Vue的構造函數,在插件中要使用
class VueRouter {
  constructor(options) {
    this.$options = options;
  }
}
//插件:實作install方法,注冊$router
//參數一:是Vue.use調用時傳入的(參數_Vue是vue構造函數)
VueRouter.install = (_Vue) => {
  Vue = _Vue;
  //   1.挂載$router屬性
  //this.$router.push()
  //全局混入(延遲下面的邏輯到router建立完畢并且附加到選項上時才執行)
  Vue.mixin({
    beforeCreate() {
      //注意此鈎子在每個建立執行個體的時候都會被調用
      //根執行個體才有該選項
      if (this.$options.router) {
        Vue.prototype.$router = this.$options.router;
      }
    },
  });
  //2.實作兩個元件:router-link,router-view
  //<router-link to="/">home</router-link> ==> <a href="/" target="_blank" rel="external nofollow" >home</a>
  Vue.component("router-link", {});
  Vue.component("router-view", {});
};
export default VueRouter;
           
為什麼要用混入方式寫?為了在元件建立的時候給vue執行個體的原型綁定這個router執行個體,是以咱們就可以在vue元件的地方使用this.$router

建立router-link和router-view

Vue.component("router-link", {
    props: {
      to: {
        type: String,
        required: true,
      },
    },
    render(h) {
      return h(
        "a",
        {
          attrs: {
            href: `#${this.to}`,
          },
        },
        this.$slots.default
      );
    },
  });
  Vue.component("router-view", {
    render(h) {
      let component = null;
      //暫時先不渲染任何内容
      return h(component);
    },
  });
           

監控URL變化

定義響應式的current屬性,監聽hashchang事件

class VueRouter {
  constructor(options) {
    this.$options = options;
    //把this.current變為響應式的資料 
    //将來資料發生變化,router-view的render函數能夠再次執行
    let initial = window.location.hash.slice(1) || "/";
    //把current變成響應式資料
    Vue.util.defineReactive(this, "current", initial);
   //将來資料一旦發生變化,router-view的render函數能夠重新執行
    this.current = "/";
    window.addEventListener("hashchange", () => {
      this.current = window.location.hash.slice(1) || "/";
      console.log("current", this.current);
    });
  }
}
           

動态擷取對應的元件,router-view元件

Vue.component("router-view", {
    render(h) {
      let component = null;
      let { current, $options } = this.$router;
      let routeItem = $options.routes.find((item) => item.path == current);
      routeItem && (component = routeItem.component);
      //擷取目前路由所對應的元件并将他渲染出來
      return h(component);
    },
});
           

繼續閱讀