摘要
vue是前端开发中最重要的工具之一,是否能够对其熟练使用日渐成为衡量一个前端程序员是否及格的标准。但Vue生态日趋复杂,知识点较多,对初学或复习者来说查询较为麻烦,因此,本文整理了Vue2系列的高频问题,旨在帮助各位能够快速掌握这一开发工具
vue2的生命周期及其钩子函数
生命周期图

这是vue官方文档中所展示的生命周期图。从中我们可以看到:一个完整的vue应用总共分为四个过程:创建 -> 挂载->更新->销毁
其中,更新阶段(已挂载)是否执行取决于组件内部是否存在数据更新
所有生命周期钩子的 this 上下文将自动绑定至实例中,因此你可以访问 data、computed 和 methods。这意味着 你不应该使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。因为 箭头函数绑定了父级上下文,所以 this 不会指向预期的组件实例,并且this.fetchTodos 将会是 undefined,同理,也不应该在属性或回调中使用箭头函数:如created: () => console.log(this.fn());
钩子函数
vue 钩子函数在每个生命阶段都存在两个,用于提供在某个生命阶段到达前后的自定义操作空间
创建
beforecreate: // 这个阶段的data,methods,computed以及watch的数据和方法不能被访问
created: // 实例创建完毕后,实例上的属性以及数据便可访问和修改,但此时 dom 未被渲染,因此所有基于
// dom 的操作将失败。官方提供了 nextTick 函数延迟回调,更新数据后立即操作 dom
挂载
beforeMount: // 发生在页面渲染之前,当前阶段虚拟Dom已经创建完成,即将开始渲染
// 此时也可对数据进行修改,不会触发updated
mounted: // 在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定
// 可以访问到Dom节点,使用$refs属性对Dom进行操作
更新
beforeUpdate: // 在更新之前,即响应式数据发生更新前,虚拟dom重新渲染之前被触发
// 可在当前阶段进行更改数据,不会引起重复渲染
updated: // 更新后触发,当前阶段组件Dom已完成更新,若在当前阶段修改数据可能会导致无限循环的更新
销毁
beforeDestroy: // 发生在实例销毁之前,在当前阶段实例完全可被使用
// 这时可进行善后收尾工作,比如清除计时器,清空闭包变量
额外
activited: // keep-alive内组件被激活时调用
deactivited: // keep-alive内组件被销毁时调用
v-show 、v-if 与v-for
v-show:实现原理为display:none / block,因此其优势在于性能好,响应快,适用于需要频繁显示隐藏dom元素的情况
v-if:渲染前判断,渲染后若发现不符合条件的dom节点,需要将其移除或替换,性能花费较高,适合初次渲染时判断需要渲染的dom元素的情况(条件渲染)
v-for:循环渲染当前dom元素,一般使用 :key 作为元素的唯一标识
注:v-show在是否满足条件时都会渲染元素,之后根据条件判断是否隐藏该元素,v-if则在渲染阶段判断是否满足条件,若为真则开始渲染,为假则不做渲染,vue2 中v-for的优先级高于v-if,连用会导致渲染完后判断,容易造成性能浪费,因此不推荐v-if和v-for在同一个标签中同时使用
Vue2组件通信
父组件传递给子组件
在父组件调用子组件的位置通过 v-bind 指令(简写 :[属性名]),绑定需要传递给子组件的变量名,这里传递了一个 message 变量,为使用该变量,需要在 script 标签内定义(代码运行环境为cli脚手架)。子组件使用 props 属性接收父组件传递过来的数据
父组件内代码
使用 v-bind指令传递message变量
<template>
<!-- 调用了 newComponent 组件,并 -->
<newComponent :msg="message"></newComponent>
</template>
import newComponent from '@/components/newComponent';
export default {
name: 'index',
conponents: {
newComponent // 注册组件
},
data() {
return {
message: '父组件传递的消息'
}
}
}
子组件内代码
使用props属性接收数据,props可以是一个数组或对象,当为对象时则可对传入数据进行校验
<template>
<span>{{msg}}</span> <!-- msg 文字为 父组件传递的消息 ->
<template>
props属性可为数组,使用绑定时的名称接收
export default {
name: 'newComponent', // 非驼峰命名时可能会警告或报错
props: ['msg'],
data() {
return {
message: '父组件传递的消息'
}
}
}
props属性为对象时,可使用以下属性进行数据校验
export default {
name: 'newComponent', // 非驼峰命名时可能会警告或报错
props: {
msg: {
type: string, // 校验数据类型
default: 'hello vue!', // 不传入值时的默认值
require: true // 设置该属性是否必填
}
},
data() {
return {
message: '父组件传递的消息'
}
}
}
子组件传递给父组件
在子组件中使用v-on进行事件绑定(简写@[事件处理函数],使用$emit将需要传递的值填入触发父组件绑定函数的形参中
子组件
<template>
<button @click="fn_send">点击子传父</button>
</template>
export default {
name: 'newComponent',
data() {
return {
message: '子组件传递的消息'
}
}
methods: {
fn_send() {
// 使用全局方法 $emit 触发父组件绑定的自定义事件并将 message 变量传递给其处理函数的形参
this.$emit('fatherReceiveMethod', this.message)
}
}
}
父组件
父组件中需要在调用的子组件上绑定自定义事件,且与子组件触发的事件同名,并为此函数设置一个形参用于接收子组件传递的信息
<template>
<newComponent @fatherReceiveMethod="fn_receive"></newComponent>
</template>
import newComponent from '@/components/newComponent';
export default {
name: 'index',
conponents: {
newComponent // 注册组件
},
methods: {
fn_receive(msg) {
console.log(msg); // 输出 子组件传递的消息
}
}
}
补充:子组件也可通过$parent对父组件进行操作,父组件通过$children对子组件进行操作
兄弟组件互相传递
简介
vue目前常用的兄弟组件传值方式为两种:vuex和事件总线,这里先只描述下事件总线,vuex的使用方法后面会给出
事件总线指通过一个公共组件作为中间方传递消息,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件
使用方式
通过创建一个js文件并将其中的vue实例进行导出或直接在Vue原型上注册全局事件总线
// event-bus.js
import Vue from 'vue';
export const EventBus = new Vue();
// main.js
Vue.prototype.$EventBus = new Vue();
通过事件函数或生命周期钩子函数触发事件总线全局函数$emit,第一个参数为事件名,第二个参数为要传递的数据
<!-- A组件 -->
<template>
<button @click="sendMsg()"></button>
</template>
import { EventBus } from "../event-bus.js";
export default {
methods: {
sendMsg() {
EventBus.$emit("aMsg", 'A组件通过事件总线传递的消息');
}
}
};
通过$on全局函数接收事件总线的消息,第一个参数为要接收的事件名,第二个参数为要处理的函数,通过处理函数的第一个形参接收传递的数据。这个操作通常在生命周期钩子函数内使用
import { EventBus } from "../event-bus.js";
export default {
data(){
return {
message: ''
}
},
mounted() {
EventBus.$on("aMsg", msg => {
// A组件传递过来的消息
this.message = msg;
});
}
computed和watch的区别
computed
支持缓存,只在依赖的数据发生变化时,才会重新计算,否则调用缓存内计算好的结果
不支持异步,当涉及异步操作时无效,且无法监听数据的变化
更适用于一个数据是通过其他数据计算或推断而来的场景
watch
不支持缓存,每调用一次就计算一次
支持异步,通过deep属性深度监听对象,使用immediate属性可监听第一次变化
更适用于当数据发生变化时要执行操作的场景
注:methods同样不支持缓存,每调用一次就重新执行一次
data 函数形式和对象形式的区别
data为函数形式时,每一个函数都有自己的局部作用域因此每次实例化后的组件中的data都为独立数据,当改变数据时不会影响到复用的其他组件中的data
data为对象形式时,因为对象是引用类型,每个组件的data都是指向内存的同一个地址,如果修改一个对象里面的数据,其他实例的也会被影响到,即当修改某一个组件内data中的值,另一个复用的组件内data也会随之修改
slot 插槽
vue中的slot的作用是允许父组件向子组件传递内容,包括但不限于文本、HTML标签等,分别为:默认插槽、具名插槽和作用域插槽
默认插槽
使用slot标签放置在想要插入内容的区域,父组件在调用子组件时将需要插入的内容填入子组件的标签包裹区域即可。slot标签包裹区内可填写slot默认值
<!-- 子组件 slotComponent -->
<template>
<p>下方为slot内容</p>
<slot>此为slot内容的默认值</slot>
<p>上方为slot内容</p>
</template>
<!-- 父组件 -->
<template>
<slotComponent>
此内容将会替换子组件内slot标签的默认内容
</slotComponent>
</template>
具名插槽
顾名思义,即具有一个名称的插槽,使用方式是在默认插槽中添加name属性。父组件调用时需使用template标签包裹想要传递的内容,并在template标签上加上指令v-slot:slotname
注:slotname为自定义名称
<!-- 子组件 slotComponent -->
<template>
<p>下方为slot内容</p>
<slot name="slotname">此为slot内容的默认值</slot>
<p>上方为slot内容</p>
</template>
<!-- 父组件 -->
<template>
<slotComponent>
此内容将会替换子组件内slot标签的默认内容
<template v-slot:slotname>
<p>具名插槽内容</p>
</template>
</slotComponent>
</template>
作用域插槽
当父组件需要访问子组件内定义的变量时,可以通过作用域插槽来实现。子组件需要先设置成具名插槽并绑定需要被访问的数据
<!-- 子组件 slotComponent -->
<!-- 假设子组件有以下数据 -->
data() {
return {
student: {
name: 'lily',
age: 18,
sex: 'girl'
}
}
}
<template>
<p>下方为slot内容</p>
<slot name="slotname" :studentInfo="student">{{student.name}}</slot>
<p>上方为slot内容</p>
</template>
父组件需要使用v-slot:指令指定需要访问的插槽名称,并使用“=”号接收传递出来的值,并用新值访问子组件中绑定的数据的数据
<!-- 父组件 -->
<template>
<slotComponent>
此内容将会替换子组件内slot标签的默认内容
<template v-slot:slotname=“scopeName”>
{{scopeName.studnetInfo.age}}
</template>
</slotComponent>
</template>
nextTick
vue中的Dom更新是异步的,即当被处理数据是动态变化时,此时对应的Dom未能及时更新(同步更新)就会导致数据已经更新(model层已经更新)而视图层未更新(Dom未更新)但需要进行dom相关的操作时就需要使用nextTick来获取更新后的Dom的值
// 将需要执行的操作放入 nextTick 中会进入异步队列,dom更新后会立即执行
this.$nextTick(()=>{
// 在这里获取dom更新后的值
})
v-model实现原理
v-model 是 vue 中进行数据双向绑定的指令,在内部实际上是通过语法糖来完成数据的双向绑定,一种是绑定在普通表单元素上,一种是绑定在自定义组件上,两者在实现上也略微不同
select、checkbox、radio 语法糖对应的是 v-bind:value="something" 和 v-on:change="something = $event.target.value";
input、textarea 对应的语法糖有几种情况:
默认绑定事件为 input 事件;
如果在 v-model 绑定时用了 lazy 修饰符,那么它绑定的事件是 change;
如果有 type="range"属性,则绑定的事件是 __r;
如果有 trim 或者 number 修饰符,则会多绑定一个 blur 事件
当 v-model 绑定在自定义组件时,语法糖为 v-bind:value 和 v-on:input 或者自定义 model 选项
vueRouter
前端路由是在保证只有一个HTML页面的情况下,通过对每个视图展示形式匹配一个特殊的url来实现所谓的切换效果。不会重新向服务端发送请求,也不会跳转页面。无论是刷新、前进、还是后退,都可以通过特殊url实现,而vueRouter能更加方便的实现这种功能
vue-router 的钩子函数及其执行顺序
钩子函数种类有:全局守卫、路由守卫、组件守卫
全局前置/钩子:beforeEach、beforeResolve、afterEach
路由独享的守卫:beforeEnter
组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
完整导航时钩子函数的执行顺序
导航被触发
在失活的组件中调用beforeRouterLeave
调用全局的beforeEach
在重用的组件内调用beforeRouterUpdate
在路由配置里调用beforeEneter
解析异步路由组件
在被激活的组件内调用beforeRouterEnter
调用全局的beforeResolve
确认导航
调用全局的afterEach
触发DOM更新
调用beforeRouterEnter函数中传给next的回调函数,创建好的组件实例会作为函数的参数传入
vueRouter 动态路由
vue router可以通过动态路径参数实现同一路径多种状态的效果,通常有两种实现方式:params和query,两种方式均有优缺点
params方式
路由定义
// 在APP.vue中
<router-link :to="'/user/'+userId">用户</router-link>
//在index.js
{
path: '/user/:userid',
component: User,
},
路由跳转
// 方法1:
<router-link :to="{ name: 'lily', params: { age: 18 }}"> 按钮 </router-link>
// 方法2:
this.$router.push({name:'lily', params: { age: 18 }})
// 方法3:
this.$router.push('/user/' + 18)
注:通过 this.$route.params 获取传递的值
使用name跳转时需在路由注册阶段定义name值
query方式
路由定义
// 方式1:直接在 router-link 标签上以对象的形式传递
<router-link :to="{ path:'/link',query:{ name:'lily',age:18 } }">链接</router-link>
// 方式2:写成按钮以点击事件形式
<button @click='profileClick'>我的</button>
profileClick(){
this.$router.push({
path: "/link",
query: {
name: "lily",
age: "18"
}
});
}
路由跳转
// 方法1:
<router-link :to="{ name: 'lily', query: { id: '0010' }}">链接</router-link>
// 方法2:
this.$router.push({ name: 'lily', query:{ id: '0010' }})
// 方法3:
<router-link :to="{ path: '/link', query: { id: '0010' }}">链接</router-link>
// 方法4:
this.$router.push({ path: '/link', query:{ id: '0010' }})
// 方法5:
this.$router.push('/user?uname=' + jsmes)
注:通过 this.$route.query 获取传递的值
params 和 query 的区别
query name 和 path 属性都可以使用,params要用name来引入,接收参数分别为 this.$route.query 和 this.$route.params
query 在浏览器地址栏中显示参数,params则不显示
query刷新不会丢失数据, params刷新会丢失数据
$route 和 $router 的区别
$router为VueRouter实例,想要导航到不同URL,则使用$router.push方法
$route为当前router跳转对象,里面可以获取name、path、query、params等
vue-router 中常用的路由模式
hash 模式
location.hash 为 URL 中 # 后面的参数,它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面
每一次改变 hash(window.location.hash),都会在浏览器的访问历史中增加一个利用 hash 的记录,基于以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了
优缺点:兼容性好但是不美观,会有多余的 # 符号在地址栏的url内
history 模式
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础
优缺点:地址栏和正常的url访问一致但需要自定义404页面
vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,允许其在各个页面上实现数据的共享和操作
Vuex核心分成五个部分: State、Getters、Mutations、Actions、Module
const store = new Vuex.Store({
state: {num: 2}, // 存放数据
getters: {}, // 计算属性
mutations: {}, // 修改state中数据的一些方法
actions: {}, // 异步方法
modules: {} // store模块
})
export default store
使用方式
可通过$store.state调用state属性中的值
可使用 getters自定义对state的读取
通过mutations可以提交对state属性中的数据更改操作
使用actions可支持异步更改state中的数据
module提供了模块化vuex配置项,使模块具有更高的封装度和复用性
总结
本文目前就整理到这里,关于里面的知识点以后我会新开文章详细介绍,感谢阅读