vue使用

题目

  1. v-show 和 v-if 的区别
  2. 为何 v-for 中要用 key
  3. 描述 vue 组件生命周期(有父子组件的情况)
  4. vue 组件如何通讯
  5. 描述组件渲染和更新的过程
  6. 双向数据绑定 v-model 的实现原理

基本使用,组件使用 - 常用,必须会

基本使用

指令

  1. 插值、表达式
  2. 指令、动态属性
  3. v-html 有xss风险,会覆盖子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<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

  1. computed 有缓存,data不变则不会重新计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<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>
  1. watch 如何深度监听
  2. watch 监听引用类型,拿不到 oldVal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div>
<input v-model="name"/>
<input v-model="info.city"/>
</div>
</template>

<script>
export default {
data() {
return {
name: 'wsr',
info: {
city: '北京'
}
}
},
watch: {
name(oldVal, val) {
// eslint-disable-next-line
console.log('watch name', oldVal, val) // 值类型,可正常拿到 oldVal 和 val
},
info: {
// 引用类型深度监听要写 handler 和 deep
handler(oldVal, val) {
// eslint-disable-next-line
console.log('watch info', oldVal, val)
// 引用类型,拿不到oldVal 。因为指针相同,此时已经指向了新的 val
},
deep: true // 深度监听
}
}
}
</script>

class 和 style

  1. 使用动态属性
  2. 驼峰写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<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>

条件渲染

  1. v-if v-else 的用法,可使用变量,也可以使用 === 表达式
  2. v-if 和 v-show 的区别

v-if 只渲染条件成立的
v-show 页面上都渲染,不成立的添加了一个 display:none

  1. v-if 和 v-show 的使用场景

切换频繁时使用 v-show,不频繁使用v-if

循环(列表)渲染

  1. 如何遍历对象?也可以用v-for
  2. key 的重要性。key 不能乱写 (random 或 index),要与业务结合
  3. v-for 和 v-if 不能一起使用,因为 v-for 比 v-if 优先执行,一起用的话 v-if 执行多次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<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>

事件

  1. event 参数,自定义参数
  • event 是原生的
  • 事件被挂载到当前元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<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>
  1. 事件修饰符,按键修饰符
  • 事件修饰符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
即内部元素触发的事件先在此处理,然后才交由内部元素进行处理
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
  • 按键修饰符
1
2
3
4
5
6
<!-- 即使 alt 或 shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 ctrl 被按下时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才会触发 -->
<button @click.exact="onClick">A</button>
  1. 观察事件被绑定到哪里

表单

  1. v-model
  2. 常见表单项 textarea checkbox radio select
  3. 修饰符 lazy number trim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<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: 'wsr',
age: 18,
desc: '爱撸猫的程序员',

checked: true,
checkedNames: [],

gender: 'female',

selected: '',
selectedList: []
}
}
}
</script>

组件使用

props 和 $emit (父子组件间通讯)

  1. props 父组件传递一个值到子组件
  2. $emit 子组件调用父组件的一个函数

组件间的通讯 - 自定义事件 (兄弟组件间)

参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!-- 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!-- 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!-- 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>

组件生命周期

单个组件

  1. 挂载阶段

created 只是初始化完成 vue 实例,并没有开始渲染。
mounted dom 已经绘制完成

  1. 更新阶段
  2. 销毁阶段

父子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- index父组件,list 子组件 -->
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

index created
list created
list mounted
index mounted

子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程
父beforeUpdate->父updated

index before update
list before update
list updated
index updated

销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

高级特性 - 不常用,体现深度

自定义 v-model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<div>
<p>vue 高级特性</p>
<hr>

<!-- 自定义 v-model -->
<p>{{name}}</p>
<CustomVModel v-model="name"/>

</div>
</template>

<script>
import CustomVModel from './CustomVModel'

export default {
components: {
CustomVModel
},
data() {
return {
name: '张三',
website: {
url: 'http://imooc.com/',
title: 'imooc',
subTitle: '程序员的梦工厂'
},
showFormDemo: false
}
}
}

</script>

CustomVModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<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>

参考

