vuex vue-router简化版

最近在看vuexvue-router的源码,代码还是蛮复杂的,但是我只想了解其核心原理,因此,不断地删代码,整理出了简化版

比如vuex,有时我仅仅是想要支持响应式的全局变量

因为vuex的使用会增加额外的概念,有时候既不想用vuex,也不想在组件中频繁的传参,也不想用bus,因此,我只把vuex中的stategetters抽取了出来,用法跟vuex基本保持一致,代码只有60来行,压缩后体积由10k降至不到2k

let store
let Vue
export default class Store {
  constructor (options = {}) {
    store = this
    const {
      state = {},
      getters = {}
    } = options
    if (!Vue) throw new Error('请先调用install安装')
    store.init(state, getters)
  }
  init (state, getters) {
    store.getters = {}
    const computed = {}
    // 将getters转换成computed计算属性,这样后续通过store.getters.xxx就可以访问到了
    Object.keys(getters).forEach(gettersKey => {
      const fn = getters[gettersKey]
      computed[gettersKey] = () => fn(store.state, store.getters)
      // 定义访问store.getters上key实际上是在store._vm上拿
      Object.defineProperty(store.getters, gettersKey, {
        get: () => store._vm[gettersKey],
        enumerable: true
      })
    })
    store._vm = new Vue({
      data: {
        $$state: state
      },
      computed
    })
  }
  // 访问store.state.xxx实际上是访问我们初始化时候传进来的state.xxx
  get state () {
    return this._vm._data.$$state
  }
}

export const install = _Vue => {
  if (Vue && process.env.NODE_ENV !== 'production') return console.warn('您已注册过store')
  Vue = _Vue
}

// 提供便利的mapState, mapGetters
export const mapState = createHelper('state')

export const mapGetters = createHelper('getters')

function createHelper (storeKey) {
  return function (ins, keys) {
    if (!(ins instanceof Store)) {
      keys = ins
      ins = store
    }
    if (!ins) {
      throw new Error('未传入store实例或者未初始化')
    }
    const res = {}
    keys.forEach(key => {
      res[key] = () => store[storeKey][key]
    })
    return res
  }
}

Store.install = install
Store.mapState = mapState
Store.mapGetters = mapGetters

复制代码

用法跟vuex几乎一模一样,即可单独使用,如果项目里面已经用了vuex也是可以混着用的,比如,新建store.js定义store

import Vue from 'vue'
import VueStore from './vue-store'

Vue.use(VueStore)

const store = new VueStore({
  // 同vuex state
  state: {
  },
  // 同vuex getters
  getters: {
  }
})

// 如果是单项目,且没有用到vuex可以这样写
Vue.prototype.$store = store

export const mapState = VueSimpleHub.mapState
export const mapGetters = VueSimpleHub.mapGetters
export default store
复制代码

组件中的使用

import store, { mapState, mapGetters } from './store.js'

export default {
  computed: {
    count () {
      return store.state.count
      // 或者,你把store挂载在Vue.prototype.$store
      return this.$store.state.count
    },
    isOdd () {
      return store.getters.isOdd
    },
    ...mapState(['num', 'num1']),
    ...mapGetters(['num2'])
  }
}
复制代码

修改状态,直接修改store.state.xx即可

store.state.count = 3
复制代码

vue-router的代码特别的复杂,为了理解里面核心流程,只抽离了vue-router里面router-viewpushreplace等基础功能,路由定义只支持一层且为最简单的精确配置模式,压缩后的代码体积由24K减至不到4k,同时apivue-router一模一样,以后复杂了可以无缝迁移(以下代码只做理解vue-router源码使用,请勿用于生产环境)

定义router-view组件, components/view.js

export default {
  name: 'RouterView',
  functional: true,
  render (_, { props, children, parent, data }) {
    const h = parent.$createElement
    // 这行代码很重要,$route是响应式的,render函数初次执行会做依赖收集,当$route发生变化的时候,render函数会重新渲染,从而实现了页面路径变更时视图的自动变更
    const route = parent.$route || {}
    const { component } = route
    if (!component) return h()
    return h(component, data, children)
  }
}
复制代码

安装vue-simple-routerinstall.js

import View from './components/view'

export let _Vue

export function install (Vue) {
  if (install.installed && _Vue === Vue) return
  install.installed = true

  _Vue = Vue

  const isDef = v => v !== undefined

  Vue.mixin({
    beforeCreate () {
      // 给每个组件注入_routerRoot,给根组件注入_router,_route
      if (isDef(this.$options.router)) {
        this._routerRoot = this
        this._router = this.$options.router
        // router-view能响应式变化的核心代码,将_route定义为响应式的
        Vue.util.defineReactive(this, '_route', this._router.history.current)
        this._router.init(this)
      } else {
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
    }
  })

  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })
  // 全局注册router-view
  Vue.component('RouterView', View)
}
复制代码

