阅读 248

vue 复盘总结一

前端架构模式

MVC

MVC简单来说是用户对View(视图)的操作交给了Controller处理相关业务逻辑,在Controller中响应View的事件调用Model的接口对数据进行操作,一旦Model发生变化便通知相关视图进行更新

  • 模型(Model):数据保存(数据库访问)
  • 视图(View):用户界面
  • 控制器(Controller):业务逻辑

MVVM

在 MVVM 模式中,View(视图) 和 Model(数据) 是不可以直接通讯的,在它们之间存在着 ViewModel 这个中间介充当着观察者的角色。当用户操作 View(视图),ViewModel 感知到变化,然后通知 Model 发生相应改变;反之当 Model(数据) 发生改变,ViewModel 也能感知到变化,使 View 作出相应更新。这个一来一回的过程就是我们所熟知的双向绑定。

  • Model模型层,主要处理相关业务逻辑处理和数据操作
  • View视图层:用户界面
  • ViewMode视图模型层:用来连接Model和View,是Model和View之间的通信桥梁(vue的核心-双向绑定)

双向绑定原理

主要通过Object.defineProperty+观察者模式实现的;get方法用于”依赖收集“,set方法用于”派发更新“,data中声明的属性,都有一个专属依赖收集器dep(观察者目标),手机相关的watcher(观察者),通知相关的依赖进行对应更新

vue 生命周期

生命周期钩子函数

生命周期钩子函数
阶段
描述使用场景
beforeCreate创建前实例初始化之后,创建之前,data、methods、computed以及watch上的数据和方法均不可访问初始化非响应式变量
created创建后实例创建后,data、methods、computed以及watch上的数据和方法可访问,el未挂载初始数据的获取
beforeMount载入前虚拟Dom已经创建完成,未挂载,$ref属性,DOM节点不可访问对数据进行更改,不会触发updated
mounted载入后vue实例挂载完成,可以通过DOM API获取到DOM节点,$ref属性可以访问可使用$refs属性对Dom进行操作
beforeUpdate更新前响应式数据更新时调用,发生在虚拟DOM打补丁之前;当前阶段操作数据,不会重渲染手动移除已添加的事件监听器
updated更新后虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作;避免在这个钩子函数中操作数据,可能陷入死循环重绑定事件监听器
beforeDestroy销毁前实例销毁前调用,实例还可以用,this能获取到实例常用于销毁定时器、解绑全局事件、销毁插件对象等操作
destroyed销毁后实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁

keep-alive中的生命周期钩子

  • activated 被 keep-alive 缓存的组件激活时调用。
  • deactivated 被 keep-alive 缓存的组件停用时调用。

生命周期执行顺序

页面间切换生命周期顺序

A页面跳转至B页面 : beforeCreate( A )-- created( A )--beforeMount( A )-- mounted( A )-- beforeCreate( B )-- created( B )--beforeMount( B )--beforeDestroy( A )--destroyed( A )-- mounted( B )

父子组件间执行顺序

渲染顺序

beforeCreate( 父 )-- created( 父 )--beforeMount( 父 ) -- beforeCreate( 子 )-- created( 子 )--beforeMount( 子 )-- mounted( 子 )-- mounted( 父 )

更新顺序

注意 : 在不影响父或子组件的情况下,均止执行各自的更新周期

  • 父组件更新过程

影响子组件 beforeUpdate( 父 )-- beforeUpdate( 子 )--updated( 子 )-- updated( 父 )

  • 子组件更新过程

影响父组件 beforeUpdate( 父 )-- beforeUpdate( 子 )--updated( 子 )-- updated( 父 )

销毁顺序

beforeDestroy( 父 )-- beforeDestroy( 子 )--destroyed( 子 )-- destroyed( 父 )

组件和minxin之间的执行顺序

mixin不是在哪个阶段,mix和被mix的组件的同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用

vue 指令

  • 指令 (Directives) 是带有 v- 前缀的特殊 attribute
  • 当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM

v-bind 指令

Vue中,提供的用于绑定属性的指令,可简写为:;该指令需要注意的几个绑定的属性有一下几种

class 绑定

  • 数组语法
<template>
 <button
   :class="[
     'mk-button',
     type ? 'mk-button--' + type : '',
     size ? 'mk-button--' + size : '',
     ,
     {
       'is-plain': plain,
     },
   ]"
 >
 </button>
</template>

复制代码
  • 对象语法
<template>
  <button
    :class="{
        'is-plain': plain,
        'is-disabled': disabled,
      }"
  >
  </button>
</template>
复制代码

style绑定

  • 对象语法
<template>
  <button :style="{
      'width' : btnWidth,
      'height' : btnHight
    }">
  </button>
</template>
复制代码
  • 数组语法
