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