简易实现VueRouter和Vuex

手动实现简易Vue-router
  • vue-router 包的使用

    • 导入import VueRouter from 'vue-router'

    • 在Vue上进行VueRouter plugin 注册 Vue.use(VueRouter)

    • 创建VueRouter 实例并导出 , 创建实例的时候传入配置项,routes是路由配置表

      • export default new VueRouter({
          mode: 'hash',
          base: process.env.BASE_URL,
          routes
        })
        
    • 最后创建Vue实例的时候,配置项中传入这个导出的VueRouter 实例

    • 项目中则使用router-linkrouter-view 即可简易使用

  • 根据上述vue-router包的使用, 可以自己实现简易vue-router

    • 创建一个VueRouter的类,该类的constructor传入一个配置项options,我们需要将这个配置项目放入实例上

    • VueRouter这个类实现一个install方法,因为Vue.use(xxx) 注册plugin的时候就会调用 传入plugin的install方法

    • install 方法接收一个参数,这个参数就是我们Vue这个类, 我们需要将这个Vue保存在这个模块设置的变量Vue上,之后在VueRouter这个类的constructor方法中我们需要使用 Vue.util.defineReactive 这个方法,将数据进行响应式化

    • 其次我们在install 方法中 利用Vue.mixin() 方法创建一个全局mixin, 这个mixin中 追入一个生命周期beforeCreate,这个每个组件实例被创建的时候,这个mixin都会执行,通过条件判断 this.$options.router 是否存在,如果存在说明这个这个组件就是根组件。ps: 只有根组件 new Vue 创建实例时,我们的配置项中才会传入router 这个实例。 我们将router实例挂在到Vue 的原型上,这样以后组件中就可以通过this.$router 进行一些路由操作

      • Vue.mixin({
          beforeCreate() {
            if (this.$options.router) {
              Vue.prototype.$router = this.$options.router
            }
          }
        })
        
    • 之后我们还需要全局注册上 router-linkrouter-view 这两个组件

      • router-link 主要就是render出一个a标签,他的href属性就是 # + 传递过来的to属性,名称就是默认插槽。ps: #拼接是为了使用hash 避免a标签跳转导致整个页面的刷新
      • router-view 则主要是想办法拿到路由配置表,然后 render 当前匹配下的组件即可
  • 整体代码

let Vue

class VueRouter {
  constructor(options) {
    const initial = window.location.hash.slice(1) || '/'
    this.$options = options

    // 让 current 变为响应式数据,一旦发生变化,则会导致render函数重新执行
    Vue.util.defineReactive(this, 'current', initial)

    window.addEventListener('hashchange', () => {
      this.current = window.location.hash.slice(1)
    })
  }
}

VueRouter.install = (_Vue) => {
  Vue = _Vue

  Vue.mixin({
    beforeCreate() {
      if (this.$options.router) {
        Vue.prototype.$router = this.$options.router
      }
    }
  })


  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
      // 想要拿到路由配置表
      // 由于前面全局注册了mixin 并且已经将router实例放到了Vue 的原型对象上。所以下面可以通过this.$router获取
      const routes = this.$router.$options.routes || []
      const route = routes.find(route => route.path === this.$router.current)

      if (route) {
        Component = route.component
      }


      return h(Component)
    }
  })
}

export default VueRouter
嵌套路由实现
  • router-view 深度标记
  • 路由匹配时获取代表深度层级的matched数组
   Vue.component('router-view', {
    render(h) {
      // 标记当前router-view 深度
      this.$vnode.data.routerView = true
      let depth = 0
      console.log(this.$router.matched);
      /**
       * 递归往当前router-view 组件父级查找,
       * 如果该router-view父级依然依然存在router-view组件,则这个router-view的深度depth需要+1
       */
      let parent = this.$parent
      while (parent) {
        const vnodeData = parent.$vnode && parent.$vnode.data
        if (vnodeData) {
          if (vnodeData.routerView) {
            // 说明当前parent是一个router-view
            depth++
          }
        }
        parent = parent.$parent
      }

      let Component = null

      // 想要拿到路由配置表。 
      // matched在VueRouter构造器中已处理,创建实例的时候会递归匹配,将符合要求的路由对应表存入matched
      const route = this.$router.matched[depth]
      
      if (route) {
        Component = route.component
      }


      return h(Component)
    }
  })