<template>
  <button :style="[{'color': 'red'},{
      'width' : btnWidth,
      'height' : btnHight
    }]">
    <h1>qqqqqqqqqqqqqqqq</h1>
  </button>
</template>
复制代码

src 绑定

当动态绑定img/video等元素的src的时候,vue数据绑定图片的相对路径或者是绝对路径的时候,需要require路径

<template>
 <img :src="require('@/assets/images/digital_audio_frame.png')" alt />
</template>
复制代码

相关修饰符

详细内容请查看 Vue修饰符

  • .prop
  • .camel
  • .sync

v-if、v-show、v-for

这几个指令在项目中经常使用,具体的使用方法不过多描述,在这块把注意点列出来

指令使用

在template元素上使用 v-if / v-for 可实现切换/遍历生成多个元素

v-if 和 v-show比较

  • v-if 每次切换都会进行销毁和重建,初始条件变为true时,才开始渲染;v-show,初始化直接渲染,使用display:none来隐藏或显示元素
  • 元素频繁切换,则使用v-show,减小切换开销;运行条件很少改变,则使用v-if,减小初始化开销

v-if 和 v-for

当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,每次列表渲染会执行一遍,因此不建议同时使用,可结合计算属性computed和filter实现同样效果

注意 : 如果存在列表中需使用v-if、v-else渲染不同的元素,则使用template这种虚拟标签将v-for分离出去

<template>
    <template v-for="(item, index) in list">
      <p :key="item.id" v-if="item.status ==1">{{item.title}}</p>
      <button :key="item.id" v-else-if="item.status ==2">{{item.title}}</button>
    </template>
</template>

复制代码

v-on

定义

v-on是用来绑定事件监听器,用在普通元素上时,只能监听原生 DOM 事件。用在自定义元素组件上时,也可以监听子组件触发的自定义事件。

语法:v-on:click="say" or v-on:click="say('参数', $event)"

相关修饰符

详细内容请查看 Vue修饰符

  • .stop
  • .prevent
  • .capture
  • .self
  • .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
  • .native
  • .once
  • .left
  • .right
  • .middle
  • .passive

v-model

v-model本质就是一个语法糖,可以看成是value + input方法的语法糖。 可以通过model属性的prop和event属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性

应用

  • 应用在组件上 : 就是一个语法糖 解析成value和事件 挂载到propsData,被当成组件的props属性(一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件);
  • 应用在标签上 : 真正的一个指令

v-model在组件中的用法

在组件模块会进行详细的说明

相关修饰符

详细内容请查看 Vue修饰符

  • .lazy
  • .number
  • .trim

v-cloak

可以隐藏未编译的 Mustache 标签直到实例准备完毕,防止刷新页面,网速慢的情况下出现{{ }}等数据格式

v-pre

用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。

v-once

表示元素和组件只渲染一次不会随着数据的改变而发生变化。

v-html

更新元素的 innerHTML

v-text

更新元素的 text文本

自定义指令

Vue核心思想是数据驱动、组件化,但有些情况下,我们仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令

注册方式

  • 全局注册
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
复制代码
  • 局部注册
const focus = {
  inserted: function (el, binding, vnode) {
    console.log(el)
    console.log(binding)
    console.log(vnode)
    // 聚焦元素
    el.focus()
  },
}
export default {
  name: 'directive',
  data() {
    //这里存放数据
    return {}
  },
  directives: {
    focus,
  },
}
复制代码

钩子函数

钩子函数描述函数参数
bind只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。el、binding、vnode
inserted被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。el、binding、vnode
update所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。el、binding、vnode oldVnode
componentUpdated指令所在组件的 VNode 及其子 VNode 全部更新后调用。el、binding、vnode oldVnode
unbind只调用一次,指令与元素解绑时调用el、binding、vnode

钩子函数参数

  • el:指令所绑定的元素,可以用来直接操作 DOM。
  • vnode:Vue 编译生成的虚拟节点。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
  • binding 相关属性,常用的大概是value、oldValue、arg、modifiers
属性值描述绑定来源
value指令的绑定值v-my-directive="2",绑定值为2
oldValue指令绑定的前一个值update 和 componentUpdated 钩子中可用
arg传给指令的参数,可以动态传参v-my-directive:left="2" arg为 "left"
modifiers一个包含修饰符的对象v-my-directive.mini="2" 值为{mini:true}

实际运用

  • arg(传给指令的参数,可以动态传参)的实际使用
