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 不变则不会重新计算
- computed 可以设置 set 和 get 方法
- watch 深度监听
- watch 默认不是深度监听
- 启动深度监听的话需要设置 deep 为 true
- watch 监听引用类型,拿不到 old value
computed demo
<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>
watch demo
<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) {
console.log('watch name', oldVal, val) // 值类型,可正常拿到 oldVal 和 val
},
info: {
handler(oldVal, val) {
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 会直接销毁和加载 DOM
- v-show 修改 display 属性为显示或隐藏
- v-if 和 v-show 的使用场景?
- 频繁切换显示隐藏场景使用 v-show
- 非频繁切换显示隐藏场景(如切换注册/登录)可使用 v-if
代码演示
<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 不可乱写(如 random 或者 index)
- 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参数,自定义参数
- 在不传自定义参数的情况下,event 对象可以直接在函数形参获取
- 传入自定义参数情况下,需要将 $event 传入
- 此 event 是原生的 event 对象
- 事件修饰符,按键修饰符
-
stop
阻值点击事件继续传播 -
prevent
提交事件不再重载页面 -
v-on:click.stop.prevent
修饰符可以串联使用
-
- 【观察】事件被绑定到哪里?
- Vue 的事件是被挂载到当前元素的
代码演示
<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) {
console.log('event', event, event.__proto__.constructor) // 是原生的 event 对象
console.log(event.target)
console.log(event.currentTarget) // 注意,事件是被注册到当前元素的,和 React 不一样
this.num++
// 1. event 是原生的
// 2. 事件被挂载到当前元素
// 和 DOM 事件一样
},
increment2(val, event) {
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
(去除两边空格)
代码演示
<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: 33,
desc: '自我介绍',
checked: true,
checkedNames: [],
gender: 'male',
selected: '',
selectedList: []
}
}
}
</script>
其他
-
v-once
让某元素标签只渲染一次 -
ref
写在标签上可以通过this.$refs
获取操作 DOM 节点-
ref
还可以写在子组件上,从而获取子组件的引用
-
组件
props 和 $emit
- props 父组件向子组件传递信息
- $emit 子组件向父组件触发一个事件
- 实现父子组件之间通讯
父组件
<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() {
console.log('index created')
},
mounted() {
console.log('index mounted')
},
beforeUpdate() {
console.log('index before update')
},
updated() {
console.log('index updated')
},
}
</script>
Input 组件
<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>
List 组件
<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) {
console.log('on add title', title)
}
},
created() {
console.log('list created')
},
mounted() {
console.log('list mounted')
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler)
},
beforeUpdate() {
console.log('list before update')
},
updated() {
console.log('list updated')
},
beforeDestroy() {
// 及时销毁,否则可能造成内存泄露
event.$off('onAddTitle', this.addTitleHandler)
}
}
</script>
自定义事件
- 创建一个 event.js (如下图)
- 通过在 event 上绑定自定义事件,调用自定义事件实现兄弟组件之间通讯(上图代码所示)
// event.js
import Vue from 'vue'
export default new Vue()
生命周期
单个组件
带有父子组件的生命周期
- 初始化父子组件执行顺序
先触发父元素,然后子元素先进行,父元素收尾。
父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount
=> 子mounted => 父mounted
先父组件创建虚拟dom,再子组件创建虚拟dom 。初始化先保证父组件初始化完再初始化子组件。(外 => 内)
然后子组件渲染,再父组件渲染 。渲染先保证子组件渲染完再渲染父组件 (内 => 外)
最先开始创建的是最外层组件,但是最先创建完的是最内层组件。 => 类似先进后出
(栈)。
- 删除子组件触发生命周期顺序
先触发父元素,然后子元素先进行,父元素收尾。
父beforeUpdate => 子beforeDestroy => 子destroyed => 父updated
- 更新子组件触发生命周期顺序
先触发父元素,然后子元素先进行,父元素收尾。
父beforeUpdate => 子beforeUpdate => 子updated => 父updated
首先是修改父组件的data,所以触发父组件的beforeUpdate,它要把更新后数据再传递给给子组件,子组件更新props,因此再触发子组件的beforeUpdate,紧接着渲染页面触发子组件的updated,只有子组件渲染完页面后,这个时候父组件也就渲染完,触发父组件的updated。
Non-Props
- 父组件向子组件传值,子组件没有通过 props 接收,那么子组件会把该属性变成子组件最外层的 DOM 上的一个属性
- 如果不希望发生此特性那么可以在子组件上加 一条属性:
inheritAttrs: false
- 子组件可以通过
this.$attrs
手动获取 non-props 属性
插槽
-
slot 中使用的作用域的问题
- 父模板里调用的数据属性,使用的都是父模板里的数据
- 子模板里调用的数据属性,使用的都是子模板里的数据
-
具名插槽
- 父组件传递时加上属性
v-slot="..."
可简写为#...
- 子组件
<slot name="..."></slot>
- 父组件传递时加上属性
-
作用域插槽
代码演示:
const app = Vue.createApp({ template: ` <List v-slot="slotProps"> // 可简写为 ES6 解构形式 v-slot="{ item }", 则下面直接用 item 即可 <span>{{ slotProps.item }}</span> // 2. slotProps.item 就是子组件传入的数据 </List> ` }) app.component('List', { data() { return { list: [1, 2, 3] } }, template: ` <div> <slot v-for="item in list" :item=item /> // 1. 将item传给父组件 </div> ` })
分析上面代码:
父组件调子组件的时候传入 slot 进来
子组件通过 :item=item 把数据传给父组件
-
作用域插槽解决了什么问题?
- 当子组件渲染的内容要由父组件决定的情况
- 通过作用域插槽,可以让使父组件调用子组件里的数据