手写vue-router与vue-vuex

一、手写vue-router

1、kvue-router.js


import Link from './krouter-link'
import View from './krouter-view'


//1.创建一个krouter对象,只需要把krouter挂载到Vue.prototype.$router = router上  这样在所有的组件中都可以使用$router了
//krouter是一个对象,只需要实现一个{install()} 方法就可以了

let Vue;
class kVueRouter {
  constructor(options) {    //options接收用户传进来的配置及属性
    this.$options = options

    //创建响应式的current
    // Vue.util.defineReactive(this, 'current', '/')   //看vue文档

    this.current = window.location.hash.slice(1) || '/'
    Vue.util.defineReactive(this, 'matched', [])
    //match方法可以递归遍历路由表,获得匹配关系数据matched
    this.match()



    /*  
    //这里注释掉是不希望有重复的代码  
    window.addEventListener('hashchange', () => {
      console.log('window.location.hash:===', window.location.hash);

      this.current = window.location.hash.slice(1)
    })
    window.addEventListener('load', () => {
      console.log('window.location.hash:===', window.location.hash);

      this.current = window.location.hash.slice(1)
    }) */

    //这里使用bind(this)的原因是因为是window调用的,用bind(this)就是重新指向当前类KVueRouter
    window.addEventListener('hashchange', this.onHashChange.bind(this))
    window.addEventListener('load', this.onHashChange.bind(this))

    //创建一个path和component之间的路由映射表
    /* this.routeMap = {}
    console.log('options.routes:===', options.routes)
    options.routes.forEach(route => {
      this.routeMap[route.path] = route     //这里还得再看看看why?????   2020.01.11
    }) */

  }

  onHashChange() {
    console.log('window.location.hash:===', window.location.hash);

    this.current = window.location.hash.slice(1)
    //当路由变化的时候,把matched数组清空,重新匹配
    this.matched = []
    this.match()
  }
  match(routes) {
    routes = routes || this.$options.routes

    //递归遍历
    for (const route of routes) {
      if (route.path === '/' && this.current === '/') {
        this.matched.push(route)
        return
      }
      //this.current是/about/info时的判断
      if (route.path !== '/' && this.current.indexOf(route.path) != -1) {
        this.matched.push(route)
        //路由/info
        if (route.children) {
          this.match(route.children)
        }
        return
      }
    }
  }

}
kVueRouter.install = function (_vue) {
  //保存构造函数,在KVueRouter中使用
  Vue = _vue

  //挂载$router   
  //怎么获取根实例下的router选项
  Vue.mixin({
    beforeCreate() {
      // console.log(this);
      //确保根实例的时候才执行,只有根实例的时候才会存在router,所以用下面的判断
      if (this.$options.router) {
        Vue.prototype.$router = this.$options.router
      }

    }
  })



  //任务2:实现两个全局组件router-line 和router-view
  /* Vue.component('router-link', {
    template:'<a></a>'       //在这里不能使用template的原因是现在是run-time only,即纯运行时的环境,没有编译器,所以不能使用template
  }) */
  Vue.component('router-link', Link)
  Vue.component('router-view', View)

}

export default kVueRouter









2、krouter-view.js

export default {
  render(h) {
    //获取path对应的component    这里性能太低,path每变一次都需要都去循环一下
    /* let component = null;
    this.$router.$options.routes.forEach(route => {
      if (route.path === this.$router.current) {
        component = route.component
      }
    }) */

    //标记当前router-view深度
    this.$vnode.data.routerView = true;
    let depth = 0
    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
    const route = this.$router.matched[depth]
    if (route) {
      component = route.component    //当前的组件component设为匹配到的路由的组件
    }

    return h(component)
  }
}

3、krouter-link.js

export default {
  props: {
    to: {
      type: String,
      required: true
    },
  },
  render(h) {
    //<a href="#/about">abc</a>   url中最终渲染的
    //<router-link :to="/about">XXXX</router-link>   最终使用时的用法
    //h(tag ,data, children)
    console.log('this.$slots:===', this.$slots);

    return h('a', {
      attrs: { href: '#' + this.to }, class: 'router-link'
    }, this.$slots.default)
    //下面是jsx的写法   但是脱离了vue-cli没办法成功,因为vue-cli有webpack可以进行编译
    // return <a href={'#' + this.to}>{this.$slots.default}</a>   
  }
}