Vue.directive('fixed', {
  bind: function (el, binding, vnode) {
    el.setAttribute(
      'style',
      `
      position : fixed
    `
    )
    const direction = binding.arg || 'left'  //获取指令参数
    const dirArr = ['left', 'right', 'top', 'bottom']
    dirArr.forEach((value) => {
      el.style[value] = null
    })
    el.style[direction] = binding.value + 'px'
  },
  update: function (el, binding, vnode) {
    const direction = binding.arg || 'left'
    const dirArr = ['left', 'right', 'top', 'bottom']
    dirArr.forEach((value) => {
      el.style[value] = null
    })
    el.style[direction] = binding.value + 'px'
  },
})

//使用
<button class="fabBtn" @click="isLoadingSwitch" v-fixed:[direction]="200">加载开关</button>    //动态传参

<button class="fabBtn" @click="isLoadingSwitch" v-fixed:left="200">加载开关</button>   //普通传参
复制代码
  • 如果只需使用bind 和update钩子,并且触发逻辑相同,可简写
//定位指令
Vue.directive('fixed', function (el, binding, vnode) {
  console.log('-----------执行')
  console.log(binding.value)
  el.setAttribute(
    'style',
    `
      position : fixed
    `
  )
  const direction = binding.arg || 'left'
  const dirArr = ['left', 'right', 'top', 'bottom']
  dirArr.forEach((value) => {
    el.style[value] = null
  })
  el.style[direction] = binding.value + 'px'
})
复制代码
  • value,oldValue、 modifiers(修饰符)实际运用

fullscreen、lock 为传递的修饰符; isLoading的值为绑定值

//使用
<div
  style="height:100px;width:100%"
  loading-background="rgba(0, 0, 0, 0.8)"
  v-mloading.lock="isLoading"
></div>


//loading指令
function loadMaskSwitch(el, binding, vnode) {
  const { value, modifiers } = binding //获取修饰符 和 绑定值
  const { fullscreen = false, lock = false } = modifiers
  let parentNode = fullscreen ? document.body : el
  if (value) {
    parentNode.style.position = 'relative'
    lock && (parentNode.style.overflow = 'hidden')
    parentNode.appendChild(el.mask)
    el.isMaskRender = true
  } else if (el.isMaskRender) {
    parentNode.removeChild(el.mask)
    el.isMaskRender = true
  }
}
Vue.directive('mloading', {
  bind: function (el, binding, vnode) {
    const ele = document.createElement('div')
    const { fullscreen = false, lock = false } = binding.modifiers //获取修饰符

    ele.setAttribute(
      'style',
      `
        position : ${fullscreen ? 'fixed' : 'absolute'};
        top : 0;
        right : 0;
        left : 0;
        bottom : 0;
        z-index : 99;
        display : flex;
        justify-content:center;
        align-items : center;
        background : rgba(0, 0, 0, 0.8);
        color : #fff
      `
    )
    ele.innerHTML = '加载中...'
    el.mask = ele
    loadMaskSwitch(el, binding, vnode)
  },
  update: function (el, binding, vnode) {
    loadMaskSwitch(el, binding, vnode)
  },
})
复制代码

绑定值也可传递为对象,传递loading 相关的背景,文本等配置,在这里不准备展开写,另一种方案是element采用的方案,将相关的配置通过属性配置,使用getAttribute获取绑定属性

// 使用
<div
  style="height:100px;width:100%"
  v-mloading.lock="isLoading"
  background="rgba(0, 0, 0, 0.8)"
  textColor="#409eff"
  :text="text"
></div>

// bind 钩子函数 的方法修改
bind: function (el, binding, vnode) {
    const ele = document.createElement('div')
    const { fullscreen = false, lock = false } = binding.modifiers //获取修饰符
    const bgColor = el.getAttribute('background') || 'rgba(0, 0, 0, 0.8)'
    const loadingText = el.getAttribute('text') || '加载中...'
    const textColor = el.getAttribute('textColor') || '#fff'
    ele.setAttribute(
      'style',
      `
        position : ${fullscreen ? 'fixed' : 'absolute'};
        top : 0;
        right : 0;
        left : 0;
        bottom : 0;
        z-index : 99;
        display : flex;
        justify-content:center;
        align-items : center;
        background : ${bgColor};
        color : ${textColor};
      `
    )
    ele.innerHTML = loadingText
    el.mask = ele
    loadMaskSwitch(el, binding, vnode)
}

复制代码

自定义指令也可以结合组件来使用;通过 Vue.extend() 创建构造器、

// 使用
<div
  style="height:100px;width:100%"
  v-mloading.lock="isLoading"
  background="rgba(0, 0, 0, 0.8)"
  textColor="#409eff"
  :text="text"
></div>


//loading.vue ,样式省略,可以自己写
<template>
  <transition name="el-loading-fade" @after-leave="handleAfterLeave">
    <div
      v-show="visible"
      class="el-loading-mask"
      :style="{ backgroundColor: background || '',color:textColor }"
      :class="[customClass, { 'is-fullscreen': fullscreen }]"
    >
      <div class="el-loading-spinner">
        <p v-if="text" class="el-loading-text">{{ text }}</p>
      </div>
    </div>
  </transition>
