美文网首页前端那些事儿
02全家桶(Vue-router&&Vuex)源码实现

02全家桶(Vue-router&&Vuex)源码实现

作者: LM林慕 | 来源:发表于2020-07-04 12:03 被阅读0次

    Vue-router

    Vue Router 是 Vue.js 官方的路由管理器,它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。

    安装:

    vue add router
    

    核心步骤:

    1. 使用 vue-router 插件,router.js

      import Route from 'vue-router'
      Vue.use(Router)
      
    2. 创建 Router 实例,router.js

      export default new Router({...})
      
    3. 在根组件上添加该实例,main.js

      import router from './router'
      new Vue({
        router
      }).$mount('#app')
      
    4. 添加路由视图,App.vue

      <router-view></router-view>
      
    5. 导航

      <router-link to='/'>Home</router-link>
      <router-link to='/about'>About</router-link>
      

    vue-router 源码实现

    单页面应用程序中,URL 发生变化,不刷新并显示对应的视图内容

    需求分析

    1. spa 点击链接不能出刷新页面

      • hash #xxx

      • history api

    2. 事件 hashchange,通知 router-view 更新

      • 利用 Vue 数据响应式

      • 制作一个响应式数据表示当前 URL,在 router-view 的 render 函数使用它

    任务

    1. 实现一个插件

      • 实现 VueRouter 类

      • 实现 install 方法

    2. 实现两个全局组件

      • router-link

      • router-view

    创建 kvue-router.js

    // 插件
    
    let KVue
    
    // 1. 实现一个install方法
    class VueRouter {
      constructor(options) {
        this.$options = options
    
        // 响应式数据
        const initial = window.location.hash.slice(1) || '/'
        KVue.util.defineReactive(this, 'current', initial)
    
        // this.current = '/'
    
        // 监听事件
        window.addEventListener('hashchange', this.onHashChange.bind(this))
        window.addEventListener('load', this.onHashChange.bind(this))
    
        // 缓存path和route映射关系
        this.routeMap = {}
        this.$options.routes.forEach(route => {
          this.routeMap[route.path] = route
        })
      }
    
      onHashChange () {
        this.current = window.location.hash.slice(1)
        console.log(this.current)
      }
    }
    
    // 形参是Vue构造函数
    KVueRouter.install = function (Vue) {
      // 保存构造函数(独立的包,不希望将vue也打包进去)
      KVue = Vue
    
      // 1. 挂载$router
      Vue.mixin({
        beforeCreate () {
          // 全局混入,将来在组件实例化的时候才执行
          // 此时Vue实例已经存在了
          // this指的是组件实例
          if (this.$options.router) {
            Vue.prototype.$router = this.$options.router
          }
        }
      })
    
      // 2. 实现两个全局组件
      Vue.component('router-link', {
        props: {
          to: {
            type: String,
            required: true
          }
        },
        // h是createElement函数
        render (h) {
          // <a href='#/xxx'></a>
          // h(tag,props,children)
          // jsx语法也可以用
          // return <a href={'#' + this.to}>{this.$slots.default}</a>
          return h(
            'a',
            {
              attrs: {
                href: '#' + this.to
              }
            },
            this.$slots.default
          )
        }
      })
      // rouetr-view 是一个容器
      Vue.component('router-view', {
        render (h) {
          // 1. 获取路由器实例
          // const routes = this.$router.$options.routes
          // const current = this.$router.current
          // const route = routes.find(route => route.path === current)
          // const comp = route ? route.component : null
    
          const { routeMap, current } = this.$router
          const comp = routeMap[current] ? routeMap[current].component : null
    
          // 获取路由表 eg:'/'===home组件
          // return h('div','view')
          return h(comp)
        }
      })
    }
    
    export default KVueRouter
    

    Vuex 原理

    Vuex 集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以可预测的方式发生变化。

    Vuex

    整合 vuex

    vue add vuex
    

    核心概念

    • state 状态、数据
    • mutations 更改状态的函数
    • actions 异步操作
    • store 包含以上概念的容器

    状态 - state

    state保存应用状态

    export default new Vuex.Store({
      state: { counter:0 },
    })
    

    状态变更 - mutations

    mutations用于修改状态,store.js

    export default new Vuex.Store({
      mutations: {
        add (state) {
          state.counter++
        }
      }
    })
    

    派生状态 - getters

    从state派生出新状态,类似计算属性

    export default new Vuex.Store({
      getters: {
        doubleCounter (state) { // 计算剩余数量
          return state.counter * 2;
        }
      }
    })
    

    动作 - actions

    添加业务逻辑,类似于 controller

    export default new Vuex.Store({
      actions: {
        add ({ commit }) {
          setTimeout(() => {
            commit('add')
          }, 1000);
        }
      }
    })
    

    测试代码:

    <p @click="$store.commit('add')">counter: {{$store.state.counter}}</p>
    <p @click="$store.dispatch('add')">async counter: {{$store.state.counter}}</p>
    <p>double:{{$store.getters.doubleCounter}}</p>
    

    原理解析

    任务

    1. 实现⼀个插件:声明Store类,挂载$store

    2. Store具体实现:

      • 创建响应式的 state,保存 mutations、actions 和 getters

      • 实现 commit 根据用户传入 type 执行对应 mutation

      • 实现 dispatch 根据用户传入 type 执行对应 action,同时传递上下文

      • 实现 getters,按照 getters 定义对 state 做派生

    代码实现:

    let KVue
    
    // 实现 Store 类
    class Store {
      constructor(options) {
        // 保存 mutations
        this._mutations = options.mutations
    
        // 保存 actions
        this._actions = options.actions
    
        // 绑定 this 到 store 实例
        // 绑定 commit 上下⽂否则 action 中调⽤ commit 时可能出问题!!
        // 同时也把 action 绑了,因为 action 可以互调
        const store = this
        const { commit, action } = store
        this.commit = function boundCommit (type, payload) {
          commit.call(store, type, payload)
        }
        this.action = function boundAction (type, payload) {
          return action.call(store, type, payload)
        }
    
        // getters
        // 1. 遍历用户传入 getters 所有 key,动态赋值,其值应该是函数执行结果
        // 2. 确保它是响应式的,Object.defineProperty(this.getters,key,{get(){}})
        // 3. 缓存结果,可以利用 computed    
        let computed = {}
        options.getters && this.handleGetters(options.getters, computed)
    
        // 响应式的 state
        this._vm = new KVue({
          data: {
            $$state: options.state
          },
          computed
        })
      }
    
      handleGetters (getters, computed) {
        this.getters = {}
        Object.keys(getters).forEach(key => {
          computed[key] = () => getters[key](this.state)
          Object.defineProperty(this.getters, key, {
            get: () => getters[key](this.state),
            enumerable: true,
          })
        })
      }
    
      get state () {
        return this._vm._data.$$state
      }
      set state (v) {
        console.error('请重新设置' + v + '的名称')
      }
    
      // commit(type,payload):执行 mutation,修改状态
      commit (type, payload) {
        // 根据 type 获取对应的 mutations
        const entry = this._mutations[type]
        if (!entry) {
          console.error('这是未知的 mutation 类型')
          return
        }
        entry(this.state, payload)
      }
    
      // dispatch(type,payload)
      dispatch (type, payload) {
        const entry = this._actions[type]
    
        if (!entry) {
          console.error('这是未知的 action 类型')
          return
        }
    
        return entry(this, payload)
      }
    }
    
    // 实现插件
    function install (Vue) {
      KVue = Vue
    
      // 混入
      Vue.mixin({
        beforeCreate () {
          if (this.$options.store) {
            Vue.prototype.$store = this.$options.store
          }
        }
      })
    }
    
    // 此处导出的对象理解为 Vuex
    export default { Store, install }
    

    相关文章

      网友评论

        本文标题:02全家桶(Vue-router&&Vuex)源码实现

        本文链接:https://www.haomeiwen.com/subject/glbyqktx.html