Vue3
模板语法
插值
文本
1
2
3
|
<div id="app">
{{ message }}
</div>
|
v-html
v-bind :用于html内的属性绑定
v-model
Text & Textarea
使用value
property 和 input
事件
v-model
绑定的值对应 内容
checkbox 和 radio
使用radio
property 和change
事件
v-model
绑定的值对应 value
而checkbox
中v-model
对应bool
Tips
如果 v-model
表达式的初始值未能匹配任何选项,<select>
元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change
事件。因此,更推荐像上面这样提供一个值为空的禁用选项
多选绑定数组
修饰符
.lazy
默认情况下v-model
在每次input
事件触发后将输入框的值与数据进行同步。.lazy
修饰符将其转换为change
事件之后进行同步,即失去焦点或者enter
后进行同步
.number
自动将用户输入值转化为数值类型
.trim
自动过滤用户输入的首尾空白字符
v-on @用于添加事件
v-for 用于循环
1
2
3
|
<p v-for="item in items"></p>
<p v-for="(value, name) in items"></p>
<p v-for="(value, name, index) in items"></p>
|
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key
attribute:
1
2
3
|
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
|
v-if v-else v-else-if
动态参数 避免大写
1
|
<a v-on:[eventname]="doSomething"> ... </a>
|
修饰符
1
|
<form v-on:submit.prevent="onSubmit">...</form>
|
Data Property
组件的 data
选项是一个函数。Vue 在创建新组件实例的过程中调用此函数。它应该返回一个对象,然后 Vue 会通过响应性系统将其包裹起来,并以 $data
的形式存储在组件实例中
防抖与节流
防抖与节流都是为了优化性能,避免同一函数被频繁访问
打个比方:电梯15s上一次楼,防抖就规定15s运行一次,节流是没上来一个人重新计时15s,15s电梯运行
防抖
Vue 没有内置支持防抖和节流,但可以使用 Lodash 等库来实现。
如果某个组件仅使用一次,可以在 methods
中直接应用防抖:
1
2
3
4
5
6
7
8
9
10
11
|
<script src="https://unpkg.com/lodash@4.17.20/lodash.min.js"></script>
<script>
Vue.createApp({
methods: {
// 用 Lodash 的防抖函数
click: _.debounce(function() {
// ... 响应点击 ...
}, 500)
}
}).mount('#app')
</script>
|
但是,这种方法对于可复用组件有潜在的问题,因为它们都共享相同的防抖函数。为了使组件实例彼此独立,可以在生命周期钩子的 created
里添加该防抖函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
app.component('save-button', {
created() {
// 用 Lodash 的防抖函数
this.debouncedClick = _.debounce(this.click, 500)
},
unmounted() {
// 移除组件时,取消定时器
this.debouncedClick.cancel()
},
methods: {
click() {
// ... 响应点击 ...
}
},
template: `
<button @click="debouncedClick">
Save
</button>
`
})
|
组件 component
1
2
3
4
5
6
7
8
9
10
|
const app = Vue.createApp({});
app.component('组件名', {
template: '<h1>自定义组件</h1>'
})
app.mount('#app')
使用方法
<组件名></组件名>
|
计算属性与监听器
计算属性
computed
computed 也可以写在 methods 中,但computed是基于它们的响应依赖关系缓存的,也就是说,只要computed中的值不发生改变,就不会重新求值。
Setter
监听器 watch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
watch: {
//对象写法
name: {//监听对象
handel() {//调用函数
},
immediate: true/false, //是否立刻触发一次
deep: true/false //对象中任何属性变化都可以监听,否则无法监听对象内容
}
//函数写法
name(newValue, oldeValue){
}
}
|
事件处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<div @click="greet"></div>
Vue.createApp({
data() {
return {
name: 'Vue.js'
}
},
methods: {
greet(event) {
// `methods` 内部的 `this` 指向当前活动实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM event
if (event) {
alert(event.target.tagName)
}
}
}
}).mount('#event-with-method')
|
内联处理器
使用$event
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<div @click="say('hi', $event)"></div>
Vue.createApp({
data() {
return {
name: 'Vue.js'
}
},
methods: {
say(message, event) {
// `methods` 内部的 `this` 指向当前活动实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM event
if (event) {
alert(event.target.tagName)
}
}
}
}).mount('#event-with-method')
|
多事件处理器
1
2
3
|
<button @click="one($event), two($event)">
Submit
</button>
|
事件修饰符
.stop
.prevent
.capture
.self
.once
.passive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<!-- 阻止单击事件继续传播 -->
<a @click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form @submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div @click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div @click.self="doThat">...</div>
|
按键修饰符
1
|
<input @keyup.enter="submit"/>
|
1
|
<input @keyup.page-down="onPageDown"/>
|
按键别名
Vue 为最常用的键提供了别名:
.enter
.tab
.delete
(捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
系统修饰键
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
Tips: Mac中meta对应command键(⌘),Windows中meta对应徽标键 (⊞),
Tips:必须修饰键的情况下释放其它按键,才能出发keyup,而单单释放修饰键也不会触发事件
.exact
.exact保证了只有确定的修饰符组合才能触发按键, 例如
当且仅当按下ctrl时才触发,同时按其他键不会触发
1
|
<button @click.ctrl.exact="onCtrlClick">A</button>
|
鼠标按钮修饰符
组件基础
基本实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const app = Vue.createApp({})
app.component('button-counter',{
data() {
return {
count: 0
}
},
template:`
<button @click="count++">
You clicked me {{ count }} times.
</button>
`
})
|
传递数据
props & emits
监听子组件事件
使用事件抛出一个值
1
2
3
4
5
6
7
8
9
10
11
12
|
<button @click="$emit('enlargeText', 0.1)">
Enlarge text
</button>
抛出值可用$event接受
<blog-post ... @enlarge-text="onEnlargeText"></blog-post>
methods: {
onEnlargeText(enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
抛出值用参数接受
|
在组件上使用v-model
1
2
3
|
<input v-model="searchText">
等价于
<input :value="searchText" @input="searchText = $event.target.value">
|
在组件上使用时,v-model等价于
1
2
3
4
|
<custom-input
:model-value="searchText"
@update:model-value="searchText = $event"
></custom-input>
|
为了让它正常工作,这个组件内的 <input>
必须:
- 将其
value
attribute 绑定到一个名叫 modelValue
的 prop 上
- 在其
input
事件被触发时,将新的值通过自定义的 update:modelValue
事件抛出
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
|
<div id="app">
<custom-input
v-model="searchText"
></custom-input>
<span> {{ searchText }} </span>
</div>
const app = Vue.createApp({
data() {
return {
searchText: ""
}
}
})
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
app.mount('#app')
|
也可以使用computed来实现
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
|
<div id="app">
<custom-input></custom-input>
</div>
const app = Vue.createApp({
})
app.component('custom-input', {
data() {
return {
value: ""
}
},
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input v-model="value">
<span> {{ value }} </span>
`,
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
})
app.mount('#app')
|
插槽
我们时常在html中添加内容,在模板中我们应该使用插槽来指出内容的位置
1
2
3
4
5
6
7
8
9
10
11
|
<alert-box>
There is something wrong
</alert-box>
app.component('alert-box',{
template: `
<strong>Error!</strong>
<slot></slot>
`
})
|
动态组件
实现不同组件的动态切换,使用is
attribute
1
|
<component :is="getCurrentTag" class="tag"></component>
|
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
|
<body>
<div id="app">
<button
v-for="tag in tags"
:key="tag"
:class="['tag-button', {active: tag == currentTag}]"
@click="currentTag = tag"
>
{{ tag }}
</button>
<component :is="getCurrentTag" class="tag"></component>
</div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
currentTag: "Home",
tags: ["Home", "Posts", "Archive"]
}
},
computed: {
getCurrentTag(){
return this.currentTag.toLowerCase()
}
}
})
app.component('home', {
template: `
<div>Home<div>
`
})
app.component('posts', {
template: `
<div>Posts<div>
`
})
app.component('archive', {
template: `
<div>Archive<div>
`
})
app.mount("#app")
</script>
|
解析DOM模板时的注意事项
Element Placement Restrictions
有些 HTML 元素,诸如 <ul>
、<ol>
、<table>
和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>
、<tr>
和 <option>
,只能出现在其它某些特定的元素内部。
1
2
3
4
5
6
7
8
9
|
不合法
<table>
<blog-post-row></blog-post-row>
</table>
合法
<table>
<tr is="vue:blog-post-row"></tr>
</table>
|
Case Insensitivity
js中的大写字母,要在html中使用 kebab-cased (横线字符分隔)
1
|
postTitle <=> post-title
|
深入组件
组件注册
全局注册
局部注册
注意局部注册的组件在其子组件中不可用
1
2
3
4
5
6
7
8
9
10
11
12
13
|
const ComponentA = {
}
const ComponentB = {
}
const app = Vue.creat({
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
|
1
2
3
4
5
6
7
8
|
const ComponentA = {
}
const ComponentB = {
components: {
'component-a': ComponentA
}
}
|
Props
prop类型
字符串数组
1
|
props: ['value', 'message', 'title']
|
对象
以值+数据类型
给出
1
2
3
4
5
|
props: {
value: Number,
message: String,
title: String
}
|
传递静态或动态的Prop
使用v-bind
来传递动态的Prop,同时v-bind
可以告诉这是一个JavaScript表达式,而不是字符串
1
2
|
<blog :ids="[123,456]"></blog>
使用v-bind vue才能识别这是一个数组
|
传入一个对象的所有property
可以使用不带参数的v-bind
1
2
3
4
5
6
7
8
|
peo: {
name: 'Frank',
age: 17
}
<blog v-bind="peo"></blog>
等价于
<blog :name="peo.name" :age="peo.age"></blog>
|
Prop验证
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
|
app.component('', {
//数据类型检测,null和undefined可以通过任意数据类型检测
propA: 数据类型,
//多个可能的类型
propB: [数据类型,数据类型],
propC: {
required: true, //必填
default: property //默认值
},
propD: {
//对象或数组默认值必须从一个工厂函数获取
type: Object,
default() {
return {
message: 1
}
}
}
//自定义验证函数
propE: {
validator(value){
return ['success','failure'].includes(value)
}
}
})
|
非prop的Attribute
Atrribute继承
1
2
3
4
5
6
7
8
9
10
11
|
<my-component status="actived"></my-component>
app.component('my-component',{
template: `
<div>
<input>
</div>
`
})
渲染后,status会继承在div上,也就是root山
|
禁用Atrribute继承
如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false
。
禁用继承的常见情况是需要atrribute应用在除根节点外的所有其它元素
通过将 inheritAttrs
选项设置为 false
,你可以访问组件的 $attrs
property,该 property 包括组件 props
和 emits
property 中未包含的所有属性 (例如,class
、style
、v-on
监听器等)。
1
2
3
4
5
6
7
8
9
10
11
12
|
<my-component status="actived"></my-component>
app.component('my-component',{
inheritAttrs: false,
template: `
<div>
<input v-bind="$attrs">
</div>
`
})
渲染后,status会在input上
|
多个根节点上的 Attribute 继承
多根节点的组件不具有自动fallthrough行为。若为显式绑定$attrs
,运行时会给予警告
1
2
3
4
5
6
|
app.component('custom-layout', {
template: `
<header>...</header>
<main>...</main>
<footer>...</footer>`
})
|
自定义事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<div v-color="color"></div>
export default {
directives: {
color: {
//第一次被绑定时立即触发
bind(el, binding) {
el.style.color = 'red'
},
//每次dom更新时触发
update(el, binding){
el.style.color = 'red'
}
}
//若bind和update相同,可简写为
color(el, binding){
el.style.color = 'red'
}
}
}
|
定义自定义事件
可以通过emits
选项在组件上定义发出的事件
1
2
3
|
app.component('custom-form',{
emits: ['click', 'submit']
})
|
验证抛出的事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
app.component('custom-form',{
emits: {
//无验证
click: null,
submit: ({mail, password}) => {
if(mail && password){
return true
} else {
return false
}
}
}
})
|
多个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
34
35
|
<div id="app">
<p>First name {{ firstName }}</p>
<p>Last name {{ lastName }}</p>
<user-name
v-model:first-name="firstName"
v-model:last-name="lastName"
></user-name>
</div>
const app = Vue.createApp({
data(){
return {
firstName: 'Frank',
lastName: 'John'
}
}
})
app.component('user-name', {
props: {
firstName: String,
lastName: String
},
/* emits: ['update:firstName','update:lastName'], */
template: `
<input
:value="firstName"
@input="$emit('update:firstName',$event.target.value)"
>
<input
:value="lastName"
@input="$emit('update:lastName',$event.target.value)"
>
`
})
app.mount('#app')
|
处理v-model修饰符
v-model修饰符在modelModifiers
prop中,若为带参数的v-model
绑定,则在args+Modifiers
中,实际上v-model修饰符的作用在于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
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
69
70
71
72
73
74
75
|
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<p>First name {{ firstName }}</p>
<p>Last name {{ lastName }}</p>
<user-name
v-model:first-name.trans="firstName"
v-model:last-name.trans="lastName"
></user-name>
</div>
</body>
<script>
const app = Vue.createApp({
data(){
return {
firstName: 'Frank',
lastName: 'John'
}
}
})
app.component('user-name', {
props: {
firstName: String,
lastName: String,
firstNameModifiers:{
default: () => ({})
},
lastNameModifiers: {
default: () => ({})
}
},
methods: {
changeFirstName(e){
let value = e.target.value
console.log(value)
if(this.firstNameModifiers.trans){
value = value.charAt(0).toUpperCase() + value.slice(1)
}
this.$emit('update:firstName', value)
},
changeLastName(e){
let value = e.target.value
console.log(value)
if(this.lastNameModifiers.trans){
value = value.charAt(0).toUpperCase() + value.slice(1)
}
this.$emit('update:lastName', value)
}
},
/* emits: ['update:firstName','update:lastName'], */
template: `
<input
:value="firstName"
@input="changeFirstName"
>
<input
:value="lastName"
@input="changeLastName"
>
`
})
app.mount('#app')
</script>
</html>
|
插槽
插槽内容
插槽可以包含任何模板代码,包括 HTML、组件
渲染作用域
父级模板里的所有内容都是在父级作用域中编译的;
子模板里的所有内容都是在子作用域中编译的。
备用内容
1
2
3
4
5
6
|
<button type="submit">
<slot>Submit</slot>
</button>
<submit-button></submit-button>
当组件中无内容时显示Submit,有内容时显示内容
|
具名插槽
具名插槽用于需要多插槽的场景
插槽具有name
属性,用<template>
和v-slot
来对应
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
|
//html
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
//组件
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
|
Provide/Inject
适用于跨级传参/接受参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
app.component('todo-list', {
data() {
return {
todos: ['Feed a cat', 'Buy tickets']
}
},
provide() {
return {
todoLength: this.todos.length
}
},
template: `
...
`
})
|
provide/inject
绑定并不是响应式的
1
2
3
4
5
6
7
8
|
app.component('todo-list', {
// ...
provide() {
return {
todoLength: Vue.computed(() => this.todos.length)
}
}
})
|
动态组件&异步组件
keep-alive
使用keep-alive可以让组件失活,即在第一次创建后缓存下来
1
2
3
|
<keep-alive>
<component :is="getCurrentTag" class="tag"></component>
</keep-alive>
|
异步组件(待补)
待补
模板引用
ref
为子组件的直接引用提供了id,其位于$refs
上
Tips:$refs
只会在组件渲染完成之后生效,应避免在模板或计算属性中使用
过渡&动画(待补)
单元素/组件的过渡
过渡class
1
|
<transiton name='name'></transiton> 默认name为v
|
可复用&组合
axios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
axios({
method: '请求类型',
url: 'URL'
params: {
}
}).then((result) => {
//result为请求结果
})
方法返回值为Promise可以用await,await将其转换为一个值
addEventListener('click',async function(){
const { data } = ...
const result = await axios({
method: 'GET',
url: 'URL',
params: {
name: 'qwq'
}
})
})
请求数据储存在result.data中
|
解构赋值(重命名)
1
2
3
4
5
6
7
8
9
10
|
addEventListener('click',async function(){
const { data: res } = await axios({
method: 'GET',
url: 'URL',
params: {
name: 'qwq'
}
})
consolo.log(res.data)
})
|
直接get or post
1
2
3
4
5
6
7
8
9
10
11
12
13
|
document.querySelector('#btn').addEventListener('click',async function(){
const {data: res} = await axios.get('http://www.liulongbin.top:3006/api/getbooks',{
params: {bookname: '西游记'}
})
console.log(res.data)
})
document.querySelector('#btn').addEventListener('click',async function(){
const {data: res} = await axios.get('http://www.liulongbin.top:3006/api/getbooks',{
data: {bookname: '西游记'}
})
console.log(res.data)
})
|
生命周期