</template>

<script>
export default {
  data() {
    return {
      text: null,
      textColor: '#fff',
      spinner: null,
      background: null,
      fullscreen: true,
      visible: false,
      customClass: '',
    }
  },

  methods: {
    handleAfterLeave() {
      this.$emit('after-leave')
    },
  },
}
</script>

// 自定义指令
function loadMaskSwitch(el, binding, vnode) {
  const { value, modifiers } = binding //获取修饰符 和 绑定值
  const { fullscreen = false, lock = false } = modifiers
  let parentNode = fullscreen ? document.body : el
  if (value) {
    parentNode.style.position = 'relative'
    lock && (parentNode.style.overflow = 'hidden')
    parentNode.appendChild(el.mask)
    el.isMaskRender = true
  } else if (el.isMaskRender) {
    parentNode.removeChild(el.mask)
    el.isMaskRender = true
  }
}

Vue.directive('mloading', {
  bind: function (el, binding, vnode) {
    const { fullscreen = false, lock = false } = binding.modifiers //获取修饰符
    const { background, text, textColor } = vnode.data.attrs || {}
    const mask = new Mask({
      el: document.createElement('div'),
      data: {
        text: text,
        background: background,
        textColor: textColor,
        fullscreen: fullscreen,
        visible: true,
      },
    })
    el.instance = mask
    el.mask = mask.$el
    loadMaskSwitch(el, binding, vnode)
  },
  update: function (el, binding, vnode) {
    loadMaskSwitch(el, binding, vnode)
  },
})

复制代码

自定义指令也可通过vnode,触发组件的方法,但建议不要轻易使用,当触发方法对当前使用自定义组件的绑定值造成影响,会造成死循环!

分享一个管理项目中按钮权限的自定义指令

/**
 * @description: 按钮权限校验
 * @author: moonking_yue
 * @param {*} el
 * @param {*} binding  ----  value [btnType, btnPermissionKey] btnType:按钮对应权限key值  btnPermissionKey:当前页面按钮权限map的key
 * @return {*}
 */
function checkBtnPermission(el, binding) {
  const { value } = binding     //获取value 为 ['add']
  const [btnType, btnPermissionKey] = value
  if (value && value instanceof Array) {
    if (!btnType) {
      el.parentNode && el.parentNode.removeChild(el)
      return
    }
    //所有模块按钮权限集合
    const permissionBtn = store.getters.permissionBtn
    //当前页权限key
    const meta = router.currentRoute.meta || {}
    const permissionKey = btnPermissionKey || meta.permissionKey //获取当前路由权限集合的key值

    const currentBPArray = permissionBtn[permissionKey] || []
    if (currentBPArray.indexOf(btnType) == -1) {
      el.parentNode && el.parentNode.removeChild(el)
      return
    }
  } else {
    throw new Error(`need format Like v-permission="['add']"`)
  }
}

// v-btnPermission: 按钮权限
Vue.directive('btnPermission', {
  inserted(el, binding) {
    checkBtnPermission(el, binding)
  },
  update(el, binding) {
    checkBtnPermission(el, binding)
  },
})

<el-button type="primary" v-btnPermission="['add']"
    >测试按钮权限</el-button>
复制代码

Vue修饰符

表单修饰符

  • .lazy 取代 input 监听 change 事件;输入完成,光标离开再更新视图
  • .trim 过滤首尾空格
  • .number 限制你输入的只能是数字( 当初次输入的是非数字,则该修饰符失效 )

事件修饰符

  • .stop 阻止事件进行传递;,等同于event.stopPropagation()
  • .prevent 阻止事件的默认行为;等同于event.preventDefault()
  • .self 限制事件是由自身出发才进行处理,即事件冒泡出发该事件无效
  • .capture 事件在捕获阶段出发
  • .once 规定该事件只会触发一次
  • .passive 会立即出发事件的默认行为,即不会被event.preventDefault()影响
  • native 用来注册元素的原生事件而不是组件自定义事件的

按键修饰符

  • 按键码

.enter、.tab、.delete (捕获“删除”和“退格”键)、.esc、.space、.up、.down、.left、.right

  • 系统修饰键

.ctrl、.alt、.shift、.meta

  • .exact 修饰符

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

  • 自定义按键修饰符别名

Vue.config.keyCodes 可自定义修饰符别名

鼠标按钮修饰符

  • .left 鼠标左键点击
  • .right 鼠标右键点击
  • .middle 鼠标中键点击
文章分类
前端
文章标签