Vue3 基础语法
1. Vue 中应用和组件的基础概念
createApp
createApp 表示创建一个 Vue 应用,存储在 app 变量中。
传入的参数表示,这个应用最外层的组件,应该如何展示。
const app = Vue.createApp({
data() {
return {
message: "hello world"
}
},
template: "<div>{{ message }}</div>"
})
const vm = app.mount("#root");
vm 代表的就是 vue 应用的根组件。
MVVM 设计模式
M:model 数据模型,数据和业务逻辑在这层定义,如 data
V:view 视图,负责数据的展示,如 template
VM: 视图数据连接层,负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作。
VM 是 vue 组件帮我们做的。
获取 vm 里的data
使用 $data 属性即可
vm.$data
2. 生命周期函数
生命周期好比人生命中几个重要阶段,例如出生,上学,结婚,生孩子,死亡等,到了这些阶段都会做些事情,例如,出生后办出生证明,上学第一次带红领巾,死前立遗嘱等等。
Vue 组件同样有几个重要的生命周期,这些生命周期有对应的生命周期钩子(函数),即组件到达某个生命周期就会自动执行相应的生命周期函数的内容。
-
beforeCreate (创建前):在实例初始化之后、进行数据侦听和事件/侦听器的配置之前同步调用。此时无法访问到 data、computed、watch、methods 上的方法和数据
-
created (创建后):在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且
$el
property 目前尚不可用。 -
进入判断,判断实例里边是否有 template 选项,如果存在就把模板变成一个 render 函数后,和数据结合并执行。如果不存在,就把 el 的 innerHtml 作为 template。即,template 的模板内容可以直接写在 root 节点下面。
-
beforeMount (挂载前):模板已经变成函数后被首次调用,已经生成了 DOM 树,但是此时还未挂载在 root 节点上,页面显示的仍是 vue 占位符。
-
Mounted (挂载后):在实例挂载完成后被调用,这时候传递给
app.mount
的元素已经被新创建的vm.$el
替换了。如果根实例被挂载到了一个文档内的元素上,当mounted
被调用时,vm.$el
也会在文档内。mounted
不会保证所有的子组件也都被挂载完成。如果希望等待整个视图都渲染完毕,可以在mounted
内部使用 vm.$nextTick:mounted() { this.$nextTick(function () { // 仅在整个视图都被渲染之后才会运行的代码 }) }
-
beforeUpdate (更新前):当 data 中的数据发生变化时会自动执行的函数,此时真实 DOM 还未被渲染。
-
updated (更新后):由于数据更改导致虚拟 DOM 重新渲染和打补丁之后自动调用。
-
beforeUnmount (销毁前):在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
-
unmounted (销毁后):卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
3. 常用模板语法
3.1 常规用法
插值表达式,用两个大括号括起来。
const app = Vue.createApp({
data() {
return {
message: "hello world"
}
},
template: "<div>{{ message }}</div>"
})
app.mount("#root");
3.2 v-html
插值表达式会将标签转义,如果不想要标签被转义,就使用 v-html:
const app = Vue.createApp({
data() {
return {
message: "<strong>hello world</strong>"
}
},
template: "<div v-html='message'></div>"
})
app.mount("#root");
3.3 v-bind
如果要给标签属性赋值,就需要用 v-bind,而不是插值语法。
<script>
const app = Vue.createApp({
data() {
return {
message: "hello world"
}
},
template: "<div v-bind:title='message'>{{ message }}</div>"
})
app.mount("#root");
</script>
v-bind 内容,包括插值语法,里面可以放 js 表达式。简写形式,标签名前面加冒号即可。
3.4 v-once
v-once 表示数据只渲染一次
<script>
const app = Vue.createApp({
data() {
return {
message: "hello world"
}
},
template: "<div v-once>{{ message }}</div>"
})
app.mount("#root");
</script>
上面的代码的意思是,渲染的时候使用了 data 里的 message,后边数据更新,v-once 包裹的内容也不会更新。
3.5 v-if 和 v-show
这两个都是控制内容的显示与否,但是又有所差距
<div id="conditional-rendering">
<span v-if="seen">现在你看到我了</span>
</div>
- v-show 本质就是标签 display 设置为 none,控制隐藏
- v-if 是动态的向 DOM 树内添加或者移除 DOM 元素
因此,如果包裹的内容显示和不显示的切换频繁,用 v-show 更合理,因为避免了不停的销毁和创建。不频繁切换的话就用 v-if,减少渲染消耗。
v-if、v-else-if 和 v-else:这些如果要使用的话,就需要要紧紧贴在一起,否则不生效。
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
3.6 标签名赋值
如果标签名不确定,那么用中括号包裹,动态获取即可。
<script>
const app = Vue.createApp({
data() {
return {
message: "hello world",
name: "title",
event: "click",
}
},
methods: {
handleClick() {
console.log("click")
}
},
template: `
<div @[event]="handleClick" :[name]="message">
{{ message }}
</div>
`
})
app.mount("#root");
</script>
3.7 阻止默认行为
如,阻止表单的默认提交行为,使他不跳转。使用 e.preventDefault()
<script>
const app = Vue.createApp({
data() {
return {
message: "hello world",
name: "title",
event: "click",
}
},
methods: {
handleClick(e) {
e.preventDefault();
}
},
template: `
<form action="https://www.baidu.com">
<button type="submit" @click="handleClick">提交</button>
</form>
`
})
app.mount("#root");
</script>
更简单的写法,使用修饰符 .prevent 简化代码:
<script>
const app = Vue.createApp({
data() {
return {
message: "hello world",
name: "title",
event: "click",
}
},
methods: {
handleClick() {
console.log("click")
}
},
template: `
<form action="https://www.baidu.com">
<button type="submit" @click.prevent="handleClick">提交</button>
</form>
`
})
const vm = app.mount("#root");
</script>
4. 数据、方法、计算属性和侦听器
4.1 数据 data
数据,也就是 data
如果要更改 data 里的数据,例如 message,修改 vm.$data.message
属性即可。如果是根数据,直接修改 vm.message
即可。
4.2 方法 methods
比如上边的 handleClick,就是其中一个方法。方法的 this 指向 vue 实例。
注意:不要使用箭头函数来定义函数!箭头函数会使 this 指向丢失!
4.3 计算属性 computed
计算属性 computed,用来放置计算后的数据。
<script>
const app = Vue.createApp({
data() {
return {
count: 2,
price: 5,
}
},
computed: {
total() {
return this.count * this.price
}
},
template: `
<div>
{{ total }}
</div>
`
})
app.mount("#root");
</script>
如上面的代码,如果 count 或者 price 发生改变,total 也会跟着改变。计算属性使模板更轻量的同时,可读性更好。
就会有人问了,为什么不用 methods 呢?computed 的优势在于,只有相关的依赖的内容发生改变的时候才会重新求值,即 computed 使用了缓存。methods 每次页面调用都会去执行。
4.4 侦听器 watch
虽然大部分的情况下,computed 更合适,但是有些特殊情况,比如异步操作或开销较大的操作,使用侦听器是更合适的。
watch 用于监听 data 里的某个数据发生改变时候需要做的操作。
watch 可以接收两个参数,当前的数值和修改前的数值。
比如我监听 price 里的数据改变,使用如下代码:
watch: {
price(current, prev) {
console.log(prev, current);
}
},
更改 price 数据后,输出如下:
和 computed 相比,watch 是一个更为底层的方式,能够使用 computed 的尽量使用 computed,因为写起来更简洁。
5. 样式绑定语法
5.1 class 常规使用
简单的写法就是用 :class
动态赋予类名。
<script>
const app = Vue.createApp({
data() {
return {
classString: "red"
}
},
computed: {
total() {
return this.count * this.price
}
},
template: `
<div :class="classString">hello world</div>
`
})
const vm = app.mount("#root");
</script>
注:red 和 green 都写了相应的 css 颜色样式
更复杂的情况,比如要用到多个类,就得使用对象格式。
<script>
const app = Vue.createApp({
data() {
return {
classObj: {
red: true,
bold: true,
}
}
},
computed: {
total() {
return this.count * this.price
}
},
template: `
<div :class="classObj">hello world</div>
`
})
const vm = app.mount("#root");
</script>
里边的两个属性,如果值为 true,表示这个类的样式附加在当前的 DOM 上,值为 false 就不附加样式。这种的优势是在于可以控制多个样式。
当然,上面看着有两个 true,因为不控制类显示与否,因此显得有点鸡肋。单纯的想要添加多个类的样式,用数组即可。
<script>
const app = Vue.createApp({
data() {
return {
classArray: ["red", "bold"],
}
},
computed: {
total() {
return this.count * this.price
}
},
template: `
<div :class="classArray">hello world</div>
`
})
const vm = app.mount("#root");
</script>
后面重量级的来了,数组组合对象使用:
<script>
const app = Vue.createApp({
data() {
return {
classArray: ["red", "bold", {big: true}],
}
},
computed: {
total() {
return this.count * this.price
}
},
template: `
<div :class="classArray">hello world</div>
`
})
const vm = app.mount("#root");
</script>
5.2 class 包含子组件的情况
如果有子组件,且子组件的 template 只有一个根节点,可以在组件上写类名或者直接在组件上写或者在子组件根节点上写。
直接在组件上写类名:
<script>
const app = Vue.createApp({
template: `
<Children class="green" />
`
})
app.component("Children", {
template: `<div>Children</div>`
})
const vm = app.mount("#root");
</script>
在子组件根节点上写类名:
<script>
const app = Vue.createApp({
template: `
<Children/>
`
})
app.component("Children", {
template: `<div class="green">Children</div>`
})
const vm = app.mount("#root");
</script>
如果有两个及以上根节点,且在组件上写类名(在子组件上写,该咋写咋写,没歧义),vue 不清楚你这个类要赋予子组件的哪一个根节点,则需要使用下面的方法:
<script>
const app = Vue.createApp({
template: `
<Children class="green" />
`
})
app.component("Children", {
template: `
<div :class="$attrs.class">one</div>
<div>two</div>
`
})
const vm = app.mount("#root");
</script>
用 $attrs 获取组件的 property 然后按需赋予即可。
在子组件上写类名:
<script>
const app = Vue.createApp({
template: `
<Children/>
`
})
app.component("Children", {
template: `
<div class="green">one</div>
<div>two</div>
`
})
const vm = app.mount("#root");
</script>
5.3 style 内联样式
有两种方法,一种是常规的内联样式写法,另一种是用对象形式来写,这种方法相比于常规写法更好:
<script>
const app = Vue.createApp({
data() {
return {
styleObject: {
color: "orange",
background: "yellow",
},
}
},
template: `
<div :style="styleObject">hello world</div>
`
})
const vm = app.mount("#root");
</script>
6. 列表渲染
循环可以可以批量渲染出列表或者对象里的值。但是有一个重点在于,添加或删除的时候,只有要添加和删除的部分需要改动,其他的部分并不需要进行改动,因此用 key 值这个唯一值来标定每个循环生成的内容,就可以减少不必要的开销(如果没写 key 值,列表会全部给你重新渲染一遍)。
6.1 数组循环渲染
第一个参数为 item,第二个参数是对应的下标 (可选)
<script>
const app = Vue.createApp({
data() {
return {
list: ["sjh", "student", "vue"]
}
},
template: `
<div>
<div v-for="(item, index) in list">
{{ item }} -- {{ index }}
</div>
</div>
`
})
const vm = app.mount("#root");
</script>
还可以直接循环数字,下面的代码是,从 1 循环到 10
<div v-for="item in 10" :key="item">
{{ item }}
</div>
6.2 对象循环渲染
第一个参数为 value,第二个参数为 key (可选),第三个参数为 index (可选)
<script>
const app = Vue.createApp({
data() {
return {
userObj: {
name: "sjh",
learning: "vue",
gender: "male",
}
}
},
template: `
<div>
<div v-for="(value, key, index) in userObj" :key="key">
{{ index }} -- {{ key }} -- {{ value }}
</div>
</div>
`
})
const vm = app.mount("#root");
</script>
6.3 数组变更方法和替换方法
变更方法
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。
变更方法就是非函数式的方法,即直接修改数组本身内容。
这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换方法
替换方法就是函数式编程返回新数组后,替换原来的数组。
如 filter
, concat
, map
等。
直接修改
通过 index 修改,或者直接一个新数组覆盖原来的数组,都是可行的,没有像 React 限制比较多。同样的,vue 可以直接往对象里添加内容。
注意:
vue2 版本直接修改对象内容或者通过 index 来修改数组内容,vue 是监听不到的。
6.4 v-if 和 v-for 优先级问题
不要在同一个节点上使用 v-if
和 v-for
!当它们处于同一节点,v-if
的优先级比 v-for
更高,这意味着 v-if
将没有权限访问 v-for
里的变量。
<!-- 这将抛出一个错误,因为“todo” property 没有在实例上定义 -->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
可以把 v-for
移动到 <template>
标签中来修正。template 相当于占位符,在真正渲染的时候不做任何渲染:
<template v-for="todo in todos" :key="todo.name">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
7. 事件绑定
7.1 传参规则与 event
刚才的内容已经展示了基本的事件绑定方法,下面展示一下示例,便可以很清楚的展示了其他细节。
<script>
const app = Vue.createApp({
data() {
return {
counter: 0
}
},
methods: {
handleClick(num, event) {
this.counter += num
console.log(event)
}
},
template: `
<div>
{{ counter }}
<button @click="handleClick(2, $event)">点击</button>
</div>
`
})
const vm = app.mount("#root");
</script>
事件可以附带参数(和正常的 js 方法一样),如果又要参数又要 event,参数用 $event
填充即可。
7.2 一个事件同时绑定多个方法
这写法比较奇葩,但还是得记一下
<div>
{{ counter }}
<button @click="handleClick1(), handleClick2()">点击</button>
</div>
7.3 事件修饰符
事件修饰符可以很方便地实现一些常见的需求,例如阻止冒泡行为,阻止默认行为等。事件修饰符可以串联使用。
stop 阻止事件冒泡行为
事件有冒泡行为,因此需要进行捕获来避免冒到不必要的地方去。
<div @click="handleDivClick">
{{ counter }}
<button @click.stop="handleBtnClick">点击</button>
</div>
self 保证自身触发的事件
只有点击自身才触发事件,点击子元素不行。上面想要的效果,用 self 也可以实现,但差别在于,这种写法没有阻止冒泡。
<div @click.self="handleDivClick">
{{ counter }}
<button @click="handleBtnClick">点击</button>
</div>
其他修饰符
-
.prevent
阻止默认行为 -
.capture
捕获事件,内部触发的事件优先处理 -
.once
只执行一次 -
.passive
提升性能使用<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发, --> <!-- 而不会等待 `onScroll` 完成, --> <!-- 以防止其中包含 `event.preventDefault()` 的情况 --> <div @scroll.passive="onScroll">...</div>
7.4 按键修饰符
@keyup
和 @keydown
当按键松开 / 按下时触发。
但是更常用的是,按下特定的按钮触发事件,因此得使用按键修饰符。
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input @keyup.enter="submit" />
常用按键修饰符
.enter
.tab
.delete
(捕获“删除”和“退格”键).esc
.space
.up
.down
.left
.right
组合键修饰符
.ctrl
.alt
.shift
.meta
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
鼠标按钮修饰符
.left
.right
.middle
.exact
修饰符
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
精确地按 ctrl 和鼠标点击才会触发,按 ctrl 附加上其他键不会触发。
8. 表单双向绑定
可以用 v-model 指令在表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定。表单内容改变 data 里的值的同时,data 里的值改变也会影响表单内容。
v-model 是语法糖,底层是通过事件绑定和表单的 property 赋值实现的。
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
value
property 和input
事件; - checkbox 和 radio 使用
checked
property 和change
事件; - select 字段将
value
作为 prop 并将change
作为事件。
8.1 常规用法
文本 text
<input v-model="message" placeholder="edit me" />
<p>Message is: {{ message }}</p>
多行文本 textarea
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br />
<textarea v-model="message" placeholder="add multiple lines"></textarea>
textarea 不支持插值语法,用 v-model 代替。
复选框 checkbox
当可以多选的时候,v-model 需要绑定一个数组
<div id="v-model-multiple-checkboxes">
<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>
<br />
<span>Checked names: {{ checkedNames }}</span>
</div>
Vue.createApp({
data() {
return {
checkedNames: []
}
}
}).mount('#v-model-multiple-checkboxes')
选择框 select
单选时:
<div id="v-model-select" class="demo">
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
第一行选项不可选,且内容为空是推荐写法,v-model 没有值的时候,初始值指向第一项。
Vue.createApp({
data() {
return {
selected: ''
}
}
}).mount('#v-model-select')
若选择框多选时,绑定到一个数组上即可。
<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
8.2 值绑定
复选框 checkbox
选中的值和未选中的值想要自定义的时候,使用 true-value 和 false-value 属性值
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
单选框 radio
<input type="radio" v-model="pick" v-bind:value="a" />
当选中的时候,值为 a
8.3 修饰符
.lazy
.lazy v-model 默认 input 触发事件,用 lazy 修饰符则改为失去焦点的时候 blur 触发。
.number
将输入的值转化为数值类型
.trim
输入的值截去首尾空白字符