vue-router简化版的核心代码, index.js

import { install } from './install'
import { pushState, replaceState, supportsPushState } from './util/push-state'

export default class VueRouter {

  constructor (options = {}) {
    this.current = {
      path: null,
      component: null
    }
    this.history = {
      current: this.current
    }
    this.pathMap = {}
    this.pathList = []
    // 根据 new VueRouter传进来的{ routes: [...] }做初始化
    this.initRoutes(options.routes)
  }
  initRoutes (routes = []) {
    routes.forEach(conf => {
      const { path, component } = conf
      this.pathList.push(path)
      this.pathMap[path] = { path, component }
    })
  }
  // 路由跳转的逻辑
  transitionTo (currentPath, onComplete) {
    // 修改this.app._route即可触发router-view重新渲染
    this.app._route = this.pathMap[currentPath]
    this.current = this.app._route
    onComplete(this.app._route)
  }
  // 页面初始化,install(VueRouter)会调用该方法
  init (app) {
    this.app = app
    const setupHashListener = () => {
      this.setupListeners()
    }
    this.transitionTo(
      this.getCurrentLocation(),
      setupHashListener
    )
  }
  back () {
    this.go(-1)
  }

  forward () {
    this.go(1)
  }
  setupListeners () {
    const router = this.router

    // 回退的跳转逻辑
    window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => {
      if (!ensureSlash()) return
      this.transitionTo(getHash(), route => {
        if (!supportsPushState) {
          replaceHash(route.path)
        }
      })
    })
  }

  push (newPath) {
    if (newPath === this.current.path) return
    this.transitionTo(newPath, route => {
      pushHash(route.path)
    })
  }

  replace (newPath) {
    if (newPath === this.current.path) return
    this.transitionTo(newPath, route => {
      replaceHash(route.path)
    })
  }

  go (n) {
    window.history.go(n)
  }

  getCurrentLocation () {
    return getHash()
  }
}

function getHash () {
  const href = window.location.href
  const index = href.indexOf('#')
  return index === -1 ? '' : decodeURI(href.slice(index + 1))
}
function pushHash (path) {
  if (supportsPushState) {
    pushState(getUrl(path))
  } else {
    window.location.hash = path
  }
}
function ensureSlash () {
  const path = getHash()
  console.log('ensureSlash', path)
  if (path.charAt(0) === '/') {
    return true
  }
  replaceHash('/' + path)
  return false
}
function replaceHash (path) {
  if (supportsPushState) {
    replaceState(getUrl(path))
  } else {
    window.location.replace(getUrl(path))
  }
}
function getUrl (path) {
  const href = window.location.href
  const i = href.indexOf('#')
  const base = i >= 0 ? href.slice(0, i) : href
  return `${base}#${path}`
}

VueRouter.install = install

复制代码

push-state的相关操作方法,./util/push-state.js

export const supportsPushState = (function () {
  const ua = window.navigator.userAgent

  if (
    (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
    ua.indexOf('Mobile Safari') !== -1 &&
    ua.indexOf('Chrome') === -1 &&
    ua.indexOf('Windows Phone') === -1
  ) {
    return false
  }

  return window.history && 'pushState' in window.history
})()

const Time = window.performance && window.performance.now
  ? window.performance
  : Date

let _key = genKey()

function genKey () {
  return Time.now().toFixed(3)
}

export function getStateKey () {
  return _key
}

export function setStateKey (key) {
  _key = key
}

export function pushState (url, replace) {
  // try...catch the pushState call to get around Safari
  // DOM Exception 18 where it limits to 100 pushState calls
  const history = window.history
  try {
    if (replace) {
      history.replaceState({ key: _key }, '', url)
    } else {
      _key = genKey()
      history.pushState({ key: _key }, '', url)
    }
  } catch (e) {
    window.location[replace ? 'replace' : 'assign'](url)
  }
}

export function replaceState (url) {
  pushState(url, true)
}

复制代码

用法跟vue-router是保持一致的,以后项目复杂了可以无缝切换

export const router = new VueRouter({
  routes: [
    { path: '/', component: () => import('./Home') },
    { path: '/foo', component: Foo },
    { path: '/bar', component: Bar }
  ]
})

new Vue({
  el: '#app',
  router,
  render: h => h(App)
})
复制代码

转载于:https://juejin.im/post/5b9547a35188255c451e8ce1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值