手写简易版Vue全家桶

231 阅读2分钟

Vue Router

写之前先分析 Vue Router 究竟实现了哪些核心功能?

  • 构建单页面应用
  • 根据url的变化能够展示不同内容
    1. router-view 组件以及 router-link 组件
    2. 响应url的变化渲染路由表中配置的内容

分析之后,我们将简单的实现一下它的核心功能,主要要做两件事情:构造一个 VueRouter 类,它负责处理路由选项,监控url变化并响应变化;将类封装成一个Vue插件,也就是实现它的 install 方法,在这里我们要实现路由注册,并注册router-view 组件和 router-link 组件。

实现VueRouter类

1. 初始化VueRouter

将路由配置保存到 VueRouter 实例中,监听 hashchange 事件,利用Vue提供的工具函数,将路由地址变为响应式变量(只要变量变化,引用了该变量的组件都会重新渲染)。

let _Vue

class VueRouter {
  // 传入routes路由表
  constructor(options) {
    this.$options = options
    // 提供路由表映射
    this.routerMap = new Map()
    options.routes.forEach(route => {
      this.routerMap.set(route.path, route)
    })
    // 利用Vue提供的工具函数将current变为响应式变量
    _Vue.util.defineReactive(this, 'current', window.location.hash.slice(1) || '/')
    window.addEventListener('hashchange', this.onHashChange.bind(this))

  }

  onHashChange() {
    this.current = window.location.hash.slice(1) || '/'
  }
}

// 插件安装
VueRouter.install = (Vue) => {
  // 引用 Vue 实例
  _Vue = Vue
}

2. 插件安装

在插件安装过程中,需要将 VueRouter 实例挂载到 Vue 实例中,然而使用过程中我们会发现 Vue.use(VueRouter) 出现在 new VueRouter(routes) 之前。也就是说,安装时还没有 VueRouter 实例。所以这里需要延迟挂载:

// 插件安装
VueRouter.install = (Vue) => {
  // 1.引用 Vue 实例
  _Vue = Vue
  // 2.在beforeCreate中挂载VueRouter实例
  Vue.mixin({
   beforeCreate() {
     if (this.$options.router) {
       Vue.prototype.$router = this.$options.router
     }
   }
  })
}

接着注册两个全局组件:

// 插件安装
VueRouter.install = (Vue) => {
  ...
  // 3. 注册全局组件
  Vue.component('router-link', {
    props: {
      to: {
        type: String,
        default: '/'
      },
    },
    render(h) {
      return h('a', {
        attrs: {
          href: `#${this.to}`
        }
      },this.$slots.default)
    }
  })
  
  Vue.component('router-view', {
    render(h) {
      // 找到匹配当前路由的组件
      const {routerMap, current} = this.$router
      const route = routerMap.get(current)
      let component = route ? route.component : null
      return h(component)
    }
  })
}

至此,我们实现一个超简单的路由插件。

Vuex

Vuex是一个集中式的状态管理插件,具体机制在此就不赘述。同样先分析我们需要实现的需求:

  • 实现一个插件:声明 Store 类,挂载$store
  • Store的具体实现:
    • 创建响应式的state,保存mutations、actions和getters
    • 实现commit根据⽤户传⼊type执⾏对应mutation
    • 实现dispatch根据⽤户传⼊type执⾏对应action,同时传递上下⽂
    • 实现getters,按照getters定义对state做派⽣

初始化

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

function install(Vue) {
  _Vue = Vue
  // 和VueRouter类似,使用混入绑定$store
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    }
  })
}

export default {
  Store,
  install
}

提供响应式的state

class Store {
  constructor(options = {}) {
    ...
    // 创建响应式的数据
    this._vm = new _Vue({
      data() {
        return {
          // $$ 不会被代理
          $$state: options.state
        }
      }
    })
  }
  
  get state() {
    return this._vm._data.$$state
  }

  set state(v) {
    console.error('please use replaceState api to reset state')
  }
}

实现getters

class Store {
  constructor(options = {}) {
    ...
    const computed =  {}
    this.getters = {}
    const store = this
    Object.keys(this._wrappedGetters).forEach(key => {
      const fn = store._wrappedGetters[key]
      computed[key] = function() {
        return fn(store.state)
      }

      // 定义只读的自定义属性
      Object.defineProperty(store.getters, key, {
        get: () => store._vm[key],
        set: () => {
          console.error('can not set value for getters')
        }
      })
    })
    // 创建响应式的数据
    this._vm = new _Vue({
      data() {
        return {
          // $$ 不会被代理
          $$state: options.state
        }
      },
      computed
    })
  }
  ...
}

实现mutation

class Store {
  ...
  // 修改state
  commit(type, payload) {
    // 获取type对应的mutations
    const entry = this._mutations[type]

    if (!entry) {
      console.error('unknown mutation')
      return
    }
    
    entry(this.state, payload)
  }
}

实现action

class Store {
  ...
  dispatch(type, payload) {
    // 获取type对应的actions
    const action = this._actions[type]

    if (!action) {
      console.error('unknown action')
      return null
    }
    
    return action(this, payload)
  }
}