前端架构模式
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 鼠标中键点击