前方高能,这是最新的一波Vue实战技巧,不用则已,一用惊人

葡萄美酒夜光杯,欲饮琵琶产品催。 客户现场君莫笑,古来埋坑几人回?javascript

最近一直在开发后台管理系统,日复一日的重复着表单表格表格表单,标准的CV仔,感受好无聊,如何能在这种无聊的开发过程当中去提高本身,小编今天又整理了一波新的Vue实战技巧,这些技巧,不用则已,一用惊人。同时你也能够点击下面的连接阅读近期小编的文章。vue

实战技巧,Vue原来还能够这样写 获赞 1700+java

绝对干货~!学会这些Vue小技巧,能够早点下班和女神约会了 获赞 970+web

看到赚到!重读vue2.0风格指南,我整理了这些关键规则 获赞 120+面试

插槽,我要钻到你的怀里

插槽,相信每一位Vue都有使用过,可是如何更好的去理解插槽,如何去自定义插槽,今天小编为你带来更形象的说明。ajax

默认插槽

大学毕业刚上班,穷鬼一个,想着每月租房还要掏房租,因此小编决定买一个一居室,东拼西凑借了一堆债,终于凑够了首付,买了一个小小的毛坯房。咱们能够把这个一居室的毛坯房想一想成一个组件,这个房子的户型,面积,楼层都是固定的,可是室内如何装修,摆什么家具,这个倒是由你来决定的,房间内部就能够理解为插槽,容许用户去自定义内容。vuex

1. 开发商终于将一居室开发完交房了

<template>
 <!--这是一个一居室-->  <div class="one-bedroom">  <!--添加一个默认插槽,用户能够在外部随意定义这个一居室的内容-->  <slot></slot>  </div> </template> 复制代码

2. 小编要开始装修了

<template>
 <!--这里一居室-->  <one-bedroom>  <!--将家具放到房间里面,组件内部就是上面提供的默认插槽的空间-->  <span>先放一个小床,反正没有女友</span>  <span>再放一个电脑桌,在家还要加班写bug</span>  </one-bedroom> </template> <script> import OneBedroom from '../components/one-bedroom' export default {  components: {  OneBedroom  } } </script>  复制代码

具名插槽

过了几年,小编有了女友,准备结婚了,一居室房间确定不行啊,丈母娘嫌小不一样意,没办法,只能又凑钱买大房子,买了一个两居室(穷逼一个),由于是两居室,因此有了主卧和次卧之分,装修是否也不能把主卧和次卧装修的如出一辙,因此就须要进行区分。将房子想一想成组件,那么组件就有两个插槽,而且须要起名字进行区分。element-ui

1. 开发商终于开发完交房了

<template>
 <div class="two-bedroom">  <!--这是主卧-->  <div class="master-bedroom">  <!---主卧使用默认插槽-->  <slot></slot>  </div>  <!--这是次卧-->  <div class="secondary-bedroom">  <!--次卧使用具名插槽-->  <slot name="secondard"></slot>  </div>  </div> </template>  复制代码

2. 小编要卖血攒钱装修了

<template>
 <two-bedroom>  <!--主卧使用默认插槽-->  <div>  <span>放一个大床,要结婚了,嘿嘿嘿</span>  <span>放一个衣柜,老婆的衣服太多了</span>  <span>算了,仍是放一个电脑桌吧,还要写bug</span>  </div>  <!--次卧,经过v-slot:secondard 能够指定使用哪个具名插槽, v-slot:secondard 也能够简写为 #secondard-->  <template v-slot:secondard>  <div>  <span>父母要住,放一个硬一点的床,软床对腰很差</span>  <span>放一个衣柜</span>  </div>  </template>  </two-bedroom> </template> <script> import TwoBedroom from '../components/slot/two-bedroom' export default {  components: {  TwoBedroom  } } </script>  复制代码

做用域插槽

装修的时候,装修师傅问我洗衣机是要放到卫生间仍是阳台,通常状况下开发商会预留放洗衣机的位置。而这个位置能够理解为插槽传的参数,这个就是做用域插槽。c#

1. 看一下卫生间插槽传了什么参数

<template>
 <div class="two-bedroom">  <!--其余内容省略-->  <div class="toilet">  <!--经过v-bind 能够向外传递参数, 告诉外面卫生间能够放洗衣机-->  <slot name="toilet" v-bind="{ washer: true }"></slot>  </div>  </div> </template>  复制代码

