[學(xué)習(xí)vue]全家桶的原理和實(shí)現(xiàn)

在正式進(jìn)入之前先拋出幾個(gè)問題:
1 vue-router能不能放在react中使用?
2 use vueRouter的時(shí)候發(fā)生了什么?
3 為什么要把router作為一個(gè)選項(xiàng)放在new vue 中?
4 為什么router-link router-view 不需要注冊(cè),就可以直接使用?
答案就在文章中~~

vue-router

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,讓構(gòu)建單頁面應(yīng)用變得易如反掌。主要解決的問題就單頁面應(yīng)用的導(dǎo)航問題。

router的任務(wù)分析

  • 解析routers選項(xiàng)
  • 監(jiān)控url變化
    html5 history api /login
    hash xx.html#login
//vue-router.js
//聲明插件:vue插件需求實(shí)現(xiàn)一個(gè)install靜態(tài)方法
let Vue; //保存vue構(gòu)造函數(shù)引用 目的是不用吧vue打包進(jìn)去
class KVueRouter{
}
//參數(shù)是vue構(gòu)造函數(shù)
KVueRouter.install = function(_vue){
    Vue = _Vue;
    //實(shí)現(xiàn)一個(gè)混入
    Vue.mixin({
          beforeCreate(){
               //獲取KVueRouter實(shí)例并掛載到Vue.prototype
               if(this.$options.router){
                  // 在跟組件beforeCreate時(shí)執(zhí)行一次且只會(huì)執(zhí)行一次
                   Vue.prototype.$router = this.$options.router;
               }
         }
    })
}

這時(shí)候有一個(gè)疑問,為什么要寫把router掛載的配置寫在混入里,而不是直接寫在install方法呢?
這是因?yàn)楹蛯?shí)例的關(guān)系很大,我們先執(zhí)行了Vue.use的方法,其實(shí)是執(zhí)行了插件的install的方法,但是這時(shí)候,實(shí)例還不存在呢,那怎么辦呢,我們只好退而求其次,把代碼延后執(zhí)行,延后到當(dāng)beforeCreate的時(shí)候,才執(zhí)行。
接下來我們需要注冊(cè)兩個(gè)全局組件 router-view 和 router-link

...
Vue.component('router-link',{})
Vue.component('router-view',{})

接下來來完成核心任務(wù)

class KVueRouter{
         //解析routes
         //監(jiān)聽事件
         //聲明組件
    constructor(options){
          this.$options = options;
          this.routeMap = {};  // {'/index': {component: Index,...}}
          //當(dāng)前url需要響應(yīng)式的
          this.app = new Vue({
               data : { current : '/'}
          })
    }

    //初始化
   init(){
      //監(jiān)聽事件
       this.bindEvents();
      //解析routes
       this.createRouteMap();
       //聲明組件
       this.initComponent();
   }
   bindEvents(){
        window.addEventListener('hashchange', this.onHashchange.bind(this))
   }
   onHashchange(){
       this.app.current = window.location.hash.slice(1) || '/'
   }
   createRouteMap(){
      //遍歷用戶配置路由數(shù)組
      this.$options.routes.forEach(route => {
            this.routeMap[route.path] = route;
      })
   }
  initComponent(){
      //轉(zhuǎn)換目標(biāo): <a href = '/'>xx</a>
      // <router-link to = '/'>
     Vue.component('router-link', {
           props: {
                 to: String
           },
           render(h){
                 // h(tag, data,children)
                return h( 'a' , {
                      attrs:{href : '#' + this.to}
                }, [ this.$slots.default ]) // 這里還可以放其他的具名插槽 甚至作用域插槽 
               // 也可以使用jsx
           }
     })
  }
}

最后執(zhí)行一下install方法,把KVueRouter導(dǎo)出一下

export default KVueRouter

這時(shí)候可以引入寫好的文件,來調(diào)試一下了

//router.js
import Vue from 'vue'
// import Router from 'vue-router'
import Router from './kvue-router'
import Home from './views/Home.vue'

// 1.應(yīng)用插件:做了什么?
Vue.use(Router) // use執(zhí)行了插件install()

// 2.創(chuàng)建Router實(shí)例
export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    }
  ]
})

在main.js中掛載router

//main.js
import router from './router'
new Vue({
  router, // 配置router實(shí)例
  render: h => h(App),
}).$mount("#app");

//app.vue
<div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view></router-view>

這時(shí)候我們可以實(shí)現(xiàn)路由跳轉(zhuǎn)了,但是并不能真正的加載對(duì)應(yīng)的組件,就差渲染內(nèi)容組件了
router-view 這個(gè)組件負(fù)責(zé)實(shí)現(xiàn)渲染組件工作,我們拿出我們要渲染的component

   // 獲取path對(duì)應(yīng)的Component將它渲染出來
    Vue.component("router-view", {
        render: (h) => {
            const Component = this.routeMap[this.app.current].component;
            return h(Component)
        }
    })

另外vue也像我們暴露了一個(gè)可以定義一個(gè)響應(yīng)式數(shù)據(jù)的方法

const initial = window.location.hash.slice(1) || ‘/’
Vue.util.defineReactive(this,’current’,initial)

