【愣锤笔记】基于vue的进阶散点干货

vue的开发如日中天,愈来愈多的开发者投入了vue开发大军中。但愿本文中的一些vue散点,能在实际项目中灵活运用,切实地为咱们解决一些难点问题。前端

插槽

// 组件调用时使用插槽
<todo-list-item v-for="(item, index) in list" :key="index">
    <template v-slot:theNameOfSlot="theSlotProps">
      <span>{{item}}-{{theSlotProps.checked}}</span>
    </template>
</todo-list-item>

// todo-list-item组件的定义
<li>
    <input type="checkbox" v-model="checked">
    
    // name定义插槽名
    // :checked="checked"经过bind属性的方法,使得父组件在使用插槽时能够读取到如今分发出去的数,例如这样父组件就能够读取到这个分发出的checked值
    <slot name="theNameOfSlot" :checked="checked"></slot>
</li>
复制代码

注意点:vue

  • 父组件的做用域是父组件的,若是想获取自组件的插槽数据,则须要自组件的插件将数据分发出去
  • 2.6版本以后绑定插槽名称,只能在template模板上,v-slot:插槽名,v-slot:theNameOfSlot="theSlotProps"属性值为slot分发给父组件的数据

依赖注入

// 在一个组件上设置注入的属性,能够是对象,也能够是函数返回一个对象
provide: {
    parentProvide: {
      msg: 'hello world'
    }
},

// 在其任意层级的子节点能够获取到父节点注入的属性
inject: [
    'parentProvide'
]
复制代码

依赖注入的属性是没法修改的,若是须要在祖孙组件中监听注入的属性变化,须要在祖宗组件中的注入属性为this, 即把祖宗属性做为注入属性往下传递。node

// 注意这里注入时使用的是函数返回的对象
provide () {
    return {
      parentProvide: this
    }
},

// 接收注入的属性并能够直接修改,修改后祖宗的这个属性值也会变化
inject: [
    'parentProvide'
  ],
methods: {
    updataParentMsg () {
      this.parentProvide.msg = '重置了'
    }
},
复制代码

依赖注入很好的解决了在跨层级组件直接的通讯问题,在封装高级组件的时候会很经常使用。webpack

实现简易的vuex

// 封装
import Vue from 'vue'
const Store = function (options = {}) {
  const {state = {}, mutations = {}} = options
  this._vm = new Vue({
    data: {
      $$state: state
    }
  })
  this._mutations = mutations
}

Store.prototype.commit = function (type, payload) {
  if (this._mutations[type]) {
    this._mutations[type](this.state, payload)
  }
}

Object.defineProperties(Store.prototype, {
  state: {
    get () {
      return this._vm._data.$$state
    }
  }
})

export default { Store }


// main.js,使用
// 首先导入咱们封装的vuex
import Vuex from './min-store/index'

// 简易挂载
Vue.prototype.$store = new Vuex.Store({
  state: {
    count: 1
  },
  getters: {
    getterCount: state => state.count
  },
  mutations: {
    updateCount (state) {
      state.count ++
    }
  }
})

// 页面使用
computed: {
    count () {
      return this.$store.state.count
    }
  },
  methods: {
    addCount () {
      this.$store.commit('updateCount')
    }
},
复制代码

vuex-getter的注意点

// getter第一个参数是state,第二个参数是其余getters,模块中的getter第三个参数是根状态
const getters = {
  count: state => state.count,
  
  // 例如能够返回getters.count * 2
  otherCount: (state, getters) => getters.count * 2,
  
  // 跟状态
  otherCount: (state, getters, rootState) => rootState.someState,
}


// 辅助函数
import { mapGetters } from 'vuex'

computed: {
    ...mapGetters([
        'count',
        'otherCount'
    ])
},

// 模块加命名空间以后,car是当前getter所在的文件名
// 若是父级也有命名空间,则须要加上父级的命名空间,例如`parentName/car`:
computed: {
    ...mapGetters('car', [
        'count',
        'otherCount'
    ])
},

// 若是mapGetters中的值来自于多个模块,能够用对象的形式分别定义:
...mapGetters({
    'count': 'car/count',
    'otherCount': 'car/otherCount',
    'userName': 'account/userName'
})

// 也能够写多个mapGetters
computed: {
    ...mapGetters('account', {
          'userName': 'userName'
    }),
    ...mapGetters('car', [
      'count',
      'otherCount'
    ])
}
复制代码

mapGetter的参数用数组的形式,书写更简洁方便,可是在须要从新命名getters等状况下则没法实现,此时能够换成对象的书写方式。git

vuex-mutations注意点

推荐使用常量替代 mutation 事件类型:在store文件夹中新建mutation-types.js文件,将全部的mutation事件类型以常量的形式定义好。es6

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

// 引用时,经过es6的计算属性命名的方式引入事件类型
import * as types from '../mutation-types'

const mutations = {
  [types.UPDATE_USERINFO] (state, userInfo) {
    state.count = userInfo
  }
}

// 使用mapMutations
import { mapMutations } from 'vuex'
// 常量+命名空间的mutation貌似无法经过像mapGetters的同样用法,只能经过this.$store.commit的方式提交。
this[`account/${types.UPDATE_USERINFO}`]('xiaoming')
// 因此我的以为这种状况,仍是用action去触发mutation
复制代码

常量放在单独的文件中可让你的代码合做者对整个 app 包含的 mutation 一目了然github

vetur

生成简易vue模板的快捷键:scaffoldweb