2. 把洗衣机放到卫生间

<template>
 <two-bedroom>  <!--其余省略-->  <!--卫生间插槽,经过v-slot="scope"能够获取组件内部经过v-bind传的值-->  <template v-slot:toilet="scope">  <!--判断是否能够放洗衣机-->  <span v-if="scope.washer">这里放洗衣机</span>  </template>  </two-bedroom> </template> 复制代码

插槽默认值

小编的同事不想等期房,因此就买了二手房,二手房前业主都装修好了,能够直接入住。固然也能够从新装修,下面是同事买的二手房。后端

1. 这是装修好的二手房

<template>
 <div class="second-hand-house">  <div class="master-bedroom">  <!--插槽能够指定默认值,若是外部调用组件时没有修改插槽内容,则使用默认插槽-->  <slot>  <span>这里有一张水床,玩的够嗨</span>  <span>还有一个衣柜,有点旧了</span>  </slot>  </div>  <!--这是次卧-->  <div class="secondary-bedroom">  <!--次卧使用具名插槽-->  <slot name="secondard">  <span>这里有一张婴儿床</span>  </slot>  </div>  </div> </template>  复制代码

2. 同事决定先把主卧装修了,之后结婚用

<second-hand-house>
 <!--主卧使用默认插槽,只装修主卧-->  <div>  <span>放一个大床,要结婚了,嘿嘿嘿</span>  <span>放一个衣柜,老婆的衣服太多了</span>  <span>算了,仍是放一个电脑桌吧,还要写bug</span>  </div>  </second-hand-house> 复制代码

了解选项合并策略,自定义生命周期钩子函数

当你使用Vuemixins的时候,是否有发现,若是混入的methods里面的方法与组件的方法同名,则会被组件方法覆盖,可是生命周期函数若是重名,混入的与组件自身的都会被执行,且执行顺序是先混入和自身,这是怎么作到的呢?

1. 了解Vue合并策略

Vue中,不一样的选项有不一样的合并策略,好比 data,props,methods是同名属性覆盖合并,其余直接合并,而生命周期钩子函数则是将同名的函数放到一个数组中,在调用的时候依次调用,具体可参考小编前面的一篇文章绝对干货~!学会这些Vue小技巧,能够早点下班和女神约会了

Vue中,提供了一个api, Vue.config.optionMergeStrategies,能够经过这个api去自定义选项的合并策略。

在代码中打印

console.log(Vue.config.optionMergeStrategies)
复制代码
控制台打印内容
控制台打印内容

经过上图能够看到Vue全部选项的合并策略函数,咱们能够经过覆盖上面的方法,来自定义合并策略函数,不过通常用不到。

2. 经过合并策略自定义生命周期函数

背景

最近客户给领导反馈,咱们的系统用一段时间,浏览器就变得有点卡,不知道为何。问题出来了,原本想甩锅到后端,可是浏览器问题,无法甩锅啊,那就排查吧。

后来发现页面有许多定时器,ajax轮询还有动画,打开一个浏览器页签无法问题,打开多了,浏览器就变得卡了,这时候我就想若是能在用户切换页签时候将这些都停掉,不久解决了。百度里面上下检索,找到了一个事件visibilitychange,能够用来判断浏览器页签是否显示。

有方法了,就写呗

