1. Vue3简介 1.1 什么是Vue? 官方介绍 :Vue (发音为 /vjuː/,类似 view ) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
1.2 什么是Vue3? Vue3是Vue.js框架的第三个主要版本,它带来了一系列引人注目的新特性和改进,旨在提升开发者的效率和代码的可维护性。以下是对Vue3的简要介绍:
性能提升 :Vue3相较于Vue2在性能上有显著的提升,处理大量数据和复杂组件时效果尤为明显。这主要得益于其更高效的渲染机制,包括异步渲染和编译优化等措施。
Composition API :Vue3引入了Composition API,这是一种基于函数的API,它让组件代码更加简洁和可复用。通过Composition API,开发者可以更加灵活和自由地编写组件,实现代码的逻辑复用和更好的组织。
TypeScript支持 :Vue3对TypeScript的支持更加严格,提供了更加完整、准确的类型检查和错误提示。这使得开发者在编写代码时更加安心,减少错误的发生。
可维护性和拓展性 :通过组件化和模块化的方式,Vue3极大地增加了代码的可维护性和拓展性。这使得开发者在项目开发过程中更加容易进行代码管理和扩展。
新特性 :Vue3还引入了一些新的特性,如Fragment(允许组件返回多个根节点)、Suspense(用于异步组件加载时的等待状态管理)和Teleport(允许将组件的子节点渲染到DOM中的任意位置)。这些特性为开发者提供了更多的灵活性和选择。
生态系统 :Vue3拥有丰富的生态系统,包括官方支持的库和社区开发的插件。这使得开发者能够快速地构建复杂的应用程序,并与其他开发者共享和重用代码。
2. 创建Vue3工程 2.1 基于 vue-cli 创建 点击查看官方文档
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 npm install -g @vue/cli npm update -g @vue/cli vue --version vue create vue_test cd vue_testnpm run serve
2.2 基于 vite 创建(推荐) vite
是新一代前端构建工具,官网地址:https://vitejs.cn ,vite
的优势如下:
轻量快速的热重载(HMR
),能实现极速的服务启动。
对 TypeScript
、JSX
、CSS
等支持开箱即用。
真正的按需编译,不再等待整个应用编译完成。
webpack
构建 与 vite
构建对比图如下:
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 npm create vue@latest √ Project name: vue3_test √ Add TypeScript? Yes √ Add JSX Support? No √ Add Vue Router for Single Page Application development? No √ Add Pinia for state management? No √ Add Vitest for Unit Testing? No √ Add an End -to-End Testing Solution? » No √ Add ESLint for code quality? Yes √ Add Prettier for code formatting? No cd vue3_testnpm install npm run dev
PS: 生成的项目中的示例组件使用的是 组合式 API 和 <script setup>
,而非 选项式 API 。官方推荐的 IDE 配置是 Visual Studio Code + Vue - Official 扩展 。
2.3 Vue项目目录结构
2.4 Vue项目开发流程
2.5 Vue项目简要介绍 2.5.1 main.ts组件 1 2 3 4 5 6 7 8 9 import { createApp } from 'vue' import App from './App.vue' const app = createApp (App )app.mount ('#app' )
2.5.2 App.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 <template > </template > <script setup lang ="ts" > </script > <style > </style >
PS:设置 lang="ts"
,但是里面也可以写JS代码 。.vue
是Vue项目中的组件文件,在Vue项目中也称为单文件组件(SFC ,Single-File Components)。Vue 的单文件组件会将一个组件的逻辑 (JS),模板 (HTML) 和样式 (CSS) 封装在同一个文件里(*.vue)。
2.5.3 setup setup
是Vue3
中一个新的配置项,后面会介绍。
2.5.4 scoped 在 Vue 中,scoped
是一个特殊的属性,主要用于 <style>
标签内,以确保其中的样式只应用于当前组件,而不是全局的。这使得组件的样式封装更加简洁和可控,减少了样式冲突的可能性。
具体来说,当你在一个 Vue 组件中使用 <style scoped>
时,Vue 会自动为组件的根元素添加一个唯一的属性(例如 data-v-f3f3eg9
),并在 <style scoped>
中的选择器后自动添加这个属性。这样,这些样式就只会应用于带有这个唯一属性的元素,即当前组件的实例。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 <template > <div class ="my-component" > <h1 > hello</h1 > </div > </template > <style scoped > .my-component { color : red; } </style >
渲染到浏览器后效果:
1 2 3 4 <div data-v-4cadc14e class ="my-component" > <h1 data-v-4cadc14e > hello</h1 > </div >
在上面的例子中,.my-component
的样式只会应用于当前组件的 <div class="my-component">
,而不会影响到其他组件或全局的 .my-component
类。
需要注意的是,scoped
样式有一些限制和注意事项:
子组件的穿透 :默认情况下,scoped
样式不会影响到子组件。但有时你可能需要穿透这个限制,对子组件进行样式调整。这时,你可以使用 ::v-deep
伪元素来实现。
性能考虑 :由于 Vue 需要为每个组件实例添加唯一的属性,并修改选择器,所以使用 scoped
样式可能会增加一些性能开销。在大型应用中,如果可能的话,尽量使用模块化的 CSS 或 CSS-in-JS 方案来替代。
第三方库和全局样式 :scoped
样式不会影响到通过 <link>
标签引入的第三方库或全局样式。这些样式仍然会全局生效。
总的来说,scoped
属性是 Vue 提供的一个非常有用的工具,用于实现组件样式的封装和隔离。但在使用时也需要注意其限制和性能影响。
2.5.5 编写App组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template > <div class ="app" > <h1 > 你好啊!</h1 > </div > </template > <script lang ="ts" > export default { name :'App' } </script > <style > .app { background-color : #ddd ; box-shadow : 0 0 10px ; border-radius : 10px ; padding : 20px ; } </style >
总结:
Vite
项目中,index.html
是项目的入口文件,在项目最外层。
加载 index.html
后,Vite
解析 <script type="module" src="xxx">
指向的 JavaScript
。
Vue3
中是通过 createApp
函数创建一个应用实例。
2.5.6 简单的效果(Vue2
语法) Vue3
向下兼容Vue2
语法,且Vue3
中的模板中可以没有根标签。
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 > <h2 > 姓名:{{ name }}</h2 > <h2 > 年龄:{{ age }}</h2 > <button @click ="changeName" > 修改名字</button > <button @click ="changeAge" > 年龄+1</button > <button @click ="showTel" > 点我查看联系方式</button > </div > </template > <script lang ="ts" > export default { name : 'App' , data ( ) { return { name : '张三' , age : 18 , tel : '13888888888' } }, methods : { changeName ( ) { this .name = 'zhang-san' }, changeAge ( ) { this .age += 1 }, showTel ( ) { alert (this .tel ) } }, } </script >
3. 常用指令 指令:HTML标签上带有 v-
前缀的特殊属性,不同的指令具有不同的含义,可以实现不同的功能。
常用指令:
指令
作用
v-for
列表渲染,遍历容器的元素或者对象的属性
v-bind
为HTML标签绑定属性值,如设置 href , css样式等
v-if/v-else-if/v-else
条件性的渲染某元素,判定为true时渲染,否则不渲染
v-show
根据条件展示某元素,区别在于切换的是display属性的值
v-model
在表单元素上创建双向数据绑定
v-on
为HTML标签绑定事件
3.1 v-for 作用: 列表渲染,遍历容器的元素或者对象的属性。
语法:v-for = "(item,index) in items"
参数说明:
items 为遍历的数组
item 为遍历出来的元素
index 为索引/下标,从0开始 ;可以省略,省略index语法: v-for = “item in items”
使用 v-for 时建议提供一个唯一标识 key
,语法:v-for="item in items" :key="item.id"
详见官网:通过 key 管理状态
PS:遍历的数组,必须在data中定义; 要想让哪个标签循环展示多次,就在哪个标签上使用 v-for 指令。
3.2 v-bind 作用:动态为HTML标签绑定属性值,如设置href,src,style样式等。
语法:v-bind:属性名="属性值"
简化::属性名="属性值"
3.3 v-if & v-show 作用:这两类指令,都是用来控制元素的显示与隐藏的。
v-if
语法:v-if="表达式"
,表达式值为 true,显示;false,隐藏。
其它:可以配合 v-else-if
/ v-else
进行链式调用条件判断。
原理:基于条件判断,来控制创建或移除元素节点(条件渲染)。
场景:要么显示,要么不显示,不频繁切换的场景。
v-show
语法:v-show="表达式"
,表达式值为 true,显示;false,隐藏。
原理:基于CSS样式display来控制显示与隐藏。
场景:频繁切换显示隐藏的场景。
3.4 v-on 作用:为html标签绑定事件。
语法:v-on:事件名="函数名"
简化:@事件名="函数名"
3.5 v-model 作用:在表单元素上使用,双向数据绑定。可以方便的 获取 或 设置 表单项数据 。
语法:v-model="变量名"
PS:v-model 中绑定的变量,必须在data中定义。
4. 组件基础 组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:
这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。Vue 同样也能很好地配合原生 Web Component。如果你想知道 Vue 组件与原生 Web Components 之间的关系,可以阅读此章节 。
使用步骤:
第一步:引入组件。
第二步:注入组件。
第三步:显示组件。
src
目录下新建 components
文件夹用于存放组件。定义一个组件MyComponents.vue
:
1 2 3 4 5 6 7 8 9 <template > <h1 > 我是一个组件</h1 > </template > <script setup lang ="ts" > console .log ('我是一个组件' )</script > <style > </style >
在App.vue引入组件(组件可以被重用任意多次):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template > <MyComponents /> <MyComponents /> <MyComponents /> <MyComponents /> </template > <script lang ="ts" > import MyComponents from './components/MyComponents.vue' export default { name : 'App' , components : { MyComponents } } </script >
通过 <script setup>
,导入的组件都在模板中直接可用,可以省略第二步:注入组件
(组合式 API)
1 2 3 4 5 6 7 8 9 10 11 12 <template > <MyComponents /> <MyComponents /> <MyComponents /> <MyComponents /> </template > <script setup lang ="ts" > import MyComponents from './components/MyComponents.vue' </script >
5. Vue3核心语法 5.1 OptionsAPI 与 CompositionAPI
Vue2
的API
设计是Options
(配置)风格的。选项式 API (Options API)
Vue3
的API
设计是Composition
(组合)风格的。组合式 API (Composition API)
5.1.1 Options API 的弊端 Options
类型的 API
,数据、方法、计算属性等,是分散在:data
、methods
、computed
中的,若想新增或者修改一个需求,就需要分别修改:data
、methods
、computed
,不便于维护和复用。
5.1.2 Composition API 的优势 可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。
说明:以上四张动图原创作者为大帅老猿。
5.2 拉开序幕的 setup 5.2.1 setup 概述 setup
是Vue3
中一个新的配置项,值是一个函数,它是 Composition API
“表演的舞台” ,组件中所用到的:数据、方法、计算属性、监视……等等,均配置在setup
中。
特点如下:
setup
函数返回的对象中的内容,可直接在模板中使用。
setup
中访问this
是undefined
。
setup
函数会在beforeCreate
之前调用,它是“领先”所有钩子执行的。
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 > <h2 > 姓名:{{ name }}</h2 > <h2 > 年龄:{{ age }}</h2 > <button @click ="changeName" > 修改名字</button > <button @click ="changeAge" > 年龄+1</button > <button @click ="showTel" > 点我查看联系方式</button > </div > </template > <script lang ="ts" > export default { name : 'App' , setup ( ) { let name = '张三' let age = 18 let tel = '13888888888' function changeName ( ) { name = 'zhang-san' console .log (name) } function changeAge ( ) { age += 1 console .log (age) } function showTel ( ) { alert (tel) } return { name, age, tel, changeName, changeAge, showTel } } } </script >
5.2.2 setup 的返回值
若返回一个对象 :则对象中的:属性、方法等,在模板中均可以直接使用(重点关注)。
若返回一个函数 :则可以自定义渲染内容,代码如下:
1 2 3 setup ( ){ return ()=> '你好啊!' }
5.2.3 setup 与 Options API 的关系
Vue2
的配置(data
、methos
……)中可以访问到 setup
中的属性、方法。
但在setup
中不能访问到 Vue2
的配置(data
、methos
……)。
如果与Vue2
冲突,则setup
优先。
5.2.4 setup 语法糖 setup
函数有一个语法糖,这个语法糖,可以让我们把setup
独立出去,代码如下:
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 <template > <div > <h2 > 姓名:{{ name }}</h2 > <h2 > 年龄:{{ age }}</h2 > <button @click ="changName" > 修改名字</button > <button @click ="changAge" > 年龄+1</button > <button @click ="showTel" > 点我查看联系方式</button > </div > </template > <script lang ="ts" > export default { name : 'App' , } </script > <script setup lang ="ts" > console .log (this ) let name = '张三' let age = 18 let tel = '13888888888' function changName ( ) { name = '李四' } function changAge ( ) { console .log (age) age += 1 } function showTel ( ) { alert (tel) } </script >
5.3 ref 创建:基本类型的响应式数据 作用:定义响应式变量。
语法:let 响应式对象 = ref(初始值)
返回值:一个 RefImpl
的实例对象,简称 ref对象
或 ref
,ref
对象的 value
属性是响应式的 。
注意点:
使用前需要导入:import { ref } from 'vue'
JS
中操作数据需要xxx.value
,但模板中不需要 .value
,直接使用即可。
对于let name = ref('张三')
来说,name
不是响应式的,name.value
是响应式的。
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 <template > <div > <h2 > 姓名:{{ name }}</h2 > <h2 > 年龄:{{ age }}</h2 > <button @click ="changeName" > 修改名字</button > <button @click ="changeAge" > 年龄+1</button > <button @click ="showTel" > 点我查看联系方式</button > </div > </template > <script setup lang ="ts" > import { ref } from 'vue' let name = ref ('张三' )let age = ref (18 )let tel = '13888888888' function changeName ( ) { name.value = '李四' console .log (name.value ) } function changeAge ( ) { age.value += 1 console .log (age.value ) } function showTel ( ) { alert (tel) } </script >
5.4 reactive 创建:对象类型的响应式数据 作用:定义一个响应式对象 。(基本类型不要用它,要用ref
,否则报错)
语法:let 响应式对象 = reactive(源对象)
返回值:一个Proxy
的实例对象,简称:响应式对象。
注意点:
使用前需要导入:import { reactive } from 'vue'
reactive
定义的响应式数据是“深层次”的。
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 <template > <div > <h2 > 汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2 > <h2 > 游戏列表:</h2 > <ul > <li v-for ="g in games" :key ="g.id" > {{ g.name }}</li > </ul > <h2 > 测试:{{ obj.a.b.c.d }}</h2 > <button @click ="changeCarPrice" > 修改汽车价格</button > <button @click ="changeFirstGame" > 修改第一游戏</button > <button @click ="test" > 测试</button > </div > </template > <script setup lang ="ts" > import { reactive } from 'vue' let car = reactive ({ brand : '奔驰' , price : 100 })let games = reactive ([ { id : 'ahsgdyfa01' , name : '英雄联盟' }, { id : 'ahsgdyfa02' , name : '王者荣耀' }, { id : 'ahsgdyfa03' , name : '原神' } ]) let obj = reactive ({ a : { b : { c : { d : 666 } } } }) function changeCarPrice ( ) { car.price += 10 } function changeFirstGame ( ) { games[0 ].name = '流星蝴蝶剑' } function test ( ) { obj.a .b .c .d = 999 } </script >
5.5 ref 创建:对象类型的响应式数据 其实ref
接收的数据可以是:基本类型 、对象类型 。若ref
接收的是对象类型,内部其实也是调用了reactive
函数。区别在于操作数据是否需要加:.value
。
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 <template > <div > <h2 > 汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2 > <h2 > 游戏列表:</h2 > <ul > <li v-for ="g in games" :key ="g.id" > {{ g.name }}</li > </ul > <h2 > 测试:{{ obj.a.b.c.d }}</h2 > <button @click ="changeCarPrice" > 修改汽车价格</button > <button @click ="changeFirstGame" > 修改第一游戏</button > <button @click ="test" > 测试</button > </div > </template > <script setup lang ="ts" > import { ref } from 'vue' let car = ref ({ brand : '奔驰' , price : 100 })let games = ref ([ { id : 'ahsgdyfa01' , name : '英雄联盟' }, { id : 'ahsgdyfa02' , name : '王者荣耀' }, { id : 'ahsgdyfa03' , name : '原神' } ]) let obj = ref ({ a : { b : { c : { d : 666 } } } }) function changeCarPrice ( ) { car.value .price += 10 console .log (car.value .price ) } function changeFirstGame ( ) { games.value [0 ].name = '流星蝴蝶剑' } function test ( ) { obj.value .a .b .c .d = 999 } </script >
5.6 ref 对比 reactive 宏观角度看:
ref
用来定义:基本类型数据 、对象类型数据 。
reactive
用来定义:对象类型数据 。
区别:
使用原则:
若需要一个基本类型的响应式数据,必须使用ref
。
若需要一个响应式对象,层级不深,ref
、reactive
都可以。
若需要一个响应式对象,且层级较深,推荐使用reactive
。
5.7 toRefs 与 toRef 作用:将一个响应式对象中的每一个属性,转换为ref
对象。
注意点:
使用前需要导入:import { toRefs, toRef } from 'vue'
toRefs
与toRef
功能一致,但toRefs
可以批量转换。
语法如下:
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 > <h2 > 姓名:{{ person.name }}</h2 > <h2 > 年龄:{{ person.age }}</h2 > <h2 > 性别:{{ person.gender }}</h2 > <button @click ="changeName" > 修改名字</button > <button @click ="changeAge" > 修改年龄</button > <button @click ="changeGender" > 修改性别</button > </div > </template > <script setup lang ="ts" > import { reactive, toRefs, toRef } from 'vue' let person = reactive ({ name : '张三' , age : 18 , gender : '男' })let { name, gender } = toRefs (person)let age = toRef (person, 'age' )function changeName ( ) { name.value += '~' } function changeAge ( ) { age.value += 1 } function changeGender ( ) { gender.value = '女' } </script >
5.8 computed 作用:根据已有数据计算出新数据(和Vue2
中的computed
作用一致)。
注意点:
使用前需要导入:import { computed } from '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 38 <template > <div > 姓:<input type ="text" v-model ="firstName" > <br > 名:<input type ="text" v-model ="lastName" > <br > 全名:<span > {{ fullName }}</span > <br > <button @click ="changeFullName" > 全名改为:li-si</button > </div > </template > <script setup lang ="ts" > import { ref, computed } from 'vue' let firstName = ref ('zhang' )let lastName = ref ('san' )let fullName = computed ({ get ( ) { return firstName.value + '-' + lastName.value }, set (val ) { console .log ('有人修改了fullName' , val) firstName.value = val.split ('-' )[0 ] lastName.value = val.split ('-' )[1 ] } }) function changeFullName ( ) { fullName.value = 'li-si' } </script >
5.9 watch 作用:监视数据的变化(和Vue2
中的watch
作用一致)
特点:Vue3
中的watch
只能监视以下四种数据 :
ref
定义的数据。
reactive
定义的数据。
函数返回一个值(getter
函数)。
一个包含上述内容的数组。
注意点:
使用前需要导入:import { watch } from 'vue'
我们在Vue3
中使用watch
的时候,通常会遇到以下几种情况:
情况一
监视ref
定义的【基本类型】数据:直接写数据名即可,监视的是其value
值的改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template > <div > <h1 > 情况一:监视【ref】定义的【基本类型】数据</h1 > <h2 > 当前求和为:{{ sum }}</h2 > <button @click ="changeSum" > 点我sum+1</button > </div > </template > <script setup lang ="ts" > import { ref, watch } from 'vue' let sum = ref (0 )function changeSum ( ) { sum.value += 1 } const stopWatch = watch (sum, (newValue, oldValue ) => { console .log ('sum变化了' , newValue, oldValue) if (newValue >= 10 ) { stopWatch () } }) </script >
情况二
监视ref
定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。
注意:
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 <template > <div > <h1 > 情况二:监视【ref】定义的【对象类型】数据</h1 > <h2 > 姓名:{{ person.name }}</h2 > <h2 > 年龄:{{ person.age }}</h2 > <button @click ="changeName" > 修改名字</button > <button @click ="changeAge" > 修改年龄</button > <button @click ="changePerson" > 修改整个人</button > </div > </template > <script setup lang ="ts" > import {ref,watch} from 'vue' let person = ref ({ name :'张三' , age :18 }) function changeName ( ){ person.value .name += '~' } function changeAge ( ){ person.value .age += 1 } function changePerson ( ){ person.value = {name :'李四' ,age :90 } } watch (person,(newValue,oldValue )=> { console .log ('person变化了' ,newValue,oldValue) },{deep :true }) </script >
情况三
监视 reactive
定义的【对象类型】数据,且默认开启了深度监视,不能关闭。
注意:
直接给 watch()
传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发。
若修改的是 reactive
定义的对象或者对象属性,newValue
和 oldValue
都是新值,因为它们是同一个对象。
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 <template > <div > <h1 > 情况三:监视【reactive】定义的【对象类型】数据</h1 > <h2 > 姓名:{{ person.name }}</h2 > <h2 > 年龄:{{ person.age }}</h2 > <button @click ="changeName" > 修改名字</button > <button @click ="changeAge" > 修改年龄</button > <button @click ="changePerson" > 修改整个人</button > <hr > <h2 > 测试:{{ obj.a.b.c }}</h2 > <button @click ="test" > 修改obj.a.b.c</button > </div > </template > <script setup lang ="ts" > import { reactive, watch } from 'vue' let person = reactive ({ name : '张三' , age : 18 }) let obj = reactive ({ a : { b : { c : 666 } } }) function changeName ( ) { person.name += '~' } function changeAge ( ) { person.age += 1 } function changePerson ( ) { Object .assign (person, { name : '李四' , age : 80 }) } function test ( ) { obj.a .b .c = 888 } watch (person, (newValue, oldValue ) => { console .log ('person变化了' , newValue, oldValue) }) watch (obj, (newValue, oldValue ) => { console .log ('Obj变化了' , newValue, oldValue) }) </script >
情况四
监视 ref
或 reactive
定义的【对象类型】数据中的某个属性 。
注意:
若该属性值不是 【对象类型】,需要写成函数形式。
若该属性值是依然 是【对象类型】,可直接编,也可写成函数,建议写成函数。
结论:监视的要是对象里的属性,那么最好写函数式。注意点: 若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监视。
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 <template > <div > <h1 > 情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1 > <h2 > 姓名:{{ person.name }}</h2 > <h2 > 年龄:{{ person.age }}</h2 > <h2 > 汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2 > <button @click ="changeName" > 修改名字</button > <button @click ="changeAge" > 修改年龄</button > <button @click ="changeC1" > 修改第一台车</button > <button @click ="changeC2" > 修改第二台车</button > <button @click ="changeCar" > 修改整个车</button > </div > </template > <script setup lang ="ts" > import { reactive, watch } from 'vue' let person = reactive ({ name : '张三' , age : 18 , car : { c1 : '奔驰' , c2 : '宝马' } }) function changeName ( ) { person.name += '~' } function changeAge ( ) { person.age += 1 } function changeC1 ( ) { person.car .c1 = '奥迪' } function changeC2 ( ) { person.car .c2 = '大众' } function changeCar ( ) { person.car = { c1 : '雅迪' , c2 : '爱玛' } } watch (() => person.car , (newValue, oldValue ) => { console .log ('person.car变化了' , newValue, oldValue) }, { deep : true }) </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 <template > <div > <h1 > 情况五:监视上述的多个数据</h1 > <h2 > 姓名:{{ person.name }}</h2 > <h2 > 年龄:{{ person.age }}</h2 > <h2 > 汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2 > <button @click ="changeName" > 修改名字</button > <button @click ="changeAge" > 修改年龄</button > <button @click ="changeC1" > 修改第一台车</button > <button @click ="changeC2" > 修改第二台车</button > <button @click ="changeCar" > 修改整个车</button > </div > </template > <script setup lang ="ts" > import { reactive, watch } from 'vue' let person = reactive ({ name : '张三' , age : 18 , car : { c1 : '奔驰' , c2 : '宝马' } }) function changeName ( ) { person.name += '~' } function changeAge ( ) { person.age += 1 } function changeC1 ( ) { person.car .c1 = '奥迪' } function changeC2 ( ) { person.car .c2 = '大众' } function changeCar ( ) { person.car = { c1 : '雅迪' , c2 : '爱玛' } } watch ([() => person.name , person.car ], (newValue, oldValue ) => { console .log ('person.car变化了' , newValue, oldValue) }, { deep : true }) </script >
5.10 watchEffect 官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
watch
对比 watchEffect
:
注意点:
使用前需要导入:import { watchEffect } from '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 38 39 40 41 42 43 44 45 46 47 <template > <div > <h1 > 需求:水温达到50℃,或水位达到20cm,则联系服务器</h1 > <h2 id ="demo" > 水温:{{ temp }}℃</h2 > <h2 > 水位:{{ height }}cm</h2 > <button @click ="changePrice" > 水温+10</button > <button @click ="changeSum" > 水位+10</button > </div > </template > <script setup lang ="ts" > import { ref, watch, watchEffect } from 'vue' let temp = ref (0 )let height = ref (0 )function changePrice ( ) { temp.value += 10 } function changeSum ( ) { height.value += 10 } watch ([temp, height], (value ) => { const [newTemp, newHeight] = value if (newTemp >= 50 || newHeight >= 20 ) { console .log ('联系服务器' ) } }) const stopWtach = watchEffect (() => { if (temp.value >= 50 || height.value >= 20 ) { console .log ('联系服务器' ) } if (temp.value === 100 || height.value === 50 ) { console .log ('清理了' ) stopWtach () } }) </script >
5.11 标签的 ref 属性 作用:用于注册模板引用。
用在普通DOM
标签上,获取的是DOM
节点。
用在组件标签上,获取的是组件实例对象。
注意点:
使用前需要导入:import { ref } from 'vue'
用在普通DOM
标签上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template > <div > <h1 id ="title1" > 你好</h1 > <h1 ref ="title1" > 你好</h1 > <button @click ="showLog" > 点我打印内容</button > </div > </template > <script setup lang ="ts" > import { ref } from 'vue' let title1 = ref ()function showLog ( ) { console .log (document .getElementById ('title1' )) console .log (title1.value ) } </script >
用在组件标签上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template > <Person ref ="ren" /> <button @click ="test" > 测试</button > </template > <script setup lang ="ts" > import Person from './components/Person.vue' import { ref } from 'vue' let ren = ref ()function test ( ) { console .log (ren.value ) } </script >
可以拿到组件实例对象,但是看不到里面的任何东西,这是一种保护机制。
如果要看子组件内容,子组件 <script>
标签中要使用 defineExpose
暴露内容:
1 2 3 4 5 6 let name = ref ('张三' )let age = ref (18 )defineExpose ({ name, age })
5.12 props src
目录下新建 types
文件夹,新建 index.ts
:
1 2 3 4 5 6 7 8 9 10 export interface PersonInter { id : string , name : string , age : number , } export type Persons = Array <PersonInter >
App.vue
中代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template > <Person :list ="persons" /> </template > <script setup lang ="ts" > import Person from './components/Person.vue' import {reactive} from 'vue' import {type Persons } from './types' let persons = reactive<Persons >([{id :'e98219e12' ,name :'张三' ,age :18 }, {id :'e98219e13' ,name :'李四' ,age :19 }, {id :'e98219e14' ,name :'王五' ,age :20 } ]) </script >
Person.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 <template> <div> <ul> <li v-for="item in list" :key="item.id"> {{ item.name }}--{{ item.age }} </li> </ul> </div> </template> <script setup lang="ts"> import { type Persons } from '@/types' // 第一种写法:仅接收 // const props = defineProps(['list']) // 第二种写法:接收 + 限制类型 // defineProps<{ list: Persons }>() // 第三种写法:接收 + 限制类型 + 指定默认值 + 限制必要性 let props = withDefaults(defineProps<{ list?: Persons }>(), { list: () => [{ id: 'asdasg01', name: '小猪佩奇', age: 18 }] }) console.log(props) </script>
5.13 生命周期 概念:Vue
组件实例在创建时要经历一系列的初始化步骤,在此过程中 Vue
会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子
规律:生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁 ,每个阶段都有两个钩子,一前一后。
Vue2
的生命周期:
创建阶段:beforeCreate
、created
挂载阶段:beforeMount
、mounted
更新阶段:beforeUpdate
、updated
销毁阶段:beforeDestroy
、destroyed
Vue3
的生命周期:
创建阶段:setup
挂载阶段:onBeforeMount
、onMounted
更新阶段:onBeforeUpdate
、onUpdated
卸载阶段:onBeforeUnmount
、onUnmounted
常用的钩子:onMounted
(挂载完毕)、onUpdated
(更新完毕)、onBeforeUnmount
(卸载之前)
示例代码:
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 <template > <div > <h2 > 当前求和为:{{ sum }}</h2 > <button @click ="changeSum" > 点我sum+1</button > </div > </template > <script setup lang ="ts" > import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue' let sum = ref (0 )function changeSum ( ) { sum.value += 1 } console .log ('setup' )onBeforeMount (() => { console .log ('挂载之前' ) }) onMounted (() => { console .log ('挂载完毕' ) }) onBeforeUpdate (() => { console .log ('更新之前' ) }) onUpdated (() => { console .log ('更新完毕' ) }) onBeforeUnmount (() => { console .log ('卸载之前' ) }) onUnmounted (() => { console .log ('卸载完毕' ) }) </script >
5.14 自定义hook 什么是 hook
?—— 本质是一个函数,把setup
函数中使用的 Composition API
进行了封装,类似于 vue2.x
中的 mixin
。
自定义 hook
的优势:复用代码, 让 setup
中的逻辑更清楚易懂。
PS:自定义 hook
的命名一般为 useXXX.ts
示例代码
src
目录下新建 hooks
文件夹,useSum.ts
中内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { ref, onMounted } from 'vue' export default function ( ) { let sum = ref (0 ) const increment = ( ) => { sum.value += 1 } const decrement = ( ) => { sum.value -= 1 } onMounted (() => { increment () }) return { sum, increment, decrement } }
useDog.ts
中内容如下:
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 import { reactive, onMounted } from 'vue' import axios, { AxiosError } from 'axios' export default function ( ) { let dogList = reactive<string []>([]) async function getDog ( ) { try { let { data } = await axios.get ('https://dog.ceo/api/breed/pembroke/images/random' ) dogList.push (data.message ) } catch (error) { const err = <AxiosError >error console .log (err.message ) } } onMounted (() => { getDog () }) return { dogList, getDog } }
组件中具体使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <h2 > 当前求和为:{{ sum }}</h2 > <button @click ="increment" > 点我+1</button > <button @click ="decrement" > 点我-1</button > <hr > <img v-for ="(dog, index) in dogList" :key ="index" :src ="(dog as string)" > <br > <button @click ="getDog" > 再来一只狗</button > </template > <script setup lang ="ts" > import useSum from '@/hooks/useSum' import useDog from '@/hooks/useDog' const { sum, increment, decrement } = useSum ()const { dogList, getDog } = useDog ()</script >
6. 路由 6.1 路由介绍 在Vue.js中,路由(Routing)是构建单页面应用(SPA)的重要组成部分,它允许你根据URL的不同来展示不同的内容或组件,而无需重新加载整个页面。Vue Router 是Vue.js官方的路由管理器插件,它和Vue.js深度集成,使得构建单页面应用变得简单而直观。
6.2 使用步骤
第一步:安装 vue-route
第二步:在 src/router/index.ts
中创建路由器,并导出
第三步:在vue应用实例 main.ts
中使用 vue-router
第四步:声明 router-view
标签,展示组件内容
示例:
1、安装vue-route
1 npm install vue-router@4
2、在src/router/index.ts
中创建路由器,并导出
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 import { createRouter, createWebHistory } from 'vue-router' import Home from '@/views/Home.vue' import News from '@/views/News.vue' import About from '@/views/About.vue' const router = createRouter ({ history : createWebHistory (), routes : [ { path : '/home' , component : Home }, { path : '/news' , component : News }, { path : '/about' , component : About } ] }) export default router
3、在vue应用实例 main.ts
中使用 vue-router
1 2 3 4 5 import router from './router' app.use (router)
4、声明 router-view
标签,展示组件内容,
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 > <h2 class ="title" > Vue路由测试</h2 > <div > <router-link to ="/home" > 首页</router-link > <br > <router-link to ="/news" class ="active" > 新闻</router-link > <br > <router-link to ="/about" > 关于</router-link > <br > </div > <div > <router-view > </router-view > </div > </div > </template > <script setup lang ="ts" > </script >
两个注意点:
6.3 路由器工作模式 6.3.1 history模式 优点:URL
更加美观,不带有 #
,更接近传统的网站 URL
。
缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404
错误。
1 2 3 4 const router = createRouter ({ history :createWebHistory (), })
6.3.2 hash模式 优点:兼容性更好,因为不需要服务器端处理路径。
缺点:URL
带有 #
不太美观,且在 SEO
优化方面相对较差。
1 2 3 4 const router = createRouter ({ history :createWebHashHistory (), })
6.4 to的两种写法 1 2 3 4 5 <router-link to ="/home" > 主页</router-link > <router-link :to ="{path:'/home'}" > Home</router-link >
6.5 命名路由 作用:可以简化路由跳转及传参。(后面会讲)
给路由规则命名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 routes : [ { name : 'zhuye' , path : '/home' , component : Home }, { name : 'xinwen' , path : '/news' , component : News , }, { name : 'guanyu' , path : '/about' , component : About } ]
跳转路由:
1 2 3 4 5 <router-link to ="/news/detail" > 跳转</router-link > <router-link :to ="{name:'guanyu'}" > 跳转</router-link >
6.6 嵌套路由
示例
1、编写 News
的子路由:Detail.vue
2、配置路由规则,使用 children
配置项:
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 import { createRouter, createWebHistory } from 'vue-router' import Home from '@/views/Home.vue' import News from '@/views/News.vue' import About from '@/views/About.vue' import Detail from '@/views/Detail.vue' const router = createRouter ({ history : createWebHistory (), routes : [ { path : '/home' , component : Home }, { path : '/news' , component : News , children : [{ path : 'detail' , component : Detail }] }, { path : '/about' , component : About } ] }) export default router
3、跳转路由(记得要加完整路径):
1 2 3 <router-link to ="/news/detail" > xxxx</router-link > <router-link :to ="{path:'/news/detail'}" > xxxx</router-link >
4、去 News
组件中预留一个 <router-view>
1 2 3 4 5 6 7 8 9 10 11 12 <template > <div class ="news" > <ul > <li v-for ="news in newsList" :key ="news.id" > <router-link to ="/news/detail" > {{ news.title }}</router-link > </li > </ul > <div class ="news-content" > <router-view > </router-view > </div > </div > </template >
6.7 路由传参 6.7.1 query参数 1、传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <router-link :to ="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`" > {{ news.title }} </router-link > <router-link :to ="{ //name:'detail', //用name也可以跳转 path: '/news/detail', query: { id: news.id, title: news.title, content: news.content } }" > {{ news.title }} </router-link >
2、接收参数:
1 2 3 4 import {useRoute} from 'vue-router' const route = useRoute ()console .log (route.query )
6.7.2 params参数 1、传递参数(需要占位 )
index.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const router = createRouter ({ history : createWebHistory (), routes : [ { path : '/home' , component : Home }, { path : '/news' , component : News , children : [{ name :'detail' , path : 'detail/:id/:title/:content' , component : Detail }] }, { path : '/about' , component : About } ] })
News.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <RouterLink :to ="`/news/detail/${news.id}/${news.title}/${news.content}`" > {{ news.title }} </RouterLink > <router-link :to ="{ name: 'detail', //用name跳转 params: { id: news.id, title: news.title, content: news.content } }" > {{ news.title }} </router-link >
2、接收参数:
1 2 3 4 import {useRoute} from 'vue-router' const route = useRoute ()console .log (route.params )
注意:
传递 params
参数时,若使用 to
的对象写法,必须 使用 name
配置项,不能用 path
。
传递 params
参数时,需要提前在规则中占位。
6.8 路由的props配置 作用:让路由组件更方便的收到参数(可以将路由参数作为 props
传给组件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { name : 'detail' , path : 'detail/:id/:title/:content' , component : Detail , props (route ) { return route.query } }
使用 路由的 props 配置
,这样接收参数:
1 2 3 <script setup lang ="ts" > defineProps (['id' , 'title' , 'content' ])</script >
6.9 replace属性 作用:控制路由跳转时操作浏览器历史记录的模式。
浏览器的历史记录有 push
和 replace
两种写入方式:
push
是追加历史记录(默认值)。
replace
是替换当前记录。
开启 replace
模式:
1 <router-link replace > News</router-link > <br >
6.10 编程式导航 路由组件的两个重要的属性:$route
和 $router
变成了两个 hooks
。
1 2 3 4 5 6 7 8 9 import { useRoute, useRouter } from 'vue-router' const route = useRoute ()const router = useRouter ()console .log (route.query )console .log (route.parmas )console .log (router.push )console .log (router.replace )
<router-link>
渲染到页面会转换为 <a>
标签,如果只用 <router-link>
去跳转那就意味着所有导航区只能是<a>
标签,假如想要实现使用 <button>
标签实现跳转,就要用到 useRouter
实现编程式导航。
案例:点击按钮也可以查看新闻
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 <template > <div class ="news" > <ul > <li v-for ="news in newsList" :key ="news.id" > <button @click ="showNewsDetail(news)" > 查看新闻</button > <router-link :to ="{ name: 'detail', //用name跳转 query: { // query参数 id: news.id, title: news.title, content: news.content } }" > {{ news.title }} </router-link > </li > </ul > <div class ="news-content" > <router-view > </router-view > </div > </div > </template > <script setup lang ="ts" > import { reactive } from 'vue' const newsList = reactive ([ { id : 'asfdtrfay01' , title : '很好的抗癌食物' , content : '西蓝花' }, { id : 'asfdtrfay02' , title : '如何一夜暴富' , content : '学IT' }, { id : 'asfdtrfay03' , title : '震惊,万万没想到' , content : '明天是周一' }, { id : 'asfdtrfay04' , title : '好消息!好消息!' , content : '快过年了' } ]) import { useRouter } from 'vue-router' const router = useRouter ()interface NewsInter { id : string, title : string, content : string } function showNewsDetail (news: NewsInter ) { router.push ({ name : 'detail' , query : { id : news.id , title : news.title , content : news.content } }) } </script >
6.11 重定向 作用:将特定的路径,重新定向到已有路由。
具体编码:
1 2 3 4 { path : '/' , redirect : '/home' },
7. pinia Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。
7.1 准备一个效果
7.2 搭建 pinia 环境 1、安装 pinia
2、 在 vue 应用实例 src/main.ts
中使用 pinia
1 2 3 4 5 6 import { createPinia } from 'pinia' const pinia = createPinia ()app.use (pinia)
此时开发者工具中已经有了 pinia
选项:
7.3 存储+读取数据 Store
是一个保存:状态 、业务逻辑 的实体,每个组件都可以读取 、写入 它。
它有三个概念:state
、getter
、action
,相当于组件中的: data
、 computed
和 methods
。
具体编码
1、src/store/count.ts
PS:暴露的store一般命名为:useXxxxxStore
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { defineStore } from 'pinia' export const useCountStore = defineStore ('count' , { actions : {}, state ( ) { return { sum : 6 } }, getters : {} })
2、src/store/loveTalk.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { defineStore } from 'pinia' export const useTalkStore = defineStore ('talk' , { actions : {}, state ( ) { return { talkList : [ { id : 'yuysada01' , content : '你今天有点怪,哪里怪?怪好看的!' }, { id : 'yuysada02' , content : '草莓、蓝莓、蔓越莓,你想我了没?' }, { id : 'yuysada03' , content : '心里给你留了一块地,我的死心塌地' } ] } }, getters : {} })
3、组件中使用 state
中的数据
1 2 3 4 5 6 7 8 9 10 11 <template > <h2 > 当前求和为:{{ countStore.sum }}</h2 > </template > <script setup lang ="ts" > import { useCountStore } from '@/store/count' const countStore = useCountStore ()</script >
1 2 3 4 5 6 7 8 9 10 11 12 13 <template > <ul > <li v-for ="talk in talkStore.talkList" :key ="talk.id" > {{ talk.content }} </li > </ul > </template > <script setup lang ="ts" > import { useTalkStore } from '@/store/loveTalk' const talkStore = useTalkStore ()</script >
开发者工具中 pinia
选项里也可以看到对应数据:
7.4 修改数据(三种方式) 第一种方式:直接修改
第二种方式:批量修改
1 2 3 4 countStore.$patch({ sum :999 , name :'muyoukule' })
第三种方式:借助 action
修改(action
中可以编写一些业务逻辑)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { defineStore } from 'pinia' export const useCountStore = defineStore ('count' , { actions : { increment (value: number ) { if (this .sum < 10 ) { this .sum += value } }, decrement (value: number ) { if (this .sum > 1 ) { this .sum -= value } } }, })
组件中调用 action
即可:
1 2 3 4 5 const countStore = useCountStore ()countStore.increment (n.value )
7.5 storeToRefs 借助 storeToRefs
将 store
中的数据转为 ref
对象,方便在模板中使用。
PS:pinia
提供的 storeToRefs
只会将数据做转换,而 Vue
的 toRefs
会转换 store
中所有数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template > <div class ="count" > <h2 > 当前求和为:{{ sum }}</h2 > </div > </template > <script setup lang ="ts" > import { useCountStore } from '@/store/count' import { storeToRefs } from 'pinia' const countStore = useCountStore ()const { sum } = storeToRefs (countStore)</script >
7.6 getters 概念:当 state
中的数据,需要经过处理后再使用时,可以使用 getters
配置。
追加 getters
配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { defineStore } from 'pinia' export const useCountStore = defineStore ('count' , { actions : { }, state ( ) { return { sum : 1 , name : 'muyoukule' } }, getters : { bigSum : (state): number => state.sum * 10 , upperName (): string { return this .name .toUpperCase () } } })
组件中读取数据:
1 2 const { increment, decrement } = countStorelet { sum, name, bigSum, upperName } = storeToRefs (countStore)
7.7 $subscribe 通过 store 的 $subscribe()
方法侦听 state
及其变化。
1 2 3 4 talkStore.$subscribe((mutate, state ) => { console .log ('LoveTalk' , mutate, state) localStorage .setItem ('talk' , JSON .stringify (talkList.value )) })
7.8 store组合式写法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { defineStore } from 'pinia' import axios from 'axios' import { nanoid } from 'nanoid' import { reactive } from 'vue' export const useTalkStore = defineStore ('talk' , () => { const talkList = reactive ( JSON .parse (localStorage .getItem ('talkList' ) as string ) || [] ) async function getATalk ( ) { let { data : { content : title } } = await axios.get ('https://api.uomg.com/api/rand.qinghua?format=json' ) let obj = { id : nanoid (), title } talkList.unshift (obj) } return { talkList, getATalk } })
8. 组件通信 Vue3
组件通信和Vue2
的区别:
vuex
换成了pinia
。
把.sync
优化到了v-model
里面了。
把$listeners
所有的东西,合并到$attrs
中了。
$children
被砍掉了。
常见搭配形式:
8.1 props 概述:props
是使用频率最高的一种通信方式,常用于 :父 <===> 子 。
若 父传子 :属性值是非函数 。
若 子传父 :属性值是函数 。
父组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template > <div class ="father" > <h3 > 父组件</h3 > <h4 > 汽车:{{ car }}</h4 > <h4 v-show ="toy" > 子给的玩具:{{ toy }}</h4 > <Child :car ="car" :sendToy ="getToy" /> </div > </template > <script setup lang ="ts" > import Child from './Child.vue' import { ref } from 'vue' let car = ref ('奔驰' )let toy = ref ('' )function getToy (value: string ) { toy.value = value } </script >
子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template > <div class ="child" > <h3 > 子组件</h3 > <h4 > 玩具:{{ toy }}</h4 > <h4 > 父给的车:{{ car }}</h4 > <button @click ="sendToy(toy)" > 把玩具给父亲</button > </div > </template > <script setup lang ="ts" > import { ref } from 'vue' let toy = ref ('奥特曼' )defineProps (['car' , 'sendToy' ])</script >
8.2 自定义事件 概述:自定义事件常用于:子 ===> 父。
注意区分:原生事件、自定义事件。
原生事件:
事件名是特定的(click
、mosueenter
等等)
事件对象$event
: 是包含事件相关信息的对象(pageX
、pageY
、target
、keyCode
)
自定义事件:
事件名是任意名称
事件对象$event
: 是调用emit
时所提供的数据,可以是任意类型!!!
示例:
1 2 3 4 5 <Child @send-toy ="toy = $event" /> <button @click ="toy = $event" > 测试</button >
1 2 this .$emit('send-toy' , 具体数据)
8.3 mitt 概述:与消息订阅与发布(pubsub
)功能类似,可以实现任意组件间通信。
1、安装mitt
2、新建文件:src\utils\emitter.ts
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 import mitt from 'mitt' const emitter = mitt ()export default emitter
3、接收数据的组件中:绑定事件、同时在销毁前解绑事件:
1 2 3 4 5 6 7 8 9 10 11 12 import emitter from "@/utils/emitter" ;import { onUnmounted } from "vue" ;emitter.on ('send-toy' , (value ) => { console .log ('send-toy事件被触发' , value) }) onUnmounted (() => { emitter.off ('send-toy' ) })
4、提供数据的组件,在合适的时候触发事件:
1 2 3 4 5 6 import emitter from "@/utils/emitter" ;function sendToy ( ) { emitter.emit ('send-toy' , toy.value ) }
注意这个重要的内置关系,总线依赖着这个内置关系
8.4 v-model 概述:实现 父 <===> 子 之间相互通信。
前序知识 —— v-model
的本质:
1 2 3 4 5 6 7 8 9 <input type ="text" v-model ="userName" > <input type ="text" :value ="userName" @input ="userName =(<HTMLInputElement>$event.target).value" >
组件标签上的v-model
的本质::moldeValue
+ update:modelValue
事件。
1 2 3 4 5 <CustomInput v-model ="userName" /> <CustomInput :modelValue ="userName" @update:model-value ="userName = $event" />
CustomInput
组件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <div > <input type ="text" :value ="modelValue" @input ="emit('update:model-value',$event.target.value)" > </div > </template > <script setup lang ="ts" > defineProps (['modelValue' ]) const emit = defineEmits (['update:model-value' ]) </script >
也可以更换value
,例如改成abc
:
1 2 3 4 5 <CustomInput v-model:abc ="userName" /> <CustomInput :abc ="userName" @update:abc ="userName = $event" />
CustomInput
组件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template > <div > <input type ="text" :value ="abc" @input ="emit('update:abc',$event.target.value)" > </div > </template > <script setup lang ="ts" > defineProps (['abc' ]) const emit = defineEmits (['update:abc' ]) </script >
如果value
可以更换,那么就可以在组件标签上多次使用v-model
:
1 <CustomInput v-model:abc ="userName" v-model:xyz ="password" />
8.5 $attrs 概述:$attrs
用于实现当前组件的父组件 ,向当前组件的子组件 通信(祖 ===> 孙 )。
具体说明:$attrs
是一个对象,包含所有父组件传入的标签属性。
PS:$attrs
会自动排除props
中声明的属性(可以认为声明过的 props
被子组件自己“消费”了)
父组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template > <div class ="father" > <h3 > 父组件</h3 > <h4 > a:{{ a }}</h4 > <h4 > b:{{ b }}</h4 > <h4 > c:{{ c }}</h4 > <h4 > d:{{ d }}</h4 > <Child :a ="a" :b ="b" :c ="c" :d ="d" v-bind ="{ x: 100, y: 200 }" :updateA ="updateA" /> </div > </template > <script setup lang ="ts" > import Child from './Child.vue' import { ref } from 'vue' let a = ref (1 )let b = ref (2 )let c = ref (3 )let d = ref (4 )function updateA (value: number ) { a.value += value } </script >
子组件:
1 2 3 4 5 6 7 8 9 10 <template > <div class ="child" > <h3 > 子组件</h3 > <GrandChild v-bind ="$attrs" /> </div > </template > <script setup lang ="ts" > import GrandChild from './GrandChild.vue' </script >
孙组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template > <div class ="grand-child" > <h3 > 孙组件</h3 > <h4 > a:{{ a }}</h4 > <h4 > b:{{ b }}</h4 > <h4 > c:{{ c }}</h4 > <h4 > d:{{ d }}</h4 > <h4 > x:{{ x }}</h4 > <h4 > y:{{ y }}</h4 > <button @click ="updateA(6)" > 点我将爷爷那的a更新</button > </div > </template > <script setup lang ="ts" > defineProps (['a' , 'b' , 'c' , 'd' , 'x' , 'y' , 'updateA' ])</script >
8.6 $refs、$parent 概述:
$refs
用于 :父 ===> 子。
$parent
用于:子 ===> 父。
原理如下:
属性
说明
$refs
值为对象,包含所有被ref
属性标识的DOM
元素或组件实例。
$parent
值为对象,当前组件的父组件实例对象。
8.7 provide、inject 概述:实现祖孙组件 直接通信。
具体使用:
在祖先组件中通过provide
配置向后代组件提供数据。
在后代组件中通过inject
配置来声明接收数据。
具体编码:
1、父组件中,使用provide
提供数据
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 > <div class ="father" > <h3 > 父组件</h3 > <h4 > 银子:{{ money }}万元</h4 > <h4 > 车子:一辆{{ car.brand }}车,价值{{ car.price }}万元</h4 > <Child /> </div > </template > <script setup lang ="ts" > import Child from './Child.vue' import { ref, reactive, provide } from 'vue' let money = ref (100 )let car = reactive ({ brand : '奔驰' , price : 100 }) function updateMoney (value: number ) { money.value -= value } provide ('moneyContext' , { money, updateMoney })provide ('car' , car)</script >
PS:子组件中不用编写任何东西,是不受到任何打扰的。
2、孙组件中使用inject
配置项接受数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template > <div class ="grand-child" > <h3 > 我是孙组件</h3 > <h4 > 银子:{{ money }}</h4 > <h4 > 车子:一辆{{ car.brand }}车,价值{{ car.price }}万元</h4 > <button @click ="updateMoney(6)" > 花爷爷的钱</button > </div > </template > <script setup lang ="ts" > import { inject } from "vue" ;let { money, updateMoney } = inject ('moneyContext' , { money : 0 , updateMoney : (param: number ) => { } })let car = inject ('car' , { brand : '未知' , price : 0 })</script >
8.8 pinia 参考之前pinia
部分的讲解。
8.9 slot 8.9.1 默认插槽
父组件:
1 2 3 4 5 <Category title ="热门游戏列表" > <ul > <li v-for ="g in games" :key ="g.id" > {{ g.name }}</li > </ul > </Category >
子组件:
1 2 3 4 5 6 7 <template > <div > <h3 > {{ title }}</h3 > <slot > 默认内容</slot > </div > </template >
8.9.2 具名插槽 PS:插槽有默认名字default(v-slot:default)
父组件:
1 2 3 4 5 6 7 8 9 10 11 <Category > <template v-slot:s2 > <ul > <li v-for ="g in games" :key ="g.id" > {{ g.name }}</li > </ul > </template > <template #s1 > <h2 > 热门游戏列表</h2 > </template > </Category >
子组件:
1 2 3 4 5 6 <template > <div > <slot name ="s1" > </slot > <slot name ="s2" > </slot > </div > </template >
8.9.3 作用域插槽 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。 (新闻数据在News
组件中,但使用数据所遍历出来的结构由App
组件决定),具体编码:
父组件:
1 2 3 4 5 6 7 8 9 <Game > <template v-slot ="params" > <ul > <li v-for ="g in params.youxi" :key ="g.id" > {{ g.name }}</li > </ul > </template > </Game >
子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template > <div class ="category" > <h2 > 今日游戏榜单</h2 > <slot :youxi ="games" a ="哈哈" > </slot > </div > </template > <script setup lang ="ts" > import {reactive} from 'vue' let games = reactive ([ {id :'asgdytsa01' ,name :'英雄联盟' }, {id :'asgdytsa02' ,name :'王者荣耀' }, {id :'asgdytsa03' ,name :'红色警戒' }, {id :'asgdytsa04' ,name :'斗罗大陆' } ]) </script >
类比:
数据在子那边,但数据生成的结构,却由父亲决定。
压岁钱在孩子那里,但根据压岁钱买到东西,却由父亲决定。
9. 其它 API 9.1 shallowRef 与 shallowReactive
shallowRef
作用:创建一个响应式数据,但只对顶层属性进行响应式处理。
用法:
1 let myVar = shallowRef (initialValue);
特点:只跟踪引用值的变化,不关心值内部的属性变化。
shallowReactive
作用:创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成响应式的。
用法:
1 const myObj = shallowReactive ({ ... });
特点:对象的顶层属性是响应式的,但嵌套对象的属性不是。
总结
通过使用 shallowRef()
和 shallowReactive()
来绕开深度响应。浅层式 API
创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提升性能。
9.2 readonly 与 shallowReadonly
readonly
作用:用于创建一个对象的深只读副本。
用法:
1 2 const original = reactive ({ ... });const readOnlyCopy = readonly (original);
特点:
对象的所有嵌套属性都将变为只读。
任何尝试修改这个对象的操作都会被阻止(在开发模式下,还会在控制台中发出警告)。
应用场景:
创建不可变的状态快照。
保护全局状态或配置不被修改。
PS:original 可以修改,由于 readOnlyCopy 是根据 original 生成的,所以 readOnlyCopy 会随着 original 的修改而变化,但是不能直接修改 readOnlyCopy。
shallowReadonly
作用:与 readonly
类似,但只作用于对象的顶层属性。
用法:
1 2 const original = reactive ({ ... });const shallowReadOnlyCopy = shallowReadonly (original);
特点:
9.3 toRaw 与 markRaw
toRaw
作用:用于获取一个响应式对象的原始对象, toRaw
返回的对象不再是响应式的,不会触发视图更新。
官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
何时使用? —— 在需要将响应式对象传递给非 Vue
的库或外部系统时,使用 toRaw
可以确保它们收到的是普通对象。
具体编码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import { reactive, toRaw, markRaw, isReactive } from "vue" ;let person = reactive ({ name : 'tony' , age : 18 })let rawPerson = toRaw (person)let citys = markRaw ([ { id : 'asdda01' , name : '北京' }, { id : 'asdda02' , name : '上海' }, { id : 'asdda03' , name : '天津' }, { id : 'asdda04' , name : '重庆' } ]) let citys2 = reactive (citys)console .log (isReactive (person)) console .log (isReactive (rawPerson)) console .log (isReactive (citys)) console .log (isReactive (citys2))
markRaw
作用:标记一个对象,使其永远不会 变成响应式的。
例如使用 mockjs
时,为了防止误把 mockjs
变为响应式对象,可以使用 markRaw
去标记 mockjs
,编码:
1 2 3 4 5 6 7 8 9 let citys = markRaw ([ { id : 'asdda01' , name : '北京' }, { id : 'asdda02' , name : '上海' }, { id : 'asdda03' , name : '天津' }, { id : 'asdda04' , name : '重庆' } ]) let citys2 = reactive (citys)
9.4 customRef 作用:创建一个自定义的ref
,并对其依赖项跟踪和更新触发进行逻辑控制。
实现防抖效果(useSumRef.ts
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { customRef } from "vue" ;export default function (initValue: string , delay: number ) { let msg = customRef ((track, trigger ) => { let timer : number return { get ( ) { track () return initValue }, set (value ) { clearTimeout (timer) timer = setTimeout (() => { initValue = value trigger () }, delay); } } }) return { msg } }
10. Vue3新组件 10.1 Teleport 什么是Teleport?—— Teleport 是一种能够将我们的组件html结构 移动到指定位置的技术。
1 2 3 4 5 6 7 <teleport to ='body' > <div class ="modal" v-show ="isShow" > <h2 > 我是一个弹窗</h2 > <p > 我是弹窗中的一些内容</p > <button @click ="isShow = false" > 关闭弹窗</button > </div > </teleport >
10.2 Suspense 等待异步组件时渲染一些额外内容,让应用有更好的用户体验
使用步骤:
异步引入组件
使用Suspense
包裹组件,并配置好default
与 fallback
1 2 import { defineAsyncComponent,Suspense } from "vue" ;const Child = defineAsyncComponent (()=> import ('./Child.vue' ))
1 2 3 4 5 6 7 8 9 10 11 12 13 <template > <div class ="app" > <h3 > 我是App组件</h3 > <Suspense > <template v-slot:default > <Child /> </template > <template v-slot:fallback > <h3 > 加载中.......</h3 > </template > </Suspense > </div > </template >
10.3 全局API转移到应用对象
app.component
app.config
app.directive
app.mount
app.unmount
app.use
10.4 其他
过渡类名 v-enter
修改为 v-enter-from
、过渡类名 v-leave
修改为 v-leave-from
。
keyCode
作为 v-on
修饰符的支持。
v-model
指令在组件上的使用已经被重新设计,替换掉了 v-bind.sync。
v-if
和 v-for
在同一个元素身上使用时的优先级发生了变化。
移除了$on
、$off
和 $once
实例方法。
移除了过滤器 filter
。
移除了$children
实例 propert
。
……
附录
连续解构赋值 + 重命名
1 2 3 4 <script setup lang ="ts" > let {data :{content :title}} = await axios.get ('https://api.uomg.com/api/rand.qinghua?format=json' ) </script >
reactive 定义的响应式对象 在访问其内部属性时,自动解包
即 reactive
中定义的 ref
在访问其内部属性时不需要 .value
可以访问。例:
1 2 3 4 5 6 7 8 9 10 11 12 13 <script setup lang ="ts" > let obj = reactive ({ a : 1 , b : ref (2 ) }) let x = ref (9 ) console .log (obj.a ) console .log (obj.b ) console .log (x.value ) </script >
打印:
📢哔哔一下
以前也马马哈哈的接触过一丢丢Vue,只可惜知识体系过于零碎,对Vue的掌握简直就是依托答辩,甚至有些语法分不清是Vue2还是Vue3😅。通过这次的系统学习,自我感觉自己对Vue也没有那么晕乎乎了,但是这几天不造为🐎脑瓜子有点疼,可能是因为熬夜了吧😫…所以今天就浅浅滴自律一下吧,今天我一定要早睡!!! —— 2024/04/28 22:35