灵活的路由配置

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/user',
      title: '我的中心',
      component: { render: h => h('router-view') },
      children: [
        {
          path: '/user/index',
          name: 'user-dashboard',
          title: '我的中心',
          meta: {
            // 其余meta信息
          },
          component: () =>
            import(/* webpackChunkName: user */ '@/views/dashboard/index')
        },
        {
          path: '/user/car',
          name: 'user-car',
          title: '个人汽车',
          meta: {
            // 其余meta信息
          },
          component: () =>
            import(/* webpackChunkName: user */ '@/views/car/index')
        }
      ]
    }
  ]
})
复制代码
  • () => import() 自动代码分割的异步组件
  • () => import(/* webpackChunkName: user */ '@/views/dashboard/index') 将文件打包到一个模块,例如这里的写法能够将dashboard的index打包到user模块中
  • render: h => h('router-view') 能够用render函数灵活的建立user模块的入口文件,省去了入口文件的编写。

spa中的页面刷新

spa应用的刷新咱们不能采起reload的方式。因此须要另辟蹊径。vue-router

方案一:当路由当query部分变化时,配router-view的key属性,路由是会从新刷新的。vuex

<router-view :key="$route.path">

this.$router.replace({
    path: this.$route.fullPath,
    query: {
        timestamp: Date.now()
    }
})
复制代码

此种方法的弊端是url平白无故多了一个参数,不是很好看。

方案二:新建一个redirect空页面,刷新就从当前页面跳转到redirect页面,而后在redirect页面理解replace跳转回来。

// redirect页面
<script>
export default {
  name: 'redirect',
  // 在路由进入redirect前当即返回原页面,从而达到刷新原页面的一个效果
  beforeRouteEnter (to, from, next) {
    next(vm => {
      vm.$router.replace({
        path: to.params.redirect
      })
    })
  },
  // 渲染一个空内容
  render(h) {
    return h()
  }
}
</script>

// 跳转方法,咱们能够封装在utils公共函数中
// 在utils/index.js文件中:

import router from '../router'

/**
 * 刷新当前路由
 */
export const refreshCurrentRoute = () => {
  const { fullPath } = router.currentRoute
  router.replace({
    name: 'redirect',
    params: {
      redirect: fullPath
    }
  })
}
复制代码

自定义指令实现最细粒度的权限控制(组件/元素级别的)

为了扩展,咱们在src下新建directives指令文件夹,而后新建index.js、account.js

src/directives/index.js:

import account from './account'

const directives = [
  account
]

const install = Vue => directives.forEach(e => e(Vue))

export default { install }
复制代码

src/directives/account.js

import { checkAuthorization } from '../utils/index'
/**
 * 导出和权限相关的自定义指令
 */
export default Vue => {
  /**
   * 自定义权限指令
   * @description 当用户有权限才显示当前元素
   */
  Vue.directive('auth', {
    inserted (el, binding) {
      if (!checkAuthorization(binding.value)) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  })
}
复制代码

最后简单附上工具函数,具体的根据项目实际状况而定:

/**
 * 检测用户是否拥有当前的权限
 * @param { Array } auth 待检查的权限
 */
export const checkAuthorization = (auth = []) => {
  if (!Array.isArray(auth)) {
    throw TypeError('请检查参数类型:Excepted Array,got ' + Object.prototype.toString.call(auth).slice(8, -1))
  }
  return store.state.account.authorization.some(e => auth.includes(e))
}
复制代码

自定义权限组件实现最细粒度的权限控制(组件/元素级别的)

// 自定义权限组件
<script>
import { check } from "../utils/auth";
export default {
  // 因为该组件笔记简单,不须要状态/周期等,能够将其设置为函数式组件
  // 即无状态/无响应式数据/无实例/无this
  functional: true,
  // 接收的用于权限验证的参数
  props: {
    authority: {
      type: Array,
      required: true
    }
  },
  // 因为函数式组件缺乏实例,故引入第二个参数做为上下文
  render(h, context) {
    const { props, scopedSlots } = context;
    return check(props.authority) ? scopedSlots.default() : null;
  }
};
</script>

// main.js中注册为全局组件
import globalAuth from './components/authorization'

Vue.component('global-auth', globalAuth)

// 使用
<global-auth :authorization="['user']">asdasdas</global-auth>
复制代码

监听某个元素的尺寸变化,例如div

// 安装resize-detector
cnpm i --save resize-detector

// 引入
import { addListener, removeListener } from 'resize-detector'

// 使用
addListener(this.$refs['dashboard'], this.resize) // 监听
removeListener(this.$refs['dashboard'], this.resize) // 取消监听

// 通常咱们会对回调函数进行去抖
methods: {
    // 这里用了lodash的debounce
    resize: _.debounce(function (e) {
      console.log('123')
    }, 200)
}
复制代码

resize-detector传送门

提高编译速度

能够经过在本地环境使用同步加载组件,生产环境异步加载组件

// 安装插件
cnpm i babel-plugin-dynamic-import-node --save-dev

// .bablerc文件的dev增长配置
"env": {
    // 新增插件配置
    "development": {
      "plugins": ["dynamic-import-node"]
    }
    // 其余的内容
    ……
}

// 而后路由文件的引入依旧可使用以前的异步加载的方式
component: () => import('xxx/xxx')
// 经过注释可使多个模块打包到一块儿
component: () => import(/* user */ 'xxx/xxx')
复制代码

该方式修改本地环境和生产环境的文件加载方式,对代码的侵入性最小,在不须要的时候直接删去.bablerc文件中的配置就好,而不须要修改文件代码 github传送门

参考

1.《Vue开发实战》视频,做者:唐金州 地址: 传送门
2. D2admin基于vue的中后台框架, 传送门

END

百尺竿头、日进一步 我是愣锤,一名前端爱好者 欢迎交流、批评