export default {
 created() {  window.addEventListener('visibilitychange', this.$_hanldeVisiblityChange)  // 此处用了hookEvent,能够参考小编前一篇文章  this.$on('hook:beforeDestroy', () => {  window.removeEventListener(  'visibilitychange',  this.$_hanldeVisiblityChange  )  })  },  methods: {  $_hanldeVisiblityChange() {  if (document.visibilityState === 'hidden') {  // 停掉那一堆东西  }  if (document.visibilityState === 'visible') {  // 开启那一堆东西  }  }  } } 复制代码

经过上面的代码,能够看到在每个须要监听处理的文件都要写一堆事件监听,判断页面是否显示的代码,一处两处还能够,文件多了就头疼了,这时候小编突发奇想,定义一个页面显示隐藏的生命周期钩子,把这些判断都封装起来,哪里须要点哪里,so easy(点读机记得广告费)。

自定义生命周期钩子函数

定义生命周期函数 pageHiddenpageVisible

import Vue from 'vue'
 // 通知全部组件页面状态发生了变化 const notifyVisibilityChange = (lifeCycleName, vm) => {  // 生命周期函数会存在$options中,经过$options[lifeCycleName]获取生命周期  const lifeCycles = vm.$options[lifeCycleName]  // 由于使用了created的合并策略,因此是一个数组  if (lifeCycles && lifeCycles.length) {  // 遍历 lifeCycleName对应的生命周期函数列表,依次执行  lifeCycles.forEach(lifecycle => {  lifecycle.call(vm)  })  }  // 遍历全部的子组件,而后依次递归执行  if (vm.$children && vm.$children.length) {  vm.$children.forEach(child => {  notifyVisibilityChange(lifeCycleName, child)  })  } }  /**  * 添加生命周期钩子函数  * @param {*} rootVm vue 根实例,在页面显示隐藏时候,经过root向下通知  */ export function init() {  const optionMergeStrategies = Vue.config.optionMergeStrategies  /*  定义了两个生命周期函数 pageVisible, pageHidden  为何要赋值为 optionMergeStrategies.created呢  这个至关于指定 pageVisible, pageHidden 的合并策略与 created的相同(其余生命周期函数都同样)  */  optionMergeStrategies.pageVisible = optionMergeStrategies.beforeCreate  optionMergeStrategies.pageHidden = optionMergeStrategies.created }  /**  * 将事件变化绑定到根节点上面  * @param {*} rootVm  */ export function bind(rootVm) {  window.addEventListener('visibilitychange', () => {  // 判断调用哪一个生命周期函数  let lifeCycleName = undefined  if (document.visibilityState === 'hidden') {  lifeCycleName = 'pageHidden'  } else if (document.visibilityState === 'visible') {  lifeCycleName = 'pageVisible'  }  if (lifeCycleName) {  // 经过全部组件生命周期发生变化了  notifyVisibilityChange(lifeCycleName, rootVm)  }  }) }  复制代码

应用

  1. main.js主入口文件引入
import { init, bind } from './utils/custom-life-cycle'
 // 初始化生命周期函数, 必须在Vue实例化以前肯定合并策略 init()  const vm = new Vue({  router,  render: h => h(App) }).$mount('#app')  // 将rootVm 绑定到生命周期函数监听里面 bind(vm)  复制代码
  1. 在须要的地方监听生命周期函数
export default {
 pageVisible() {  console.log('页面显示出来了')  },  pageHidden() {  console.log('页面隐藏了')  } } 复制代码

provideinject,不止父子传值,祖宗传值也能够

Vue相关的面试常常会被面试官问道,Vue父子之间传值的方式有哪些,一般咱们会回答,props传值,$emit事件传值,vuex传值,还有eventbus传值等等,今天再加一种provideinject传值,离offer又近了一步。(对了,下一节还有一种)

使用过React的同窗都知道,在React中有一个上下文Context,组件能够经过Context向任意后代传值,而Vueprovideinject的做用于Context的做用基本同样

先举一个例子

使用过elemment-ui的同窗必定对下面的代码感到熟悉

<template>
 <el-form :model="formData" size="small">  <el-form-item label="姓名" prop="name">  <el-input v-model="formData.name" />  </el-form-item>  <el-form-item label="年龄" prop="age">  <el-input-number v-model="formData.age" />  </el-form-item>  <el-button>提交</el-button>  </el-form> </template> <script> export default {  data() {  return {  formData: {  name: '',  age: 0  }  }  } } </script>  复制代码

看了上面的代码,貌似没啥特殊的,每天写啊。在el-form上面咱们指定了一个属性size="small",而后有没有发现表单里面的全部表单元素以及按钮的 size都变成了small,这个是怎么作到的?接下来咱们本身手写一个表单模拟一下

本身手写一个表单

咱们如今模仿element-ui的表单,本身自定义一个,文件目录以下

自定义表单custom-form.vue

<template>
 <form class="custom-form">  <slot></slot>  </form> </template> <script> export default {  props: {  // 控制表单元素的大小  size: {  type: String,  default: 'default',  // size 只能是下面的四个值  validator(value) {  return ['default', 'large', 'small', 'mini'].includes(value)  }  },  // 控制表单元素的禁用状态  disabled: {  type: Boolean,  default: false  }  },  // 经过provide将当前表单实例传递到全部后代组件中  provide() {  return {  customForm: this  }  } } </script>  复制代码

在上面代码中,咱们经过provide将当前组件的实例传递到后代组件中,provide是一个函数,函数返回的是一个对象

自定义表单项custom-form-item.vue

没有什么特殊的,只是加了一个label,element-ui更复杂一些

<template>
 <div class="custom-form-item">  <label class="custom-form-item__label">{{ label }}</label>  <div class="custom-form-item__content">  <slot></slot>  </div>  </div> </template> <script> export default {  props: {  label: {  type: String,  default: ''  }  } } </script>  复制代码

自定义输入框 custom-input.vue

<template>
 <div  class="custom-input"  :class="[  `custom-input--${getSize}`,  getDisabled && `custom-input--disabled`  ]"  >  <input class="custom-input__input" :value="value" @input="$_handleChange" />  </div> </template> <script> /* eslint-disable vue/require-default-prop */ export default {  props: {  // 这里用了自定义v-model  value: {  type: String,  default: ''  },  size: {  type: String  },  disabled: {  type: Boolean  }  },  // 经过inject 将form组件注入的实例添加进来  inject: ['customForm'],  computed: {  // 经过计算组件获取组件的size, 若是当前组件传入,则使用当前组件的,不然是否form组件的  getSize() {  return this.size || this.customForm.size  },  // 组件是否禁用  getDisabled() {  const { disabled } = this  if (disabled !== undefined) {  return disabled  }  return this.customForm.disabled  }  },  methods: {  // 自定义v-model  $_handleChange(e) {  this.$emit('input', e.target.value)  }  } } </script>  复制代码

form中,咱们经过provide返回了一个对象,在input中,咱们能够经过inject获取form中返回对象中的项,如上代码inject:['customForm']所示,而后就能够在组件内经过this.customForm调用form实例上面的属性与方法了

**在上面代码中咱们使用了自定义v-model,关于自定义v-model能够阅读小编前面的文章绝对干货~!学会这些Vue小技巧,能够早点下班和女神约会了 **

在项目中使用

<template>
 <custom-form size="small">  <custom-form-item label="姓名">  <custom-input v-model="formData.name" />  </custom-form-item>  </custom-form> </template> <script> import CustomForm from '../components/custom-form' import CustomFormItem from '../components/custom-form-item' import CustomInput from '../components/custom-input' export default {  components: {  CustomForm,  CustomFormItem,  CustomInput  },  data() {  return {  formData: {  name: '',  age: 0  }  }  } } </script>  复制代码

执行上面代码,运行结果为:

<form class="custom-form">
 <div class="custom-form-item">  <label class="custom-form-item__label">姓名</label>  <div class="custom-form-item__content">  <!--size=small已经添加到指定的位置了-->  <div class="custom-input custom-input--small">  <input class="custom-input__input">  </div>  </div>  </div> </form> 复制代码

经过上面的代码能够看到,input组件已经设置组件样式为custom-input--small

inject格式说明

除了上面代码中所使用的inject:['customForm']写法以外,inject还能够是一个对象。且能够指定默认值

修改上例,若是custom-input外部没有custom-form,则不会注入customForm,此时为customForm指定默认值

{
 inject: {  customForm: {  // 对于非原始值,和props同样,须要提供一个工厂方法  default: () => ({  size: 'default'  })  }  } } 复制代码

若是咱们但愿inject进来的属性的名字不叫customForm,而是叫parentForm,以下代码

inject: {
 // 注入的属性名称  parentForm: {  // 经过 from 指定从哪一个属性注入  from: 'customForm',  default: () => ({  size: 'default'  })  }  },  computed: {  // 经过计算组件获取组件的size, 若是当前组件传入,则使用当前组件的,不然是否form组件的  getSize() {  return this.size || this.parentForm.size  }  } 复制代码

使用限制

  1. provideinject的绑定不是可响应式的。可是,若是你传入的是一个可监听的对象,如上面的customForm: this,那么其对象的属性仍是可响应的。

  2. Vue官网建议provideinject 主要在开发高阶插件/组件库时使用。不推荐用于普通应用程序代码中。由于provideinject在代码中是不可追溯的(ctrl + f能够搜),建议可使用Vuex代替。 可是,也不是说不能用,在局部功能有时候用了做用仍是比较大的。

dispatch 和 broadcast ,这是一种有历史的组件通讯方式

$dispatch$broadcast是一种有历史的组件通讯方式,为何是有历史的,由于他们是Vue1.0提供的一种方式,在Vue2.0中废弃了。可是废弃了不表明咱们不能本身手动实现,像许多UI库内部都有实现。本文以element-ui实现为基础进行介绍。同时看完本节,你会对组件的$parent,$children,$options有所了解。

方法介绍

$dispatch: $dispatch会向上触发一个事件,同时传递要触发的祖先组件的名称与参数,当事件向上传递到对应的组件上时会触发组件上的事件侦听器,同时传播会中止。

$broadcast: $broadcast会向全部的后代组件传播一个事件,同时传递要触发的后代组件的名称与参数,当事件传递到对应的后代组件时,会触发组件上的事件侦听器,同时传播会中止(由于向下传递是树形的,因此只会中止其中一个叶子分支的传递)。

$dispatch实现与应用

1. 代码实现

/**  * 向上传播事件  * @param {*} eventName 事件名称  * @param {*} componentName 接收事件的组件名称  * @param {...any} params 传递的参数,能够有多个  */ function dispatch(eventName, componentName, ...params) {  // 若是没有$parent, 则取$root  let parent = this.$parent || this.$root  while (parent) {  // 组件的name存储在组件的$options.componentName 上面  const name = parent.$options.name  // 若是接收事件的组件是当前组件  if (name === componentName) {  // 经过当前组件上面的$emit触发事件,同事传递事件名称与参数  parent.$emit.apply(parent, [eventName, ...params])  break  } else {  // 不然继续向上判断  parent = parent.$parent  }  } }  // 导出一个对象,而后在须要用到的地方经过混入添加 export default {  methods: {  $dispatch: dispatch  } }  复制代码

2. 代码应用

在子组件中经过$dispatch向上触发事件

import emitter from '../mixins/emitter'
export default {  name: 'Chart',  // 经过混入将$dispatch加入进来  mixins: [emitter],  mounted() {  // 在组件渲染完以后,将组件经过$dispatch将本身注册到Board组件上  this.$dispatch('register', 'Board', this)  } } 复制代码

Board组件上经过$on监听要注册的事件

export default {
 name: 'Board',  created() {  this.$on('register',(component) => {  // 处理注册逻辑  })  } } 复制代码

$broadcast实现与应用

1. 代码实现

/**  * 向下传播事件  * @param {*} eventName 事件名称  * @param {*} componentName 要触发组件的名称  * @param {...any} params 传递的参数  */ function broadcast(eventName, componentName, ...params) {  this.$children.forEach(child => {  const name = child.$options.name  if (name === componentName) {  child.$emit.apply(child, [eventName, ...params])  } else {  broadcast.apply(child, [eventName, componentName, ...params])  }  }) }  // 导出一个对象,而后在须要用到的地方经过混入添加 export default {  methods: {  $broadcast: broadcast  } }  复制代码

2. 代码应用

在父组件中经过$broadcast向下触发事件

import emitter from '../mixins/emitter'
export default {
  name: 'Board',
  // 经过混入将$dispatch加入进来
  mixins: [emitter],
  methods:{
  	//在须要的时候,刷新组件
  	$_refreshChildren(params) {
  		this.$broadcast('refresh', 'Chart', params)
  	}
  }
}
复制代码

在后代组件中经过$on监听刷新事件

export default {
 name: 'Chart',  created() {  this.$on('refresh',(params) => {  // 刷新事件  })  } } 复制代码

总结

经过上面的例子,同窗们应该都能对$dispatch$broadcast有所了解,可是为何Vue2.0要放弃这两个方法呢?官方给出的解释是:”由于基于组件树结构的事件流方式实在是让人难以理解,而且在组件结构扩展的过程当中会变得愈来愈脆弱。这种事件方式确实不太好,咱们也不但愿在之后让开发者们太痛苦。而且 $dispatch$broadcast 也没有解决兄弟组件间的通讯问题。“

确实如官网所说,这种事件流的方式确实不容易让人理解,并且后期维护成本比较高。可是在小编看来,无论黑猫白猫,能抓老鼠的都是好猫,在许多特定的业务场景中,由于业务的复杂性,颇有可能使用到这样的通讯方式。可是使用归使用,可是不能滥用,小编一直就在项目中有使用。

结语

不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶。 ——文森特・梵高

若是喜欢小编,能够关注下面的微信群与公众号,了解更多干货。同时,小手一赞,艳遇不断。

本文使用 mdnice 排版

相关文章
相关标签/搜索