1、生命周期钩子
beforeCreate
第一个生命周期函数,表示实例完全被创建之前,会执行这个函数,在beforeCreate生命周期函数执行的时候,data 和 methods 中的数据还没有被初始化。
created(常用)
在 created 中,data 和 methods 都已经被初始化好了
如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作。
beforeMount
第三个生命周期函数,表示模板已经在内存中编译完成,但是尚未把模板渲染到页面中
在beforeMount执行的时候,页面中的元素没有被真正替换过来,只是之前写的一些模板字符串。
mounted(常用)
表示内存中的模板已经真实的挂载到页面中,用户已经可以看到渲染好的页面
这个mounted是实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,此时,如果没有其它操作的话,这个实例,就静静地在内存中不动。
beforeUpdate
表示 界面还没有被更新,但是数据肯定被更新了
得出结论:当执行 beforeUpdate 的时候,页面中的显示的数据,还是旧的,此时data 数据是最新的,页面尚未和 最新的数据保持同步。
updated
updated事件执行的时候,页面和 data 数据已经保持同步了,都是最新的
beforeDestroy
当执行beforeDestory钩子函数的时候,Vue实例就已经从运行阶段到了销毁阶段,当执行beforeDestory的时候,实例身上所有的data和所有的methods以及过滤器,指令…都处于可用状态,还没有真正的执行销毁的过程。
destroyed(常用)
当执行到了destroyed的时候,组件已经被完全销毁了,此时,组件中所有的数据,方法,指令都不可用了。
测试:1、created和mounted加载前后的问题。2、html的dom节点还没有创建完成就直接去操作dom导致报错
前端:书写顺序规范问题
2、vue实例上的内容
data定义变量
data() {
return {
}
},
测试:1、变量未定义就使用,导致undefined 2、定义变量的类型和赋值的类型不符合
前端:1、变量定义语义化。2、对于后端返回的值去赋值记得判空操作。
methods
methods: {}
内联处理器中的方法:
根据传入不同的内容,去处理不同的逻辑
也可传入$event,获取原生的事件对象
测试:方法中的逻辑有问题,引起的bug。
前端:
1、方法使用语义化的名称,尽量不要 data 对象的属性同名,造成一定的混淆。(方法return值在模板渲染)
2、如果方法多个地方用到,写到公共方法里面。
3、使用native修饰符来绑定构造器里的原生方法
跳转到test
其他事件修饰符
.stop .prevent .capture .self .once .native
...
...
如果要监听根元素的原生事件,可以使用 .native 修饰符
事件修饰符之间也有顺序,顺序不同,效果也不同
修饰符有:事件修饰符,按键修饰符,系统修饰键,.exact修饰符,鼠标按钮修饰符
测试:
1、点击穿透,只想点击上面一层的子元素,下面的父元素也被点击。(表格、折叠)
2、阻止默认事件(.prevent),表单
computed
计算属性
对于任何复杂逻辑,你都应当使用计算属性。
在模板中放入太多的逻辑会让模板过重且难以维护。
计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。否则就会使用缓存中的属性值
在vue中,computed的属性可以被视为是data一样,可以读取和设值。在computed中可以分为getter(读取)和setter(设值)
computed: {
// 计算属性的 getter
reversedMessage () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
当赋值给计算属性的时候,将调用setter函数。多用于在模板组件中需要修改计算属性自身的值的时候。
只有当计算属性中的属性被直接赋值的时候,才会走setter函数,而且,setter函数和getter函数是相互独立的
(示例)
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
console.log(newValue, 'newVal')
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
},
},
},
做联动效果的组件可考虑用这种方式。
watch
watch:{
a(val, oldVal){//普通的watch监听
},
b:{//深度监听,可监听到对象、数组的变化
handler(val, oldVal){
},
deep:true, //true 深度监听
immediate: true //立即执行
}
}
watch 的一个特点是,最初绑定的时候是不会执行的,如果想要一开始就让他最初绑定的时候就执行
immediate: true // 代表在wacth里声明了b这个方法之后立即先去执行handler方法
测试:1、数据联动改变 2、更改没起效果,又恢复成初始值。3、操作了一下,回来之后又消失了。
前端:1、在watch里面不要做过多的业务逻辑。2、不滥用监听(watch虽好,可不要贪杯哦~)
props
//props数组的形式
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
//props对象的形式
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
//props带有验证需求
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的布尔值
propF: {
type: Boolean,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 带有默认值的对象也可
meta: {
type: Object,
default: () => ({}),
},
// 带有默认值的数组
hotData: {
type: Array,
default: () => {
return []
},
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
},
}
测试:
1、传值类型错误,导致前端报错。
2、定义类型不一致。
3、子组件更改父组件传过来的值导致报错。
前端:
1、传值类型保证一致性。
2、书写规范,注意定义变量的一致性。
3、前端子组件不能随意更改父组件中的值。(抛事件,.sync)
provide/inject
允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
需要注意的是这里不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据。
provide:Object | () => Object
inject:Array | { [key: string]: string | Symbol | Object }
provide选项是一个对象或返回一个对象的函数。
inject选项是:
一个字符串数组,或
一个对象
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
注意:要实现父子组件响应,父组件传递的数据类型必须是对象Object,子组件接收的数据类型必须是对象Object,其他数据类型都是不好使的(示例)
补充:数组和对象的更新检测
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
//数组的更新检测
方式1:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
方式2:
this.$set(this.data, 1, {name:"huangenai",age:"22"})
//对象的更新检测
方式1:
this.$set(this.someObject,'b',2)
方式2:
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
测试:1、纯前端添加删除内容,页面没有更新到。2、操作了数据没有更新渲染到页面。
前端:两种方式灵活选择。尽量做到代码简洁。
3、vue-router
分为history模式和hash模式
hash 模式url 尾巴后带 # 号
vue-router 默认 hash 模式
命名路由
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
要链接到一个命名路由,可以给 router-link 的 to 属性传一个对象:
跳转到user
这跟代码调用 router.push() :
router.push({ name: 'user', params: { userId: 123 } })
这两种方式都会把路由导航到 /user/123 路径。
嵌套路由
routes: [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 中
path: 'posts',
component: UserPosts
}
]
}
]
路由的跳转方式
1、声明式
2、编程式
router.push(...)
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }}) // -> /user/123
// 带查询参数
router.push({ path: 'register', query: { plan: 'private' }}) // -> /register?plan=private
const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 注意:如果你写path,后面写params传参。这里的 params 不生效
router.push({ path: ‘/user’, params: { userId }}) // -> /user
这样的规则也适用于 router-link 组件的 to 属性
router.replace
跟 router.push很像,唯一的不同就是:它不会向 history 添加新记录,是替换掉当前的 history 记录。
router.go
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步
正数向前,负数后退
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
测试:
1、该跳转的页面跳转错误。
2、前进后退一步操作多了,乱掉了。
3、权限路由。
前端:注意跳转带参数url有大小限制(Google8182字节,IE:2048字节)
路由重定向
routes: [
{ path: '/a', redirect: '/b' }
]
命名视图
//如果 router-view 没有设置名字,那么默认为 default
routes: [
{
path: '/',
components: {
default: Default, // Default组件
header: Header, // Header组件
content: Content, // Content组件
footer: Footer // Footer组件
}
}
]
路由的导航守卫
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
beforeRouteEnter (to, from, next) {
// 不能获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
beforeRouteEnter 守卫不能访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
注意:
beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteLeave
导航守卫-离开守卫
可用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
beforeRouteLeave (to, from, next) {
const answer = window.confirm('Do you really want to leave? ')
if (answer) {
next()
} else {
next(false)
}
}
beforeRouteLeave(to, from, next) {
// 从列表页去到别的页面,如果不是详情页,则不缓存列表页,不好用
if (to.path === '/detail') {
from.meta.keepAlive = true;
} else {
from.meta.keepAlive = false;
}
next();
}
测试:
1、做权限的管理认证
2、路由遇到特定标记做跳转
3、每次路由切换或页面的刷新去判断用户是否已经登录
前端:注意next()。必须调用next() 才能继续往下执行下一个钩子,否则路由跳转会停止。
next()//直接进to 所指路由
next(false) //中断当前路由
next('route') //跳转指定路由
next('error') //跳转错误路由
4、组件
组件化思想
造轮子
提升代码复用性
示例
slot插槽-组件的扩展
在vue中实现的一套分发内容的API,将slot元素作为承载内容分发出口。
具名插槽
//插槽可以这样写
//用法
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
具名插槽的缩写
v-slot 缩写成#
Here might be a page title
作用域插槽
作用域插槽主要解决的问题是:当父组件向子组件插槽传递模板内容时,父组件访问子组件的数据。
//这是子组件
{{user.name}}
当父组件引用子组件时,想将子组件中的user.name替换为user.age时,不可以直接在父组件中更改子组件中的user.name,因为在官方文档中,父级模板所有内容都是在父级作用域中编译的,子级模板所有内容都是在子作用域中编译的。
为了解决子组件的user不能在父组件中使用的问题,我们可以将user用属性绑定的方式传给父组件,父组件用一个自定义名称进行接收
{{user.name}}
{{slotProps.user.age}}
2.6.0之前slot-scope
antd表格
在子组件中,将自己的数据用属性绑定的方式传输给父组件,父组件在template中用v-slot=“自定义名”,进行接收,再通过自定义名.进行数据访问。
测试:不用插槽定位上去,脱离文档流,不同屏幕分辨率下显示错位
前端:合理利用插槽可做到组件灵活扩展,增强健壮性。
vue内置组件keep-alive
如果没有缓存,每点击一次导航,内容区就会创建一个组件,该组件会经历整个生命周期,每点击一次,就会创建一个组件,比较浪费性能,
这时,我们就要考虑到是否能将点击过的已创建的组件进行缓存,当再次点击已访问过的组件时,这时,就会从缓存中获取该组件,而不会重新创建,这就用到keep-alive.
Props:
include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
max - 数字。最多可以缓存多少组件实例。
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。主要用于保留组件状态或避免重新渲染。
当组件在 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。
include(会缓存的) 和 exclude(不会缓存的) 二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:
匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称
(父组件 components 选项的键值)。匿名组件不能被匹配。
keep-alive对应两个生命周期, activated(){} deactivated(){}
当从缓存中读取a组件时,此时a组件处于活跃状态,
当从缓存中读取b组件时,a组件处于缓存状态,此时b组件处于活跃状态,
用途:
例如: 当在a组件浏览小说到某个位置,这时,我切换到b组件,
那么就用a组件的缓存状态函数( deactivated )记录这个位置,
当我再次切换到a组件,用活跃状态函数( activated ) 回到该位置
activated() {
console.log("活跃状态");
},
deactivated() {
console.log("缓存状态")
}
最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,
已缓存组件中最久没有被访问的实例会被销毁掉。
复杂情况可以使用: meta配合keepalive
export default [
{
path:'/',
name:'home',
components:Home,
meta:{
keepAlive:true //需要被缓存的组件
},
{
path:'/book',
name:'book',
components:Book,
meta:{
keepAlive:false //不需要被缓存的组件
}
]
测试:切换回页面之后,页面不会刷新。
前端:1、组件的name值一定要写,并且保证是唯一的。2、结合实际情况、具体的应用场景去做
5、指令
v-if/v-show
v-if显示隐藏是将dom元素整个添加或删除,而v-show隐藏则是为该元素添加css: display:none,dom元素还在。
v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;
v-show只是简单的基于css切换;
性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
使用场景:v-if适合运营条件不大可能改变;v-show适合频繁切换。
v-for
//遍历数组
{{item.message}}
//遍历对象
--键是--{{key}}--值是--{{val}}
测试:1、点的速度快,导致页面错乱 2、显示的时候输入内容,隐藏再显示内容还在,应该重置
前端:根据不同的场景使用对应的
6、vuex
专业介绍:Vuex是一个专为Vue开发的应用程序的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
就是当你的组件使用到了这个Vuex的状态,一旦它改变了,所有关联的组件都会自动更新相对应的数据
state: { // 页面需要的状态,原先需要在组件的data选项中定义
},
getters: { // state中数据的计算属性---组件中的computed
},
actions: { // 异步操作,请求数据
},
mutations: { // 唯一改变数据源的方式,可以在action中调用,也可以直接在组件内调用
}
测试:1、页面显示不一致2、相同的地方要一个一个测,导致测试效率低。
前端:不用vuex,当做中大型项目时,会有一大堆组件之间的通信和逻辑代码。统一管理组件之间共享的数据,减少冗余代码,提升代码健壮性、可维护性
7、API
$nextTick
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用。
在created()里使用this.$nextTick()可以等待dom生成以后再来获取dom对象
$set
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi')
//数组的更新检测
this.$set(this.data, 1, {name:"huangenai",age:"22"})
//对象的更新检测
方式1:
this.$set(this.someObject,'b',2)
测试:1、还没有这个节点去操作这个节点。2、数据更新完,没有显示到页面上。3、操作了数据没有更新渲染到页面。
8、过滤器
用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式
过滤器添加在 JavaScript 表达式的尾部,由“管道”符号指示
{{ message | filterCapitalize }}
全局定义过滤器 局部定义过滤器 这里写法需要注意:全局注册是filter,没有s的,但局部注册的时候是filters,是有s的 当全局过滤器和局部过滤器重名时,会采用局部过滤器。过滤器是 JavaScript 函数,因此可以接收参数:
{{ message | filterA(‘arg1’, arg2) }}
这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,(过滤器函数始终以表达式的值作为第一个参数)
普通字符串 ‘arg1’ 作为第二个参数,表达式 arg2 的值作为第三个参数。
过滤器可以串联:
前提是这两个过滤器处理的不冲突
{{ message | filterA | filterB }}
用户从input输入的数据在回传到model之前也可以先处理
9、混入
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来,这样就可以提高代码的重用性,使你的代码保持干净和易于维护。
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
有冲突时,会以组件内部的数据优先。
什么时候用Mixins
当我们存在多个组件中的数据或者功能很相近时,我们就可以利用mixins将公共部分提取出来
详细看示例
测试:相同的逻辑只测一处就可以了。
前端:出现问题只改一处,提高开发效率
10、自定义指令
如果需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令
使用场景:
按钮权限
Vue.directive('permission', {
inserted: function(el, data) {
const btnVals = data.value[0].split(',')
const currentRouteName = data.value[1] || router.currentRoute.name
btnVals.some(val => {
if (!routePerm[currentRouteName].includes(val)) {
el.parentNode.removeChild(el) //移除Dome节点
return true
}
})
}
})
使用:
编辑
优化图片的加载
在图片未完成加载前,用随机的背景色占位,图片加载完成后才直接渲染出来。
Vue.directive('img', {
inserted: function(el, binding) {
const color = Math.floor(Math.random() * 1000000)
el.style.backgroundColor = `#${color}`
const img = new Image()
img.src = binding.value
img.onload = function() {
el.style.backgroundImage = 'url(' + binding.value + ')'
}
}
})
使用:
测试:显示有问题,效果没实现
前端:加个指令快速便捷的实现功能。解放双手,提高开发效率。