接下來,我們來總結(jié)一下
關(guān)于實(shí)現(xiàn)路由,我們要做的第一件事是實(shí)現(xiàn)一個(gè)插件install(),這個(gè)install方法,會(huì)把vue的構(gòu)造函數(shù)傳進(jìn)來,拿到vue的構(gòu)造函數(shù)之后,我們就可以做很多事情,接下來我們另一個(gè)要實(shí)現(xiàn)的kvuerouter里,監(jiān)聽事件,事件發(fā)生變化以后,我們要做的事情把this.app的current的改成新的hash,但是為什么修改完新的hash之后,會(huì)在router-view把對(duì)應(yīng)的組件渲染出來呢,原因是只要render函數(shù)里面用到了某個(gè)響應(yīng)式的數(shù)據(jù),這個(gè)數(shù)據(jù)發(fā)生變化了,我們的組件就會(huì)重新執(zhí)行render,這就是典型的依賴收集,意思就是說render函數(shù)里只要用的data里的東西,就會(huì)產(chǎn)生依賴,編輯器在執(zhí)行render函數(shù)的時(shí)候,會(huì)先執(zhí)行依賴收集的過程,先把依賴全部找到,vue里的current只要改變,和它相關(guān)的組件都會(huì)發(fā)生改變,也就會(huì)導(dǎo)致router-view的component的重新執(zhí)行,界面就渲染了。
該示例沒有解決嵌套路由的問題,我們可以參考一下官方的文檔。

vuex數(shù)據(jù)管理

vuex是一個(gè)專門為vue.js應(yīng)用開發(fā)的狀態(tài)管理模式,集中式存儲(chǔ)管理應(yīng)用所有組件的狀態(tài)。它是一個(gè)單項(xiàng)數(shù)據(jù)流的設(shè)計(jì)思想,它為了讓數(shù)據(jù)可控,數(shù)據(jù)可追蹤,設(shè)計(jì)出這樣一個(gè)單項(xiàng)數(shù)據(jù)流, 我們?cè)趯?shí)踐的時(shí)候,也要避免同時(shí)被父子組件操作的情況,維持這樣一個(gè)單向的關(guān)系,怎么去維系呢,我們把一些通用的全局的數(shù)據(jù),把他抽象到一個(gè)store里去保管,只能用不能改,如果想改數(shù)據(jù),只能commit一個(gè)mutaions,或者dispath一個(gè)actions,讓actions去commit一個(gè)mutaions,而且在vuex 里面必須實(shí)現(xiàn)一個(gè)數(shù)據(jù)的響應(yīng)式,實(shí)現(xiàn)的方式也是利用了vuex的構(gòu)造初始化的時(shí)候做了響應(yīng)式。


vuex

核心概念

state狀態(tài),數(shù)據(jù)

  • mutations更改狀態(tài)的函數(shù)
  • actions異步操作
  • store包含以上概念的容器

狀態(tài)和狀態(tài)的變更

state保存數(shù)據(jù)狀態(tài),mutations用于修改狀態(tài),store.js

export default new Vuex.Store({
      state: {count : 0},
      mutations:{
            increment(state){
                 state.count += 1;
            }
      }
})

vuex的任務(wù)分析

  • 實(shí)現(xiàn)插件: $store掛載
  • 實(shí)現(xiàn)store: 解析vuex配置,持有state,實(shí)現(xiàn)dispatch,commit,getters
  • 借助vue實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式
//kvuex.js
let Vue;
class Store {
  // 持有state,并使其響應(yīng)化
  // 實(shí)現(xiàn)commit和dispatch兩個(gè)方法
  constructor(options) {
    //   數(shù)據(jù)響應(yīng)式
    // this.state是Vue實(shí)例,訪問this.state.count
    this.state = new Vue({ data: options.state });

    this.mutations = options.mutations;
    this.actions = options.actions;
    this.getters = options.getters;

    // bind this
    this.commit = this.commit.bind(this);
    this.dispatch = this.dispatch.bind(this);
    this.getters = this.getters.bind(this);
  }

  //   實(shí)現(xiàn)commit:可以修改state中的數(shù)據(jù)
  commit(type, arg) {
    this.mutations[type](this.state, arg);
  }

  dispatch(type, arg) {
    return this.actions[type](this, arg);
  }

 getters(getters){
     //遍歷getters選項(xiàng),為this.getters定義property
     //屬性名就是選項(xiàng)中的key ,只需定義get函數(shù)保證只讀性
    Object.keys(getters).forEach(key =>{
        Object.defineProperty(this.getters, key, {
           get : () =>{
               return getters[key](this.state)
           }
       })
   })
 }
// 聲明插件install
// _Vue是形參:Vue構(gòu)造函數(shù),use會(huì)把它傳進(jìn)來
function install(_Vue) {
  Vue = _Vue;

  Vue.mixin({
    beforeCreate() {
      // this指的是組件實(shí)例
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store;
      }
    },
  });
}

// 導(dǎo)出Vuex
export default { Store, install };

vuex和vuerouter的實(shí)現(xiàn)思想大致相同,以上在不考慮代碼健壯性的前提下,來實(shí)現(xiàn)核心思想的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容