4、全局引入krouter  :main.js

import router from './krouter'

import store from './kstore'

Vue.config.productionTip = false

new Vue({
  //Vue.prototype.$router = router   在所有组件中都可以使用$router
  router,

  store,
  render: h => h(App)
}).$mount('#app')

5、在router/index.js中引入手写的router:

import Vue from 'vue'
import VueRouter from './kvue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

//路由表
const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
    children: [
      {
        path: '/about/info',
        component: {
          render(h) {
            return h('div', 'about child :info page')
          }
        }
      }
    ]


  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

6、About.vue页面中使用手写的嵌套路由:about/info

<template>
  <div class="about">
    <h1>This is an about page</h1>
    <router-view></router-view>
  </div>
</template>

总结:

手写router的大致思路:

1、作为一个插件存在:创建VueRouter类和install方法。kvue-router.js

2、实现两个全局组件:router-view用于显示匹配组件内容,router-link用于组件之间跳转。krouter-view.js和krouter-link.js

3、监控url变化:使用hashChange或popChange事件。kvue-router.js

4、响应最新url:创建一个响应式的属性current,当它改变时获取对应组件并显示。kvue-router.js

 

二、手写kvuex

1、kvuex.js

let Vue;//保存vue构造函数,避免打包时import导致文件过大

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可以使用的无参数形式,因为在使用时doubleCounter(state){}需要传参,但是computed计算属性不能传参数所以在这里进行封装
      //key就是上面的doubleCounter
      computed[key] = function () {
        return fn(store.state)    //6666这步操作把computed赋值为一个函数,这个函数返回fn,fn里面把state传进去
      }
      //为getters定义只读属性   
      Object.defineProperty(store.getters, key, {
        get: () => store._vm[key]   //想一下这里为什么可以这样写 ,因为我们上面定义的computed对象会把所有的key都放到new Vue实例的computed上

      })
    })

    //响应化处理state
    /*  this.state = new Vue({
       data:options.state
     }) */
    //这种方式比上面的更好
    this._vm = new Vue({
      data: {
        //加两个$,Vue不做代理
        $$state: options.state
      },
      computed
    })

    //绑定commit,dispatch的上下文为Store实例
    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)
  }
  //存取器   store.state
  get state() { return this._vm._data.$$state }    //这里不太清楚,还得再看看
  set state(v) {
    console.error('你造吗?你这样直接改store.state不好!')
  }

  //store.commit('add',1)
  //type:mutation的类型
  //paylod是载荷,是参数
  commit(type, payload) {
    const entry = this._mutations[type]
    if (entry) {
      entry(this.state, payload)
    }
  }
  dispatch(type, payload) {
    const entry = this._actions[type]
    if (entry) {
      //把this传进来,那用的时候就可以解构赋值传个add({commit,state,type}),{commit,state,type}这个是解构赋值,对应着这里传入的this
      entry(this, payload)
    }
  }

}

function install(_Vue) {
  Vue = _Vue
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    },
  })

}

export default {
  Store,
  install
}

2、在kstore/index.js中使用手写的vuex

import Vue from 'vue'
import Vuex from './kvuex'    //引入我们自己手写的vuex

Vue.use(Vuex)

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

3、在main.js全局引入

import store from './kstore'   //引入我们手写的store

new Vue({
  //Vue.prototype.$router = router   在所有组件中都可以使用$router
  router,

  store,
  render: h => h(App)
}).$mount('#app')

4、在Home.vue页面组件中使用

<!-- 手写kVuex -->
    <div>
      <p @click="$store.commit('add')">state counter: {{$store.state.counter}}</p>
      <p @click="$store.dispatch('add')">actions counter:{{$store.state.counter}}</p>
      <p>getters counter:{{$store.getters.doubleCounter}}</p>
    </div>

kvuex总结:

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

2、$store的具体实现:

  • 创建响应式state,保存mutations、actions和getters。
  • 实现commit根据用户传入的type(即上例中的add),执行对应mutation
  • 实现dispatch根据用户传入的type(即上例中的add),执行对应action,同时传递上下文。
  • 实现getters,按照getters定义对state作派生(即getters中属性的改变依赖于state)

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值