class VueRouter {
  constructor(options) { 
    this.$options = options
    this.current = window.location.hash.slice(1) || '/'
    Vue.util.defineReactive(this, 'matched', [])

    // match方法可以递归便利路由表,获取匹配关系数组
    this.match()

    window.addEventListener('hashchange', () => {
      this.current = window.location.hash.slice(1)

      this.matched = []
      this.match()
    })
  }


  match(routes) {
    routes = routes || this.$options.routes
    /**
     * 递归遍历路由表
     * 当前地址栏的路由地址current进行匹配排查。
     * 如果 current === '/' 那么只需要将route.path === '/' 的对应路由放入matched中即可
     * 如果  current === '/about/info'  那么就需要将 current 中包含的对应路由 /about 和 /about/info 都放入 matched中 
     * 因此需要 route.path !== '/' && this.current.indexOf(route.path) !== -1 条件判断
     * 然后判断 该route下是否存在children 并且children.length>0, 然后递归查找这个 route.children
     */
    for (const route of routes) {
      if (route.path === '/' && this.current === '/') {
        this.matched.push(route)
        return
      }
      // about/info
      if (route.path !== '/' && this.current.indexOf(route.path) !== -1) {
        this.matched.push(route)

        if (route.children && route.children.length > 0) {
          this.match(route.children)
        }
        return
      }
    }
  }
}
手动实现Vuex

Vuex 实际上也是vue 生态中的一个插件,其实类似于vue-router , vuex 我们也可以通过一个Store类来实现,不同的是 Store 创建实例的时候是

new Vuex.Store({
  ...
})
  
Vue.use(Vuex)  

到处Vuex 可以是一个对象, 包含属性 Store 和 install 方法,这样就满足vuex 的使用方式了。

// 使用
import Vue from 'vue'
import Vuex from './kvuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    counter: 0
  },
  mutations: {
    add(state) {
      state.counter++
    }
  },
  actions: {
    dispatchAdd({ commit }) {
      setTimeout(() => {
        commit('add')
      }, 1000);
    }
  },
  getters: {
    doubleCounter(state) {
      return state.counter * 2
    }
  },
  modules: {}
})

export default store

创建 Store 类和 install 方法

install方法主要做两件事

  1. 保存传入的Vue

  2. 创建mixin方法,在生命周期beforeCreate中将根组件传入的store实例保存到Vue到原型对象上,方便之后在组件中通过 $store.xxx 使用

const install = (_Vue) => {
  Vue = _Vue

  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    }
  })
}

Store类要做的事主要有将创建store实例时传递的参数进行保存。 将state 进行响应式化。

其次定义好commit和 dispatch 方法。注意由于这两个方法中有使用this, 因此定义的时候需要重新绑定this指针

再次就是实现getters。 思路就是先将getters取过来,然后通过绑定到创建的Vue实例 _vm 的computed上,从而实现计算属性

let Vue

class Store {
  constructor(options) {
    this._mutations = options.mutations
    this._actions = options.actions
    this._wrappedGetters = options.getters

    // 定义computed 选项
    const computed = {}
    this.getters = {}
    // { doubleCounter(state){} }
    const store = this
    Object.keys(this._wrappedGetters).forEach(key => {
      // 获取用户定义的getter
      const fn = store._wrappedGetters[key]

      // 转换为computed 可以使用的无参数形式
      computed[key] = function () {
        return fn(store.state)
      }

      // 为getters 定义只读属性
      Object.defineProperty(store.getters, key, {
        get() {
          return store._vm[key]
        }
      })
    })

    this._vm = new Vue({
      data: {
        $$state: options.state
      },
      computed
    })

    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)

  }

  get state() {
    return this._vm._data.$$state
  }

  set state(v) {
    throw new Error('please use replaceState to reset')
  }

  commit(type, payload) {
    const entry = this._mutations[type]
    if (!entry) {
      throw new Error('this mutations is not found')
    }

    entry(this.state, payload)
  }

  dispatch = (type, payload) => {
    const entry = this._actions[type]
    if (!entry) {
      throw new Error('this actions is not found')
    }

    entry(this, payload)
  }
}

const install = (_Vue) => {
  Vue = _Vue

  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    }
  })
}


export default {
  Store,
  install
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值