Vue使用
概述
基本使用,组件使用—常用,必须会
高级特性—不常用,但体现深度
Vuex和Vue-router的使用
面试题
v-show和v-if的区别
为何v-for中要用key
描述Vue组件生命周期(有父子组件的情况)
Vue组件如何通讯
描述组件渲染和更新的过程
双向数据绑定v-model的实现原理
Vue基本使用
日常使用,必须掌握,面试必考(不一定会考)
梳理知识点,从冗长的文档中摘出考点和重点
考察形式不限(参考后面的面试真题),但都在范围之内
模板(指令、插值)
插值、表达式
指令、动态属性
v-html:会有XSS风险,会覆盖子组件
<template>
<div>
<p>文本插值 {{message}}</p>
<p>JS 表达式 {{ flag ? 'yes' : 'no' }} (只能是表达式,不能是 js 语句)</p>
<p :id="dynamicId">动态属性 id</p>
<hr/>
<p v-html="rawHtml">
<span>有 xss 风险</span>
<span>【注意】使用 v-html 之后,将会覆盖子元素</span>
</p>
<!-- 其他常用指令后面讲 -->
</div>
</template>
<script>
export default {
data() {
return {
message: 'hello vue',
flag: true,
rawHtml: '指令 - 原始 html <b>加粗</b> <i>斜体</i>',
dynamicId: `id-${Date.now()}`
}
}
}
</script>
computed和watch
computed有缓存,data不变则不会重新计算
watch如何深度监听
watch监听引用类型,拿不到oldVal
<template>
<div>
<p>num {{num}}</p>
<p>double1 {{double1}}</p>
<input v-model="double2"/>
</div>
</template>
<script>
export default {
data() {
return {
num: 20
}
},
computed: {
double1() {
return this.num * 2
},
double2: {
get() {
return this.num * 2
},
set(val) {
this.num = val/2
}
}
}
}
</script>
<template>
<div>
<input v-model="name"/>
<input v-model="info.city"/>
</div>
</template>
<script>
export default {
data() {
return {
name: '双越',
info: {
city: '北京'
}
}
},
watch: {
name(oldVal, val) {
// eslint-disable-next-line
console.log('watch name', oldVal, val) // 值类型,可正常拿到 oldVal 和 val
},
info: {
handler(oldVal, val) {
// eslint-disable-next-line
console.log('watch info', oldVal, val) // 引用类型,拿不到 oldVal 。因为指针相同,此时已经指向了新的 val
},
deep: true // 深度监听
}
}
}
</script>
class和style
使用动态属性
使用驼峰式写法
<template>
<div>
<p :class="{ black: isBlack, yellow: isYellow }">使用 class</p>
<p :class="[black, yellow]">使用 class (数组)</p>
<p :style="styleData">使用 style</p>
</div>
</template>
<script>
export default {
data() {
return {
isBlack: true,
isYellow: true,
black: 'black',
yellow: 'yellow',
styleData: {
fontSize: '40px', // 转换为驼峰式
color: 'red',
backgroundColor: '#ccc' // 转换为驼峰式
}
}
}
}
</script>
<style scoped>
.black {
background-color: #999;
}
.yellow {
color: yellow;
}
</style>
条件渲染
v-if v-else的用法,可使用变量,也可以使用===表达式
v-if和v-show的区别
v-if和v-show的使用场景:切换频繁使用v-show
v-show和v-if都能控制元素的显示和隐藏。
v-show本质就是通过设置css中的display设置为none,控制隐藏,无论true或者false初始都会进行渲染,因此切换开销比较小,初始开销较大
v-if是动态的向DOM树内添加或者删除DOM元素,因为懒加载,初始为false时,不会渲染,因此初始渲染开销较小,切换开销比较大,要不停的销毁和创建)
切换频繁使用v-show
<template>
<div>
<p v-if="type === 'a'">A</p>
<p v-else-if="type === 'b'">B</p>
<p v-else>other</p>
<p v-show="type === 'a'">A by v-show</p>
<p v-show="type === 'b'">B by v-show</p>
</div>
</template>
<script>
export default {
data() {
return {
type: 'a'
}
}
}
</script>
循环(列表)渲染
如何遍历对象?—也可以用v-for
key的重要性。key不能乱写(如random或者index)
v-for和v-if不能一起使用
因为v-for的优先级比v-if高,意味着v-if 将分别重复运行于每个 v-for 循环中,造成不必要的计算,影响性能
解决方法:将 v-if 置于外层元素 或在计算属性中进行条件过滤
<template>
<div>
<p>遍历数组</p>
<ul>
<li v-for="(item, index) in listArr" :key="item.id">
{{index}} - {{item.id}} - {{item.title}}
</li>
</ul>
<p>遍历对象</p>
<ul >
<li v-for="(val, key, index) in listObj" :key="key">
{{index}} - {{key}} - {{val.title}}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
flag: false,
listArr: [
{ id: 'a', title: '标题1' }, // 数据结构中,最好有 id ,方便使用 key
{ id: 'b', title: '标题2' },
{ id: 'c', title: '标题3' }
],
listObj: {
a: { title: '标题1' },
b: { title: '标题2' },
c: { title: '标题3' },
}
}
}
}
</script>
事件
event参数,自定义参数
事件修饰符,按键修饰符
【观察】事件被绑定到哪里
<template>
<div>
<p>{{num}}</p>
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+2</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 0
}
},
methods: {
increment1(event) {
// eslint-disable-next-line
console.log('event', event, event.__proto__.constructor) // 是原生的 event 对象
// eslint-disable-next-line
console.log(event.target)
// eslint-disable-next-line
console.log(event.currentTarget) // 注意,事件是被注册到当前元素的,和 React 不一样
this.num++
// 1. event 是原生的
// 2. 事件被挂载到当前元素
// 和 DOM 事件一样
},
increment2(val, event) {
// eslint-disable-next-line
console.log(event.target)
this.num = this.num + val
},
loadHandler() {
// do some thing
}
},
mounted() {
window.addEventListener('load', this.loadHandler)
},
beforeDestroy() {
//【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
// 自己绑定的事件,需要自己销毁!!!
window.removeEventListener('load', this.loadHandler)
}
}
</script>
表单
v-model
常见表单项textarea checkbox radio select
修饰符lazy number trim
当添加.lazy修饰符之后,相当于 双向数据绑定不起作用了,主要用于控制数据同步的时机,改变input框中的内容并不会使数据发生变化,当输入框失去焦点后触发change事件,数据才更新
<template>
<div>
<p>输入框: {{name}}</p>
<input type="text" v-model.trim="name"/>
<input type="text" v-model.lazy="name"/>
<input type="text" v-model.number="age"/>
<p>多行文本: {{desc}}</p>
<textarea v-model="desc"></textarea>
<!-- 注意,<textarea>{{desc}}</textarea> 是不允许的!!! -->
<p>复选框 {{checked}}</p>
<input type="checkbox" v-model="checked"/>
<p>多个复选框 {{checkedNames}}</p>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<p>单选 {{gender}}</p>
<input type="radio" id="male" value="male" v-model="gender"/>
<label for="male">男</label>
<input type="radio" id="female" value="female" v-model="gender"/>
<label for="female">女</label>
<p>下拉列表选择 {{selected}}</p>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>下拉列表选择(多选) {{selectedList}}</p>
<select v-model="selectedList" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
name: '双越',
age: 18,
desc: '自我介绍',
checked: true,
checkedNames: [],
gender: 'male',
selected: '',
selectedList: []
}
}
}
</script>
组件
props和$emit
组件间通讯-自定义事件
//index.vue
<template>
<div>
<Input @add="addHandler"/>
<List :list="list" @delete="deleteHandler"/>
</div>
</template>
<script>
import Input from './Input'
import List from './List'
export default {
components: {
Input,
List
},
data() {
return {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
}
]
}
},
methods: {
addHandler(title) {
this.list.push({
id: `id-${Date.now()}`,
title
})
},
deleteHandler(id) {
this.list = this.list.filter(item => item.id !== id)
}
},
created() {
// eslint-disable-next-line
console.log('index created')
},
mounted() {
// eslint-disable-next-line
console.log('index mounted')
},
beforeUpdate() {
// eslint-disable-next-line
console.log('index before update')
},
updated() {
// eslint-disable-next-line
console.log('index updated')
},
}
</script>
//Input.vue
<template>
<div>
<input type="text" v-model="title"/>
<button @click="addTitle">add</button>
</div>
</template>
<script>
import event from './event'
export default {
data() {
return {
title: ''
}
},
methods: {
addTitle() {
// 调用父组件的事件
this.$emit('add', this.title)
// 调用自定义事件
event.$emit('onAddTitle', this.title)
this.title = ''
}
}
}
</script>
//event.js
import Vue from 'vue'
export default new Vue()
//List.vue
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id">
{{item.title}}
<button @click="deleteItem(item.id)">删除</button>
</li>
</ul>
</div>
</template>
<script>
import event from './event'
export default {
// props: ['list']
props: {
// prop 类型和默认值
list: {
type: Array,
default() {
return []
}
}
},
data() {
return {
}
},
methods: {
deleteItem(id) {
this.$emit('delete', id)
},
addTitleHandler(title) {
// eslint-disable-next-line
console.log('on add title', title)
}
},
created() {
// eslint-disable-next-line
console.log('list created')
},
mounted() {
// eslint-disable-next-line
console.log('list mounted')
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler)
},
beforeUpdate() {
// eslint-disable-next-line
console.log('list before update')
},
updated() {
// eslint-disable-next-line
console.log('list updated')
},
beforeDestroy() {
// 及时销毁,否则可能造成内存泄露
event.$off('onAddTitle', this.addTitleHandler)
}
}
</script>
生命周期
单个组件
- 挂载阶段(beforeCreate、created、beforeMount、mounted)
- 更新阶段(beforeUpdate、updated)
- 销毁阶段(beforeDestory、destroyed )
created和mounted区别
created把vue示例给初始化,只是存在js内存模型的内存变量而已,并没有开始渲染;
mounted组件真正在网页上汇聚完成了,页面渲染完成了
beforeDestory上可以做什么
解除绑定,销毁子组件以及事件监听器
父子组件
创建初始化vue实例是从外到内,只有父组件初始化完才能初始化子组件
渲染是从内到外,只有把子组件渲染完父组件才能渲染完
beforeUpdate:更新的时候父组件的data首先被修改,首先执行beforeUpdate
updated:只有子组件更新完了父组件才能更新完
渲染过程:父组件挂载完成一定是等子组件都挂载完成后,才算是父组件挂载完,所以父组件的mounted在子组件mouted之后。父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
子组件更新过程:
影响到父组件: 父beforeUpdate -> 子beforeUpdate->子updated -> 父updated
不影响父组件: 子beforeUpdate -> 子updated
父组件更新过程:
影响到子组件: 父beforeUpdate -> 子beforeUpdate->子updated -> 父updated
不影响子组件: 父beforeUpdate -> 父updated
销毁过程:父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
不管是哪种情况,都一定是父组件等待子组件完成后,才会执行自己对应完成的钩子
高级特性
//index.vue
<template>
<div>
<p>vue 高级特性</p>
<hr>
<!-- 自定义 v-model -->
<!-- <p>{{name}}</p>
<CustomVModel v-model="name"/> -->
<!-- nextTick -->
<!-- <NextTick/> -->
<!-- slot -->
<!-- <SlotDemo :url="website.url">
{{website.title}}
</SlotDemo> -->
<!-- <ScopedSlotDemo :url="website.url">
<template v-slot="slotProps">
{{slotProps.slotData.title}}
</template>
</ScopedSlotDemo> -->
<!-- 动态组件 -->
<!-- <component :is="NextTickName"/> -->
<!-- 异步组件 -->
<!-- <FormDemo v-if="showFormDemo"/>
<button @click="showFormDemo = true">show form demo</button> -->
<!-- keep-alive -->
<!-- <KeepAlive/> -->
<!-- mixin -->
<MixinDemo/>
</div>
</template>
<script>
// import CustomVModel from './CustomVModel'
// import NextTick from './NextTick'
// import SlotDemo from './SlotDemo'
// import ScopedSlotDemo from './ScopedSlotDemo'
// import KeepAlive from './KeepAlive'
import MixinDemo from './MixinDemo'
export default {
components: {
// CustomVModel
// NextTick
// SlotDemo,
// ScopedSlotDemo,
// FormDemo: () => import('../BaseUse/FormDemo'),
// KeepAlive
MixinDemo
},
data() {
return {
name: '双越',
website: {
url: 'http://imooc.com/',
title: 'imooc',
subTitle: '程序员的梦工厂'
},
// NextTickName: "NextTick",
showFormDemo: false
}
}
}
</script>
自定义v-model
//index.vue
<template>
<div>
<!-- 自定义 v-model -->
<p>{{name}}</p>
<CustomVModel v-model="name"/>
</div>
</template>
<script>
import CustomVModel from './CustomVModel'
export default {
components: {
CustomVModel
},
data() {
return {
name: '双越',
}
}
}
</script>
//CustomVModel.vue
<template>
<!-- 例如:vue 颜色选择 -->
<input type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
>
<!--
1. 上面的 input 使用了 :value 而不是 v-model
2. 上面的 change1 和 model.event 要对应起来
3. text1 属性对应起来
-->
</template>
<script>
export default {
model: {
prop: 'text1', // 对应 props text1
event: 'change1'
},
props: {
text1: String,
default() {
return ''
}
}
}
</script>
refs与$nextTick
Vue是异步渲染(原理部分会详细讲解)
data改变之后,DOM不会立刻渲染
$nextTick会在DOM渲染之后被处罚,以获取最新DOM节点
//index.vue
<template>
<div>
<!-- nextTick -->
<NextTick/>
</div>
</template>
<script>
import NextTick from './NextTick'
export default {
components: {
NextTick
},
data() {
return {
}
}
}
</script>
//NextTick.vue
<template>
<div id="app">
<ul ref="ul1">
<li v-for="(item, index) in list" :key="index">
{{item}}
</li>
</ul>
<button @click="addItem">添加一项</button>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem() {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
// 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
this.$nextTick(() => {
// 获取 DOM 元素
const ulElem = this.$refs.ul1
// eslint-disable-next-line
console.log( ulElem.childNodes.length ) //没有$nextTick返回3,6,有返回6,9
})
}
}
}
</script>
slot
基本使用
//index.vue
<template>
<div>
<!-- slot -->
<SlotDemo :url="website.url">
{{website.title}}
</SlotDemo>
</div>
</template>
<script>
import SlotDemo from './SlotDemo'
export default {
components: {
SlotDemo
},
data() {
return {
website: {
url: 'http://imooc.com/',
title: 'imooc',
subTitle: '程序员的梦工厂'
},
}
}
}
</script>
//SlotDemo.vue
<template>
<a :href="url">
<slot>
默认内容,即父组件没设置内容时,这里显示
</slot>
</a>
</template>
<script>
export default {
props: ['url'],
data() {
return {}
}
}
</script>
作用域插槽
//index.vue
<template>
<div>
<ScopedSlotDemo :url="website.url">
<template v-slot="slotProps">
{{slotProps.slotData.title}}
</template>
</ScopedSlotDemo>
</div>
</template>
<script>
import ScopedSlotDemo from './ScopedSlotDemo'
export default {
components: {
ScopedSlotDemo
},
data() {
return {
website: {
url: 'http://imooc.com/',
title: 'imooc',
subTitle: '程序员的梦工厂'
},
}
}
}
</script>
//ScopedSlotDemo.vue
<template>
<a :href="url">
<slot :slotData="website">
{{website.subTitle}} <!-- 默认值显示 subTitle ,即父组件不传内容时 -->
</slot>
</a>
</template>
<script>
export default {
props: ['url'],
data() {
return {
website: {
url: 'http://wangEditor.com/',
title: 'wangEditor',
subTitle: '轻量级富文本编辑器'
}
}
}
}
</script>
具名插槽
动态组件
:is="component-name"用法
需要根据数据,动态渲染的场景,即组件类型不确定
比如一个新闻详情页,可能包含text、image、video组件,而且排序展示可能不一致
//index.vue
<template>
<div>
<!-- 动态组件 -->
<!-- <component :is="NextTickName"/> -->
<div v-for="(val,key) in newsData" :key="key">
<component :is="val.tye"/>
</div>
</div>
</template>
<script>
import ScopedSlotDemo from './ScopedSlotDemo'
export default {
components: {
ScopedSlotDemo
},
data() {
return {
//NextTickName: "NextTick",
newsData:[
1:{type:'text},
2:{type:'text},
3:{type:'image},
]
}
}
}
</script>
异步组件
import()函数
按需加载,异步加载大组件
//index.vue
<template>
<div>
<!-- 异步组件 -->
<FormDemo v-if="showFormDemo"/>
<button @click="showFormDemo = true">show form demo</button>
</div>
</template>
<script>
export default {
components: {
FormDemo: () => import('../BaseUse/FormDemo')
},
data() {
return {
showFormDemo: false
}
}
}
</script>
keep-alive
缓存组件
频繁切换,不需要重复渲染
Vue常见性能优化
//index.vue
<template>
<div>
<!-- keep-alive -->
<KeepAlive/>
</div>
</template>
<script>
import KeepAlive from './KeepAlive'
export default {
components: {
KeepAlive
},
data() {
return {
showFormDemo: false
}
}
}
</script>
//KeepAlive.vue
<template>
<div>
<button @click="changeState('A')">A</button>
<button @click="changeState('B')">B</button>
<button @click="changeState('C')">C</button>
<keep-alive> <!-- tab 切换 -->
<KeepAliveStageA v-if="state === 'A'"/> <!-- v-show -->
<KeepAliveStageB v-if="state === 'B'"/>
<KeepAliveStageC v-if="state === 'C'"/>
</keep-alive>
</div>
</template>
<script>
import KeepAliveStageA from './KeepAliveStateA'
import KeepAliveStageB from './KeepAliveStateB'
import KeepAliveStageC from './KeepAliveStateC'
export default {
components: {
KeepAliveStageA,
KeepAliveStageB,
KeepAliveStageC
},
data() {
return {
state: 'A'
}
},
methods: {
changeState(state) {
this.state = state
}
}
}
</script>
//KeepAliveStateA.vue
<template>
<p>state A</p>
</template>
<script>
export default {
mounted() {
// eslint-disable-next-line
console.log('A mounted')
},
destroyed() {
// eslint-disable-next-line
console.log('A destroyed')
}
}
</script>
//KeepAliveStateB.vue
<template>
<p>state B</p>
</template>
<script>
export default {
mounted() {
// eslint-disable-next-line
console.log('B mounted')
},
destroyed() {
// eslint-disable-next-line
console.log('B destroyed')
}
}
</script>
//KeepAliveStateC.vue
<template>
<p>state C</p>
</template>
<script>
export default {
mounted() {
// eslint-disable-next-line
console.log('C mounted')
},
destroyed() {
// eslint-disable-next-line
console.log('C destroyed')
}
}
</script>
点击A-B-C-A-B-C
未加keep-alive前
加keep-alive后,离开不会destroyed,再次加载不会mounted
mixin
多个组件有相同的逻辑,抽离出来
mixin并不是完美的解决方案,会有一些问题
Vue 3 提出的Composition API旨在解决这些问题
//index.vue
<template>
<div>
<!-- mixin -->
<MixinDemo/>
</div>
</template>
<script>
import MixinDemo from './MixinDemo'
export default {
components: {
MixinDemo
},
data() {
return {
}
}
}
</script>
//MixinDemo.vue
<template>
<div>
<p>{{name}} {{major}} {{city}}</p>
<button @click="showName">显示姓名</button>
</div>
</template>
<script>
import myMixin from './mixin'
export default {
mixins: [myMixin], // 可以添加多个,会自动合并起来
data() {
return {
name: '双越',
major: 'web 前端'
}
},
methods: {
},
mounted() {
// eslint-disable-next-line
console.log('component mounted', this.name)
}
}
</script>
//mixin.js
export default {
data() {
return {
city: '北京'
}
},
methods: {
showName() {
// eslint-disable-next-line
console.log(this.name)
}
},
mounted() {
// eslint-disable-next-line
console.log('mixin mounted', this.name)
}
}
mixin问题
变量来源不明确,不利于阅读
多mixin可能会造成命名冲突
mixin和组件可能出现多对多的关系,复杂度较高
Vuex使用
面试考点并不多(因为熟悉Vue之后,vuex没有难度)
但基本概念,基本使用和API必须掌握
可能会考察state的数据结构设计(后面会讲)
Vuex基本概念
state
getters
action
mutation
异步操作在Actions里进行
用于Vue组件
dispatch
commit
mapState
mapGetters
mapActions
mapMutations
Vuex 是专门为 Vue.js 设计的状态管理库,简单地说就是采用全局单例模式,将组件的共享状态抽离出来管理,使组件树中的每一个位置都可以获取共享的状态(变量)或者触发行为。实现响应式的全局变量
state–状态,在store实例中注册state;在组件中使用store.state访问
getters–类似计算属性,对store中某个属性相同的处理操作抽出出来,做了一个公共的处理,组件中通过store.getters调用
action–异步更改状态,参数有context和payload,context.state获取store变量,context.commit触发mutation,提交mutation去修改state,组件中使用this.store.dispatch调用
mutation–更改store中状态的唯一方法,参数有state和payload,不能包含异步操作,组件中使用this.$store.commit调用
辅助函数:mapState、mapMutations、mapGetters、mapActions,它们的使用我们可以配合ES6的展开运算符将其与局部计算属性或方法混合使用
import {mapState、mapMutations、mapGetters、mapActions} from ‘vuex’
compoted:{
...mapState({})
}
compoted:{
...mapGetters({})
}
methods:{
...mapActions({})
}
methods:{
...mapMutations({})
}
Vue-router使用
面试考点并不多(前提是熟悉Vue)
路由模式(hash、H5 history)
路由配置(动态路由、懒加载)
Vue-router路由模式
hash模式(默认),如http://abc.com/#/user/10
H5 history模式,如http://abc.com/user/20
后者需要server端支持,因此无特殊需求可选择前者
H5 history模式404不会跳转,需要配置404情况
Vue-router路由配置