前端那些事儿

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

2020-07-04  本文已影响0人  LM林慕

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

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 }
上一篇下一篇

猜你喜欢

热点阅读