$nextTick refs

  1. vue 是异步渲染
  2. data 改变之后,dom不会立刻渲染
  3. $nextTick 会在 dom 渲染之后被处罚,以获取最新 dom 节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<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()}`)

// 这时候打印的是之前的长度
const ulElem = this.$refs.ul1
console.log( ulElem.childNodes.length )

// 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
// 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
this.$nextTick(() => {
// 获取 DOM 元素
const ulElem = this.$refs.ul1
// eslint-disable-next-line
console.log( ulElem.childNodes.length )
})
}
}
}
</script>

slot

  1. 基本使用 父组件往子组件中插入一个内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- index.vue -->
<template>
<SlotDemo :url="website.url">
{{website.title}}
</SlotDemo>
</template>

<!-- slotdemo.vue -->
<template>
<a :href="url">
<slot>
默认内容,即父组件没设置内容时,这里显示
</slot>
</a>
</template>

<script>
export default {
props: ['url'],
data() {
return {}
}
}
</script>
  1. 作用域插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

<!-- index.vue -->
<template>
<SlotDemo :url="website.url">
{{website.title}}
</SlotDemo>
<ScopedSlotDemo :url="website.url">
<template v-slot="slotProps">
{{slotProps.slotData.title}}
</template>
</ScopedSlotDemo>
</template>

<!-- 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>
  1. 具名插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

<NamedSlot>
<template v-slot:header>
<h1>将插入到header中</h1>
</template>
<p>将插入到main中</p>
<template v-slot:footer>
<h1>将插入到footer中</h1>
</template>
</NamedSlot>
</template>

动态、异步组件

动态组件

  1. :is=”component-name”用法
  2. 需要根据数据,动态渲染的场景。即组件类型不确定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<component :is="NextTickName"/>

<div v-for="item in newsData" :key="item.id">
<component :is="item.type"/>
</div>

<script>
import NextTick from './NextTick'
import text from './text'
import image from './image'
import video from './video'
export default {
components: {
NextTick,
text,
image,
video
},
data() {
return {
NextTickName: "NextTick",
text: "text",
image: "image",
video: "video"
newsData: [
{
type: 'text',
id: 1
},
{
type: 'image',
id: 2
},
{
type: 'video',
id: 3
}
]
}
}
}
</script>

异步组件

  1. import() 函数
  2. 按需加载,异步加载大组件
  3. vue 常见性能优化之一
1
2
3
4
5
6
7
8
9
10
11
<template>
<FormDemo v-if="showFormDemo"/>
<button @click="showFormDemo = true">show form demo</button>
</template>
<script>
export default {
components: {
FormDemo: () => import('../BaseUse/FormDemo')
}
}
</script>

keep-alive

  1. 缓存组件
  2. 频繁切换,不需要重新渲染
  3. vue 常见性能优化之一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<div>
<button @click="changeState('A')">A</button>
<button @click="changeState('B')">B</button>
<button @click="changeState('C')">C</button>
<!-- 使用 keep-alive 后组件不会 destroyed -->
<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>

mixin

  1. 多个组件有相同的逻辑,抽离出来
  2. mixin 并不是完美的解决方案,会有一些问题
    • 变量来源不明确,不利于阅读
    • 多mixin可能造成命名冲突
    • mixin 和组件可能出现多对多的关系,复杂度较高
  3. vue3 提出的 composition api 旨在解决这些问题

demo.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
}
}

vuex 和 vue-router 使用

vuex

  1. 面试考点并不多(熟悉vue之后,vuex没有难度)
  2. 基本概念,基本使用和api需要掌握
  3. 可能会考察 state 的数据结构设计
  • state
  • getters
  • action 只能在这做异步操作,整合多个mutation
  • mutation 原子操作
  • dispatch
  • commit
  • mapState
  • mapGetters
  • mapActions
  • mapMutations

vue-router

  1. 面试考点并不多(前提是熟悉vue)
  2. 路由模式(hash、 h5 history)

hash 模式
http://abc.com/#/user/login

h5 history模式
http://abc.com/user/login

后者需要 server 端支持,无特殊要求选择第一种

  1. 路由配置(动态路由、懒加载)

    • 动态路由
1
2
3
4
5
6
7
8
9
10
11
const user = {
// 获取动态参数 10 20
template: '<div>user {{ $router.params.id }}</div>'
}

const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头,命中 '/user/10' '/user/20' 等格式的路由
{ path: '/user/:id', component: user }
]
})
  • 懒加载,提高速度,需要加载时再加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default new VueRouter({
routes: [
{
path: '/',
component: ()=> import(
'./../components/Navigator'
)
},
{
path: '/feedback',
component: ()=> import(
'./../components/FeedBack'
)
},
]
})

一些工具

svg字体图标制作

iconmoon
iconmoon 使用说明
jsonview 谷歌json查看工具
css reset

api接口mock数据

vue.config.js添加devServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const appData = require('./data.json')
const seller = appData.seller
const goods = appData.goods

module.exports = {
devServer:{
before(app){
app.get('./api/seller', function(req,res){
res.json({
errno:0,
data:seller
})
})
app.get('./api/goods', function(req,res){
res.json({
errno:0,
data:goods
})
})
}
}
}