优秀的编程知识分享平台

网站首页 > 技术文章 正文

万字总结Vue(包含全家桶),希望这一篇可以帮到您(二)

nanyue 2024-09-29 15:09:05 技术文章 186 ℃

作者:叫我阿琛

转发链接:https://mp.weixin.qq.com/s/ph3aUt-H4QtBgw9z-VFlHA

目录

万字总结Vue(包含全家桶),希望这一篇可以帮到您(一)

万字总结Vue(包含全家桶),希望这一篇可以帮到您(二) 本篇

基本Vuex的实现

install方法

Vuex作为一个 「插件」,首先执行的是install方法,我们希望的是,「任何组件都可以访问到这里面的数据。组件的渲染是由父到子的」,所以我们既可以先进行判断。如果它是跟节点,就把这个属性挂载到根节点上,如果不是,就找它父级的这个属性,然后挂载到这个Vue实例上

// 官方Api 会把Vue作为参数传入
const install = (_vue)=>{
    Vue = _vue
  Vue.mixin({ // 先父后子
    beforeCreate() {
      if (this.$options.store) { // 跟节点
        this.$store = this.$options.store
      } else { // 往子节点传递
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}

访问state的实现

我们平时使用的过程是是这样的

const store = new Vuex.Store({
    state:{
        
    }
})

所以我们发现,我们实际上是new了一个VueX 的 Store 类。接下来我们开始写这个类。

let Vue
class Store{
    constructor(options) {
        this.state = options.state
        this.mutations = {}
        this.actions = {}
        this.getters = {}
        this.actions = {}
    }
}
// 下面是install 方法

在其他组件中使用

// VuexUse.vue
<template>
  <div>
    VueX中的属性{{$store.state.age}}
    <button @click="change2">=100</button>
  </div>
</template>
<script>
  export default {
    name: 'VuexUse',
    mounted() {
      console.log(this.$store)
    },
    methods: {
      change2() {
        this.$store.state.age = 100
        console.log(this.$store)
      }
    }
  }
</script>

大家会看到 输出10.当点击按钮的时候。再次打印,会发现数据已经发生变化,但是视图并没有刷新。「我们应该让数据更新之后,视图也跟着刷新。这时候我们就应该想到用Vue的特性」。我们改造一下刚才的代码

let Vue
class Store{
    constructor(options) {
        this.state = new Vue({ data: { state: options.state } }).state
        this.mutations = options.mutations || {}
        this.actions = options.actions || {}
        this.getters = {}
    }
}
// 下面是install 方法

这样我们就实现了数据改变,就刷新视图

commit 和dispatch

VueX,中,更改状态一般需要这两个方法,一个是同步,一个是异步,我们来实现一下这两个方法

// 使用的时候
change() {
    this.$store.commit('xxx', 10)
},

所以,这两个方法是写在Store类里面的

let Vue
class Store{
    constructor(options) {
        this.state = new Vue({ data: { state: options.state } }).state
        this.mutations = options.mutations || {}
        this.actions = options.actions || {}
        this.getters = {}
    }
    commit = (mutationName, payload)=>{
     this.mutations[mutationName](this.state, payload)
  	}	
    dispatch = (actionName, payload)=>{
    	this.actions[actionName](this, payload)
  	}
}

commit,我觉得大家都可以看懂,就是找到用户定义的mutations,把参数传入,就可以执行了。

dispatch,为什么要传入this「原因」,在定义的时候,使用的是ES6的结构赋值,所以这里要把this传入

「注意,这两种方法还可以使用 柯里化来实现,这样传值的时候只用传入 payload,更方便一点」

getter实现

首先我们要明白,getter是作什么用的。我 「个人理解」,需要对访问数据进行一定处理。也就是我们访问这个属性的时候,得到这个函数的返回结果。

let Vue
class Store{
    constructor(options) {
        this.state = new Vue({ data: { state: options.state } }).state
        this.mutations = options.mutations || {}
        this.actions = options.actions || {}
       
        // 这下面是修改的部分
        options.getters && this.handleGetters(options.getters)
    }
    
   handleGetters(getters) {
    this.getters = {}
    Object.keys(getters).forEach(key=>{
      Object.defineProperty(this.getters, key, {
        get: ()=>{
          return getters[key](this.state)
        }
      })
    })
  }
}

解释一下handleGetters这一段代码

  1. 获取每个函数函数名称
  2. 根据每个函数的名称 设置对应的返回值

这段代码相对比较简单,这样就实现了getters

模块(module)功能的实现

store/index

import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './../vuex'

Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    age: 10
  },
  strict: true,
  getters: {
    myAge(state) {
      return state.age + 30
    }
  },
  mutations: {
    change(state, payload) {
      state.age += payload
    }
  },
  actions: {
    // 异步更改state
    asyncChange({ commit }, payload) {
      setTimeout(()=>{
        commit('change', payload)
      }, 1000)
    }
  },
  modules: {
    a: {
      namespaced: true,
      state: {
        num: 'a1'
      },
      mutations: {
        // 同步更改state  在严格模式下不可以使用异步
        change(state, payload) {
          console.log(state, payload) // 是自己这个模块内的state
          console.log('a')
        }
      }
    },
    b: {
      state: {
        num: 'b1'
      },
      mutations: {
        // 同步更改state  在严格模式下不可以使用异步
        change(state, payload) {
          console.log('b')
        }
      },
      modules: {
        c: {
          namespaced: true,
          state: {
            num: 'c1'
          },
          mutations: {
            // 同步更改state  在严格模式下不可以使用异步
            change(state, payload) {
              console.log('c')
            }
          }
        }
      }
    }
  }
})
export default store

接下来这一部分可能会难理解一点。我尽力把我学习到给大家清楚的讲出来。「这部分会对之前的代码进行大量修改」

「先改造一下我们的Store,变回最开始的样子」

class ModuleCollection {
    
}

let Vue
class Store{
    constructor(options) {
        this.state = options.state
        this.mutations = {}
        this.actions = {}
        this.getters = {}
        this.modules = new ModuleCollection(options)
        console.log('收集完成的模块')
        console.log(this.modules)
    }
}
// 下面是install 方法

现在,我们需要模块化,所以我们要写一个方法来 「格式化数据」,变成我们想要的样子

「思路」,我们要把这边模块进行遍历 注册,如果模块下面还有子类,则继续遍历。「核心方法」, reduce

ModuleCollection

/**
 * 循环对象的值
 * @param obj
 * @param cb
 */
function forEach(obj, cb) {
  Object.keys(obj).forEach(key=>{
    cb(key, obj[key])
  })
}


class ModuleCollection {
  constructor(options) {
    this.register([], options)
  }
  register(path, rootModule) {
      
    // 格式化模块
    const rawModule = { 
      _raw: rootModule, //原来的modules
      _children: {},  // 孩子
      state: rootModule.state // 原来的数据
    }
    
    // 双向记录 把格式化之后的数据记录下来
    rootModule.rawModule = rawModule 
      
    // 判断是不是根的存在
    if (!this.root) {
      // 第一次肯定不存在
      this.root = rawModule
    } else {
      // 核心  返回的是各个module 对应的格式化后的模块
      const parentModel = path.slice(0, -1).reduce((root, current)=>{
        console.log(current)
        return root._children[current]
      }, this.root)
    /----------------------------------------------------/
      parentModel._children[path[path.length - 1]] = rawModule
    }

    // 递归,遍历子代。核心逻辑
    if (rootModule.modules) {
      forEach(rootModule.modules, (moduleName, module)=>{
        this.register(path.concat(moduleName), module)
      })
    }
  }
}

主要解释一下 /-------------/上下的代码。上面的parentModel,指的是模块

  1. 第一次 parentModelthis.rootrawModulea模块的定义
  2. 第二次 parentModelthis.rootrawModuleb模块的定义
  3. 第三次 parentModelb模块,rawModulec模块的定义

打印一下 this.modules

UTOOLS1592985514369.png

现在我们就把所有的模块进行了 「格式化」。接下来。我们就要对 我们格式化后的数据进行安装。使他们可以访问得到

「总结一下」,这个函数的作用就是把 我们传入的modules进行一个格式化,并且将模块进行分类。

installModule

这个函数的作用 「循环遍历子节点,安装 state action mutation getters」

/**
 * 安装 state action mutation getters  并
 * @param store Vuex 中的store
 * @param rootState 根state
 * @param path      路径
 * @param rawModule   原模块
 */
function installModule(store, rootState, path, rawModule) {

  // 安装state
  if (path.length > 0) { // 证明是子节点 
    const parentState = path.slice(0, -1).reduce((root, current)=>{
      return rootState[current] 
    }, rootState)
    
    // 官方API。
    // 向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新
    Vue.set(parentState, path[path.length - 1], rawModule.state)
  }

  // rawModule 里面有 
  // _raw 原来的模块 
  // _children 孩子 
  // state 原来的state
    
    
  // 安装getters 
  // 注意状态的使用,要使用本模块的状态
  const getters = rawModule._raw.getters || {}
  if (getters) {
    forEach(getters, (getterName, getterFun)=>{
      Object.defineProperty(store.getters, getterName, {
        get: ()=>getterFun(rawModule.state) 
      })
    })
  }

  // mutations跟actions 差不多。都是把 所有的模块的函数存在 root根模块中  使用的时候直接遍历
  const mutations = rawModule._raw.mutations || {}
  if (mutations) {
    forEach(mutations, (mutationName, mutationFun)=>{
      // 写一个发布订阅模式
      const arr = store.mutations[mutationName] || (store.mutations[mutationName] = [])
      arr.push((payload)=>{
        mutationFun(rawModule.state, payload)
      })
    })
  }

  const actions = rawModule._raw.actions || {}
  if (actions) {
    forEach(actions, (actionName, actionsFun)=>{
      const arr = store.actions[actionName] || (store.actions[actionName] = [])
      arr.push((payload)=>{
        actionsFun(store, payload)
      })
    })
  }

  // 遍历子节点
  forEach(rawModule._children, (moduleName, rawModule)=>{
    // console.log(rawModule) // 一个个子节点
    installModule(store, rootState, path.concat(moduleName), rawModule)
  })
}

storerootState始终是。Vuex中的store 和根上面的state

  1. 第一次 patch[]rawModule模块的定义
  2. 第二次 patch['a']rawModulea模块的定义
  3. 第三次 patch['b']rawModuleb模块的定义
    1. 走进来发现 b下面还有modules,所以patch['b',‘c’]rawModulec模块的定义

命名空间的实现

命名空间这个就简单了。只需要在每个方法全面加上x/就可以了

function installModule(store, rootState, path, rawModule) {
  
  // 命名空间的实现  获取命名
  let root = store.modules.root // 拿到的是格式化之后的结果
  const nameSpace = path.reduce((str, current)=>{
    root = root._children[current]
    str = str + (root._raw.namespaced ? current + '/' : '')
    return str
  }, '')

  // 安装state  这里没有发生变化
  if (path.length > 0) {
    // 证明是子节点
    const parentState = path.slice(0, -1).reduce((root, current)=>{
      return rootState[current]
    }, rootState)
    Vue.set(parentState, path[path.length - 1], rawModule.state)
  }

 
  // rawModule 里面有 
  // _raw 原来的模块 
  // _children 孩子 
  // state 原来的state
    
  // 安装getters  把方法前面加上 命名
  const getters = rawModule._raw.getters || {}
  if (getters) {
    forEach(getters, (getterName, getterFun)=>{
      Object.defineProperty(store.getters, nameSpace + getterName, {
        get: ()=>getterFun(rawModule.state) // 使用模块中的状态
      })
    })
  }

  const mutations = rawModule._raw.mutations || {}
  if (mutations) {
    forEach(mutations, (mutationName, mutationFun)=>{
      // 写一个发布订阅模式
      const arr = store.mutations[nameSpace + mutationName] || (store.mutations[nameSpace + mutationName] = [])
      arr.push((payload)=>{
        mutationFun(rawModule.state, payload)
      })
    })
  }

  const actions = rawModule._raw.actions || {}
  if (actions) {
    forEach(actions, (actionName, actionsFun)=>{
      const arr = store.actions[nameSpace + actionName] || (store.actions[nameSpace + actionName] = [])
      arr.push((payload)=>{
        actionsFun(store, payload)
      })
    })
  }

  // 遍历子节点
  forEach(rawModule._children, (moduleName, rawModule)=>{
    // console.log(rawModule) // 一个个子节点
    installModule(store, rootState, path.concat(moduleName), rawModule)
  })
}

''(空)字符串开始。根节点不需要命名空间

registerModule API的实现

class Store {
  constructor(options) {
    this.state = new Vue({ data: { state: options.state } }).state
    this.mutations = {}
    this.actions = {}
    this.getters = {}
    // 模块收集  并格式化
    this.modules = new ModuleCollection(options)
    console.log('收集完成的模块')
    console.log(this.modules)
    // 模块的安装并访问  store rootState path 根模块  安装全部模块
    installModule(this, this.state, [], this.modules.root)
  }
  // 模块开发完之后的写法
  commit = (mutationName, payload)=>{
    this.mutations[mutationName].forEach(fn=>fn(payload))
  }
  dispatch = (actionName, payload)=>{
    this.actions[actionName].forEach(fn=>fn(payload))
  }
  /**
   * 自定义注册 module
   * @param moduleName
   * @param module
   */
  registerModule(moduleName, module) {
    if (!Array.isArray(moduleName)) {
      moduleName = [moduleName]
    }
    this.modules.register(moduleName, module)
    console.log(this.modules.root)
    // 安装当前模块
    installModule(this, this.state, moduleName, module.rawModule)
  }
}

思路很简单,就是把 注册的module,进行格式化之后。再进行安装就可以

「注意」,要安装位置要确定好哦

辅助函数

VueRouter

Vuex一样,也写过一篇比较简单的实现 VueRouter的简单实现,感觉这一篇写的相对简单一点

「这部分我个人觉得自己掌握的不是特别好。所以 讲述的不太清楚。仅提供一个思路。」

最开始install方法

在我们的平常使用过程中,除了router-linkrouter-view。最常用的可能就是this.$router.push(xx)。所以我们还是跟VueX的做法差不多。在每一个实例上挂在一个属性

const install = (Vue)=>{
  Vue.mixin({
    beforeCreate() {
      if (this.$options.router) {
        // console.log(this) // 指的是一个new Vue
        this._routerRoot = this // 把vue实例挂载到这个属性上
        this._router = this.$options.router // 用户传入得 router
        // 路由的初始化
        this._router.init(this)
      } else {
        this._routerRoot = this.$parent && this.$parent._routerRoot
      }
    }
  })
}
export default install

以上代码只做了两件事。

  1. 挂载属性
  2. 调用路由的初始化方法。对路由进行初始化

主index文件

首先我们应该分析。我们这个主文件应该有什么。在我们日常使用的过程中,一般是import VueRouter from 'vue-router'

所以

  1. 我们应该有一个VueRoter类。
  2. 上面得有初始化方法install.。
  3. VueRoter类的constructor中,我们应该对用户传入的数据进行处理。还有就是分析它路由模式
  4. init方法,要可以监听到路由变换,然后跳转到对应的 路由。渲染对应的组件

分析完之后。我们就开始着手写

我先把大体框架给大家展示一下

import install from './install'
import createMatcher from './createMatcher'
import HashHistory from './history/hash'

class VueRouter {
  constructor(options) {
    // matcher 匹配器 处理树形结构 将他扁平化
    // 返回两个方法 addStore match 匹配对应结果
    this.matcher = createMatcher(options.routes || [])

    // 内部需要用 hash history 进行路由的初始化工作
    // base 表示基类,实现所有路由的公告方法都放在基本类上 保证不同路由API 有相同的使用方法
    this.history = new HashHistory(this)
  }

  push(location) {
    
  }

  init(app) {
    // app 是顶层Vue 实例
    // 获取到路径 并进行跳转 并渲染对应组件
    // 匹配一次完成后,监听路有变化,完成之后的更新操作
    
  }
}

VueRouter.install = install
export default VueRouter

createMatcher方法

「里面出现的方法在下面都会有所解释」

import createRouteMap from './createRouteMap'
import { createRoute } from './history/base'

export default function createMatcher(routes) {
  // 开始扁平化数据
  const { pathList, pathMap } = createRouteMap(routes)

  // 重载s
  function addRoute(routes) {
    createRouteMap(routes, pathList, pathMap)
  }

  function match(location) {
    console.log('create里面的match' + location)
    // 从pathMap获取的location
    const record = pathMap[location]
    // console.log(record)
    return createRoute(record, {
      path: location
    })
  }
  return {
    addRoute, match
  }
}

我们先通过createRouteMap方法,把传入的routes(即用户传入的配置)进行一个格式化处理,得到一个pathList(地址的列表)pathMap(地址映射,里面有地址,组件等等属性)

官方API中,有一个叫addRotes,也就是再添加进一组路由。

我们还是利用createRouteMap方法。这个方法具体是什么样的看下面

match方法的作用是匹配器,匹配传入的location(地址)。返回相对应的 记录

createRouteMap方法

export default function createRouteMap(routes, oldPathList, oldPathMap) {
  const pathList = oldPathList || []
  const pathMap = oldPathMap || Object.create(null)
  // Object.create(null) 和 {} 区别  前者没有原型链
  // 数组扁平化
  routes.forEach(route=>{
      addRouteRecord(route, pathList, pathMap)
    }
  )
  return {
    pathList, pathMap
  }
}

function addRouteRecord(route, pathList, pathMap, parent) {
  const path = parent ? parent.path + '/' + route.path : route.path
  const record = {
    path,
    component: route.component,
    parent
    // todo
  }
  if (!pathList[route]) {
    pathList.push(path)
    pathMap[path] = record
  }
  if (route.children) {
    route.children.forEach(route=>{
      addRouteRecord(route, pathList, pathMap, record)
    })
  }
}

「Object.create(null) 和 {} 区别 前者没有原型链」

「{}」 会存在一堆的属性

UTOOLS1593155710472.png

**Object.create(null)**不存在这些

addRouteRecord 是这个的核心方法。它的工作是

  1. 先查找父级元素。如果有。则加上父级 例如 about/a。没有就是本身
  2. 然后生成一条记录record
  3. 判断你传入的route(即每一项路由)是否在pathList里面。在了就跳过。不在就添加进去。**这个方法就实现了addRoutes**的作用
  4. 递归遍历。如果有孩子继续添加

createRoute方法

「这个方法及其关键!!!!」

原因:比如我们渲染about/a这个路径的组件。我们是不是必须得渲染about,这样才可以渲染a

所以这个方法的主要作用就是。把路径的父级也都存下来

export function createRoute(record, location) {
  const res = [] // 如果匹配到路径 就放进来
  if (record) {
    while (record) {
      res.unshift(record)
      record = record.parent
    }
  } // 把父级路径也存放下来
  console.log(res, location)
  return {
    ...location,
    matched: res
  }
}

history方法

这个即解释this.history = new HashHistory(this)

为什么要单独列出来?因为有不同的 「路由模式」,但是有公共的处理方法。当然还需要有不同的方法来处理不同的路由。

我们这里只考虑hash

base.js

export function createRoute(record, location) {
  const res = [] // 如果匹配到路径 就放进来
  if (record) {
    while (record) {
      res.unshift(record)
      record = record.parent
    }
  } // 把父级路径也存放下来
  console.log(res, location)
  return {
    ...location,
    matched: res
  }
}

class History {
  constructor(router) {
    this.router = router
    this.current = createRoute(null, {
      path: '/'// 默认路径
    })
  }

  transitionTo(location, cb) { // 最好屏蔽一下,以防止多次调用
    console.log(location, cb)
    // 得到路径 开始匹配对应的模板
    const r = this.router.match(location)
    this.current = r // 对当前路径进行更新
    // eslint-disable-next-line eqeqeq
    if (location === this.current.path && r.matched.length === this.current.matched) {
      return
    }
    this.cb && this.cb(r)
    cb && cb()
  }

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

  listen(cb) {
    this.cb = cb
  }
}

export default History

可以看出 这个base.js做了几件事

  1. 初始化了一个默认路由
  2. 提供了跳转方法
  3. 监听了路由变化
  4. listen这个等会再说

transitionTo中间的if判断。是为了防止多次调用的。

hash.js

import History from './base'

function ensureSlash() {
  if (window.location.hash) {
    return
  }
  window.location.hash = '/'
}
class HashHistory extends History {
  constructor(router) {
    super(router) // super === parent.call(this)   向父级传递router
    this.router = router
    ensureSlash() // 确保有hash值
  }

  getCurrentLocation() {
    return window.location.hash.slice(1) // 除了# 号后面的路径
  }
}

export default HashHistory

这个就比较简单了。就不再解释了

重新回到index.js

import install from './install'
import createMatcher from './createMatcher'
import HashHistory from './history/hash'

class VueRouter {
  constructor(options) {
    // matcher 匹配器 处理树形结构 将他扁平化
    // 返回两个方法 addStore match 匹配对应结果
    this.matcher = createMatcher(options.routes || [])

    // 内部需要用 hash history 进行路由的初始化工作
    // base 表示基类,实现所有路由的公告方法都放在基本类上 保证不同路由API 有相同的使用方法
    this.history = new HashHistory(this)
  }

  match(location) { // 作了一层封装 返回匹配结果
    return this.matcher.match(location)
  }

  push(location) {
    this.history.transitionTo(location, ()=>{
      window.location.hash = location// 这样的话 要渲染两遍 一边transitionTo 一边是hash的监听
    }) // hash没有改变 要改变hash
  }

  init(app) {
    // app 是顶层Vue 实例
    // console.log(app)
    // 获取到路径 并进行跳转 并渲染对应组件
    // 匹配一次完成后,监听路有变化,完成之后的更新操作
    const history = this.history
    const setupHashListener = ()=>{ // 监听之后回调
      history.setupListeners() // 监听路由变化   父类
    }
    history.transitionTo( // 跳转方法         父类
      history.getCurrentLocation(), // 获取当前路径   分路由 所以是子类
      setupHashListener
    )
    // 订阅好 然后路由 属性变化 更新此方法
    history.listen((route)=>{
      app._route = route
    })
  }
}

VueRouter.install = install
export default VueRouter

改造完之后的index.js做的事,

  1. 监听路由。
  2. 跳转路由。
  3. 设置改变_route的函数(这时候 _route还不是动态的)

回到install

import RouterView from './components/router-view'
import RouterLink from './components/router-link'

const install = (Vue)=>{
  Vue.mixin({
    beforeCreate() {
      if (this.$options.router) {
        // console.log(this) // 指的是一个new Vue
        this._routerRoot = this
        this._router = this.$options.router // 用户传入得 router
        // 路由的初始化
        this._router.init(this)
        // 将current 定义成 响应式的。数据改变则刷新视图
        console.log(this._router)
        // 给当前实例创建了 _route 属性, 取自this._router.history.current
        Vue.util.defineReactive(this, '_route', this._router.history.current)
        // 定义之后 更新_route
      } else {
        this._routerRoot = this.$parent && this.$parent._routerRoot
      }
    }
  })
  Object.defineProperty(Vue.prototype, '$route', {
    get() {
      console.log(this._routerRoot._route)
      return this._routerRoot._route
    }
  })

  Object.defineProperty(Vue.prototype, '$router', {
    get() {
      return this._routerRoot._router
    }
  })
  Vue.component('RouterView', RouterView)
  Vue.component('RouterLink', RouterLink)
}
export default install

回到install方法,初始化之后。把_route设置成动态(有getset)。

之后数据发生改变,视图就会刷新。

组件

RouterView

export default {
  functional: true, // 函数式组件 没有状态 没有this
  render(h, { parent, data }) { // 里面有很多options 这是通过解构赋值出来的
    // console.log(options)
    const route = parent.$route // 被放到了vue 原型上
    console.log(route)
    let depth = 0
    // $vnode表示占位符Vnode
    while (parent) {
      if (parent.$vnode && parent.$vnode.data.routerView) {
        depth++
      }
      parent = parent.$parent
    }
    data.routerView = true
    const record = route.matched[depth]
    console.log(record)
    if (!record) {
      return h()
    }
    return h(record.component, data)
  }
}

这段代码中最难理解的就是 depth

route是属性。这段代码在history/base.jscreateRoute 返回的结果中有一个match。里面存放了所有的 「父级路径」

routerView的解释。自定义属性。看他是否是根节点。第一次进来的时候 ,渲染App组件(里面放有RouterView)。如果存在,证明要渲染的是下一个节点了

router是方法

RouterLink

export default {
  props: {
    to: {
      type: String,
      require: true
    },
    tag: {
      type: String,
      default: 'a'
    }
  },
  methods: {
    handle() {
      this.$router.push(this.to)
    }
  },
  render(h) {
    const tag = this.tag
    return <tag onclick = { this.handle } > { this.$slots.default } < /tag>
  }
}

我这里用的是jsx语法。觉得看不懂的可以直接用RouterLink.vue正常写来代替

钩子函数(路由守卫)

这个就比较像expresskoa里面的了。

简单写个思路就是这样。这一点我还没加上。思路比较简单,我还没有去具体实现

// 储存
let deps = []

// 放置
beforeXxx(cb){
    this.deps.push(cb)
}

// 使用
// 在视图更新或者跳转前
this.deps.forEach(dep=>{
    dep()
})

路由权限

正在努力

Vue3

proxy

阮一峰老师这一本书关于这部分已经写的很好了。我就不再多做叙述了。

let obj = {
  name: {
    achen: {
      name: '阿琛',
      age: 22,
    },
  },
  sex:'男',
  arr: ['吃', '喝', '玩'],
}

let handler = {
  // target就是原对象,key是键
  get(target, key) {
    // 懒代理 如果取到了这个对象才会触发,没有取到就不会代理
    if (typeof target[key]=== 'object'){
      // 递归调用
      return new Proxy(target[key],handler)
    }
    console.log('收集')
    // return target[key] 老方法
    return Reflect.get(target,key)
  },
  set(target, key, value) {
    console.log('触发更新')
    let oldValue = target[key]
    console.log(oldValue,value,key)
    if (!oldValue){
      console.log('设置属性')
    }else if (oldValue!==value){
      console.log('修改属性')
    }
    // target[key] = value
    // 有返回值
    return Reflect.set(target,key,value)
  },
}

// 兼容性差,但是可以代理13中方法
// defineProperty 他只能对特定属性进行拦截

// 拦截的是整个对象
let proxy = new Proxy(obj,handler)
// proxy.sex = 'nv'
// console.log(proxy.sex)

// 数组
// proxy.arr.push(132)  // 先走一次obj  再收集 push length 在改值
// proxy.arr[0] = 100  // 直接触发
proxy.xxx = 100

本篇已完结


作者:叫我阿琛

转发链接:https://mp.weixin.qq.com/s/ph3aUt-H4QtBgw9z-VFlHA

Tags:

最近发表
标签列表