学 vue3 通过官方文档更详细,不过阅读本博客,可以更容易理解,且帮你速成!
- 官方文档(记得将API风格偏好切换为
组合式
否则你学的是vue2)
https://cn.vuejs.org/guide/introduction.html
学习前的准备
- 创建一个 vue3 项目,详见链接
https://blog.csdn.net/weixin_41192489/article/details/128231728
开启 vue3 的实验性特征
在项目的 vite.config.ts 中添加 reactivityTransform: true,
添加 ts 对 vue3 实验性特征的支持
在项目的 tsconfig.json 中添加 "types": ["vue/ref-macros"]
改用 script setup
与 vue2 不同,vue3 的 js 代码写在 <script setup lang="ts">
中,如
<script setup lang="ts"> let count = $ref(0); function increment() { count++; } </script>
当然,也可以延用 vue2 的选项式风格,则代码如下:
<script> export default { // `setup` 是一个专门用于组合式 API 的特殊钩子函数 setup() { let count = $ref(0); function increment() { count++; } // 暴露 count 和 increment 到模板 return { count, increment } } } </script>
很显然, <script setup lang="ts">
的组合式风格更简洁易用,下文都将使用组合式风格。
以下代码都在项目的 src\views\AboutView.vue 中测试,启动项目后,浏览器访问
http://127.0.0.1:5173/about 查看效果。
先将 AboutView.vue 初始化为
<template> <div></div> </template> <script setup lang="ts"> </script> <style scoped> </style>
定义响应式变量 $ref()
vue2中写在 data(){} 函数中的变量,vue3中写法如下
// 定义响应式变量 count,初始值为 0 let count = $ref(0);
友情提示:此处的 $ref() 用法目前处于实验性阶段,未来可能无法使用。(不过为了编码方便,下文仍会使用这个!)
目前官方的定义响应式变量的方法为 ref() 和 reactive()
ref()
ref() 能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。
- 参数可以是任意数据类型,会返回 ref 对象
(之所以这样设计,是因为对象才是引用类型,更方便实现响应式)
import { ref } from 'vue' // 定义响应式变量 count ,初始值为 0 const count = ref(0) // 此时 count 是一个 ref 对象,实际内容为 { value: 0 } ,所以要想取到 0 ,需使用 count.value
使用范例
解析:当 ref 函数创建的变量名和ref属性值相同时,该变量就能获取到该节点。(ref属性是vue特有的,模板引擎解析代码时看到ref属性,就会把这个标签的vdom缓存在定义的el变量中,以后就能通过el访问该vdom了)
toRef()
基于响应式对象上的一个属性,创建对应的 ref
- 即使源属性当前不存在,toRef() 也会返回一个可用的 ref。
- 常用于处理可选 props
const state = reactive({ foo: 1, bar: 2 }) const fooRef = toRef(state, 'foo') // 更改该 ref 会更新源属性 fooRef.value++ console.log(state.foo) // 2 // 更改源属性也会更新该 ref state.foo++ console.log(fooRef.value) // 3
<script setup> import { toRef } from 'vue' const props = defineProps(/* ... */) // 将 `props.foo` 转换为 ref,然后传入一个组合式函数 useSomeFeature(toRef(props, 'foo')) </script>
toRefs()
将响应式对象的属性,都变成响应式的
function useFeatureX() { const state = reactive({ foo: 1, bar: 2 }) // 在返回时都转为 ref return toRefs(state) } // 可以解构而不会失去响应性 const { foo, bar } = useFeatureX()
reactive()
reactive() 会创造一个响应式代理(Proxy),传递引用时会丢失响应。
- 参数必须是对象类型(对象、数组和 Map、Set 这样的集合类型)
import { reactive } from 'vue' const state = reactive({ count: 0 }) function increment() { state.count++ }
计算属性 computed
vue2中写在 computed 中的计算属性,vue3中写法如下
import { computed } from "vue"; let firstName = $ref("朝"); let lastName = $ref("阳"); // 当 firstName 或 lastName 变化时,会触发重新计算 fullName let fullName = computed(() => { return firstName + lastName; });
- 计算属性中不能直接使用 reverse() 和 sort() ,因为这两个方法将变更原始数组,正确的用法是在调用这些方法之前创建一个原数组的副本
[...numbers]
return [...numbers].reverse()
实用技巧:动态绑定样式
<template> <div> <div :class="classObject">你好</div> <button @click="bolder">加粗</button> <button @click="markError">标红</button> </div> </template> <script setup lang="ts"> import { computed } from "vue"; let isBold = $ref(false); let error = $ref(false); let classObject = computed(() => ({ bold: isBold, "text-danger": error, })); function bolder() { isBold = true; } function markError() { error = true; } </script> <style scoped> .text-danger { color: red; } .bold { font-weight: bold; } </style>
定义方法 function
vue2中写在 methods 中的方法,vue3中写法如下
// 定义方法 increment,每执行一次,count 会自增 1 function increment() { count++; }
内联事件
逻辑极其简单的方法也可以直接写在元素上
<button @click="count++">增加1</button>
在方法后加 () 也会被视为内联事件
<button @click="increment()">增加1</button>
此时可以给方法添加参数
<button @click="increment('参数1',‘参数2’)">按指定参数增加</button>
内联事件访问原生 DOM 事件
需使用 $event
<button @click="submit('参数1', $event)">提交</button>
或者使用内联箭头函数
<button @click="(event) => submit('参数1', event)">提交</button>
方法事件
逻辑比较复杂时,则需给事件绑定方法
<button @click="show">展示</button>
此时方法的默认参数为原生 DOM 事件 event
// event 为原生 DOM 事件 function show(event: any) { // 通过 event 可以获取事件相关的各种信息 console.log(event); if (event) { // 比如触发事件的标签名称 console.log(event.target.tagName); } }
事件修饰符
- .stop 禁止事件冒泡
<a @click.stop="doThis"></a>
效果同 event.stopPropagation()
- .prevent 禁止事件默认行为
效果同event.preventDefault()
使用范例:提交表单时不再重新加载页面
<form @submit.prevent="onSubmit"></form>
- .capture 事件采用捕获模式
指向内部元素的事件,在被内部元素处理前,先被外部处理
<div @click.capture="doThis">...</div>
- .passive 用于改善移动端设备的滚屏性能
一般用于触摸事件的监听器
范例:滚动事件的默认行为 (scrolling) 将立即发生而非等待 onScroll
完成,以防其中包含 event.preventDefault()
<div @scroll.passive="onScroll">...</div>
.passive 和 .prevent 不能同时使用,因为 .passive 已经向浏览器表明了你不想阻止事件的默认行为。如果同时使用,则 .prevent 会被忽略,并且浏览器会抛出警告。
还可以只使用修饰符,不绑定方法
<form @submit.prevent></form>
链式调用事件修饰符
<a @click.stop.prevent="show"></a>
但要注意调用顺序:
@click.prevent.self 阻止元素及其子元素的所有点击事件的默认行为
@click.self.prevent 只会阻止对元素本身的点击事件的默认行为。
常规按键修饰符
.按键名
即可实现指定按键触发事件
- 按键名的格式为 kebab-case 形式,如 PageDown 键需使用
.page-down
<!-- 按回车键时调用 submit --> <input @keyup.enter="submit" /> <!-- 按 a 键时调用 submit --> <input @keyup.a="submit" /> <!-- 按 PageDown 键时调用 submit --> <input @keyup.page-down="submit" />
常用的按键有以下别名
- .enter
- .tab
- .delete (捕获“Delete”和“Backspace”两个按键)
- .esc
- .space
- .up
- .down
- .left
- .right
系统按键修饰符
- .ctrl
- .alt
- .shift
- .meta
在 Mac 键盘上,meta 是 Command 键 (⌘)。
在 Windows 键盘上,meta 键是 Windows 键 (⊞)。
系统按键修饰符与 keyup 事件一起使用时,该按键必须在事件发出时处于按下状态。如 keyup.ctrl
只会在你按住 ctrl 但松开另一个键时被触发,单独松开 ctrl 键将不会触发。
组合键的实现
<!-- Alt + Enter --> <input @keyup.alt.enter="clear" /> <!-- Win + Enter --> <input @keyup.meta.enter="clear" /> <!-- Ctrl + 点击 --> <div @click.ctrl="doSomething">Do something</div> <!-- Ctrl + shift + a --> <input @keyup.ctrl.shift.a="submit" />
.exact 修饰符
控制触发一个事件所需的确定组合
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 --> <button @click.ctrl="onClick">A</button> <!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 --> <button @click.ctrl.exact="onCtrlClick">A</button> <!-- 仅当没有按下任何系统按键时触发 --> <button @click.exact="onClick">A</button>
鼠标按键修饰符
- .left
- .right
- .middle
<!-- 在该div上点击鼠标左键时触发 --> <div @mousedown.left="submit">
条件渲染 v-if v-show
多个元素需要使用 v-if 控制渲染时,可以添加 <template>
包裹
<template v-if="showDetail"> <h1>标题</h1> <p>段落一</p> <p>段落二</p> </template>
v-show 无法在 <template>
上使用
列表渲染 v-for
- 既可以用 in ,也可以用 of
<div v-for="item of items"></div>
- 可使用解构
const items = $ref([{ message: "1" }, { message: "2" }]);
<li v-for="({ message }, index) in items">{{ message }} {{ index }}</li>
- 渲染对象
<li v-for="(value, key, index) in myObject"> {{ index }}. {{ key }}: {{ value }} </li>
- 渲染整数
效果为 1、2、3、…… n
<!-- n 的初值是从 1 开始而非 0 --> <span v-for="n in 10">{{ n }}</span>
- 多个元素需要使用 v-for 渲染时,可以添加
<template>
包裹
<ul> <template v-for="item in items"> <li>{{ item.msg }}</li> <li class="divider" role="presentation"></li> </template> </ul>
此时 key 也要写在 <template>
上
<template v-for="todo in todos" :key="todo.name"> <li>{{ todo.name }}</li> </template>
- key 需为字符串或 number 类型,不要用对象作为 key
v-if 和 v-for 同时存在于一个元素上时
v-if 会首先被执行,但不推荐这种写法
<ul> <li v-for="user in users" v-if="user.isActive" :key="user.id" > {{ user.name }} </li> </ul>
应改用 <template>
包裹
<ul> <template v-for="user in users" :key="user.id"> <li v-if="user.isActive"> {{ user.name }} </li> </template> </ul>
双向绑定 v-model
v-model 绑定的值通常是静态的字符串 (复选框中是布尔值),使用 v-bind 可以将选项值绑定为非字符串的数据类型。
<select v-model="selected"> <!-- 内联对象字面量 --> <option :value="{ number: 123 }">123</option> </select>
单选按钮 radio
<div>Picked: {{ picked }}</div> <input type="radio" id="one" value="One" v-model="picked" /> <label for="one">One</label> <input type="radio" id="two" value="Two" v-model="picked" /> <label for="two">Two</label>
复选框 checkbox
单个复选框
<input type="checkbox" id="checkbox" v-model="checked" /> <label for="checkbox">{{ checked }}</label>
默认值为 true 和 false
通过以下方法可以自定义值
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
true-value 和 false-value 是 Vue 特有的属性,仅支持和 v-model 配套使用
多个复选框——值为数组
const checkedNames = $ref([])
<div>Checked names: {{ checkedNames }}</div> <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>
自定义复选框
下拉单选 select
<div>Selected: {{ selected }}</div> <select v-model="selected"> <option disabled value="">Please select one</option> <option>A</option> <option>B</option> <option>C</option> </select>
v-model 的初始值不匹配任何一个选择项时,会渲染成一个“未选择”的状态。在 iOS 上,这将导致用户无法选择第一项,因为 iOS 在这种情况下不会触发 change 事件。因此,建议提供一个空值的禁用选项,如上例所示。
.lazy 修饰符
默认情况下,v-model 会在每次 input 事件后更新数据
.lazy 修饰符可以改为在每次 change 事件后更新数据
<!-- 在 "change" 事件后同步更新而不是 "input" --> <input v-model.lazy="msg" />
.number 修饰符
让用户输入自动转换为数字
<input v-model.number="age" />
若该值无法被 parseFloat() 处理,则返回原始值。
number 修饰符会在输入框有 type=“number” 时自动启用。
.trim 修饰符
自动去除用户输入内容中两端的空格
<input v-model.trim="msg" />
生命周期–钩子函数
因 setup 执行的时间在 beforeCreate 之后,在created之前,所以需在 beforeCreate 和 created 中执行的代码,直接写在 setup 中即可,无需使用钩子函数。
onBeforeMount()
组件被挂载之前被调用
- 已完成了其响应式状态的设置(响应式变量可用),但还没有创建 DOM 节点($refs不可用)。
- 服务器端渲染期间不会被调用
onMounted()
在组件挂载完成后执行
- 服务器端渲染期间不会被调用
// 通过模板引用访问一个元素 <script setup> import { ref, onMounted } from 'vue' const el = ref() onMounted(() => { el.value // <div> }) </script> <template> <div ref="el"></div> </template>
onBeforeUpdate()
组件即将因为响应式状态变更而更新其 DOM 树之前调用
- 用于在 Vue 更新 DOM 之前访问 DOM 状态
- 可以在这个钩子中更改组件的状态
- 服务器端渲染期间不会被调用
onUpdated()
在组件因为响应式状态变更而更新其 DOM 树之后调用
- 父组件的更新钩子将在其子组件的更新钩子之后调用
- 服务器端渲染期间不会被调用
- 不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环!
- 若需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代
// 访问更新后的 DOM <script setup> import { ref, onUpdated } from 'vue' const count = ref(0) onUpdated(() => { // 文本内容应该与当前的 `count.value` 一致 console.log(document.getElementById('count').textContent) }) </script> <template> <button id="count" @click="count++">{{ count }}</button> </template>
onBeforeUnmount()
组件实例被卸载之前调用
- 组件实例依然还保有全部的功能
- 服务器端渲染期间不会被调用
onUnmounted()
组件实例被卸载之后调用
- 常用于手动清理一些副作用,如计时器、DOM 事件监听器、与服务器的连接等。
- 服务器端渲染期间不会被调用
// 页面卸载时清除计时器 <script setup> import { onMounted, onUnmounted } from 'vue' let intervalId onMounted(() => { intervalId = setInterval(() => { // ... }) }) onUnmounted(() => clearInterval(intervalId)) </script>