2024年最新版Vue3学习笔记

news2024/11/15 19:49:43

    本篇文章是记录来自尚硅谷禹神2023年课程的学习笔记,不得不说禹神讲的是真的超级棒!
在这里插入图片描述

文章目录

  • 创建Vue3工程
    • main.ts文件解析
    • 初始化项目
      • 写一个简单的效果
  • Vue3核心语法
    • setup函数
      • setup和选项式的区别
      • setup语法糖
      • 指定组件名称
    • 响应式数据
      • ref函数
        • 定义基本类型
        • 定义对象类型
      • reactive函数
      • ref和reactive的区别
      • toRef和toRefs
    • 计算属性Computed
      • 计算属性的get和set
    • 监听属性watch
      • 监视ref定义的基本类型
      • 监视ref定义的对象类型
      • 监听reactive定义的对象类型
      • 监听响应式对象的中的某个属性
      • 监听多个数据
    • watchEffect
      • 标签中的ref属性
    • 生命周期
    • 自定义hooks
  • 路由
    • 路由工作模式
    • to的两种写法
      • 命名路由
      • 嵌套路由
    • 路由传参
      • query参数
      • params参数
    • 路由的props
      • params参数作为props
      • 自定义props
        • 函数写法
        • 对象写法
      • replace属性
    • 编程式导航
      • route和router
      • 重定向
  • Pinia状态管理
    • 搭建pinia环境
    • 存储数据
    • 修改数据
      • storeToRefs
    • getters
    • 侦听state
    • store组合式写法
  • 组件通信
  • 插槽
    • 默认插槽
    • 具名插槽
    • 作用域插槽
  • 其他API
    • shallowRef与shallowReactive
      • shallowRef
      • shallowReactive
    • readonly和shallowReadonly
      • readonly
      • shallowReadonly
    • toRaw和markRaw
      • toRaw
      • markRaw
    • 自定义Ref
    • Teleport传送(?_?)
    • Suspense(?_?)
    • 全局API转移到应用对象

创建Vue3工程

基于vite创建,vite是一个新一代的前端构建工具。

优点:

  • 请求快速的热重载
  • 对ts、jsx支持开箱即用
  • 构建方式有改善
    • wepack构建流程
      请添加图片描述

    • vite构建流程
      请添加图片描述

创建步骤:

## 1.创建命令
npm create vue@latest

## 2.具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript?  Yes
## 是否添加JSX支持
√ Add JSX Support?  No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development?  No
## 是否添加pinia环境
√ Add Pinia for state management?  No
## 是否添加单元测试
√ Add Vitest for Unit Testing?  No
## 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
## 是否添加ESLint语法检查
√ Add ESLint for code quality?  Yes
## 是否添加Prettiert代码格式化
√ Add Prettier for code formatting?  No

安装插件:
请添加图片描述
目录分析:

  • Vite 项目中,index.html 是项目的入口文件,在项目最外层。
  • 加载index.html后,Vite 解析 <script type="module" src="xxx"> 指向的JavaScript
  • Vue3**中是通过 **createApp 函数创建一个应用实例。

main.ts文件解析

创建应用:import createApp from 'vue'

导入根组件:import App from ./App.vue

挂载组件:createApp(App).mount('#app')

初始化项目

编写一个App组件:

<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>

启动项目:npm run dev

写一个简单的效果

Vue3向下兼容Vue2语法,且Vue3中的模板中可以没有根标签。

新创建的组件:

<template>
  <div class="person">
    <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>

在App.vue中使用组件

<template>
  <div class="app">
    <h1>你好啊!</h1>
    <Person/>	<!--第三步,使用组件-->
  </div>
</template>

<script lang="ts">
  import Person from './components/Person.vue'	//第二步,导入组件
  export default {	//暴露
    name:'App', //组件名
    components:{Person}	//第一步,注册组件
  }
</script>

<style>...</style>

上面是使用Vue2的选项式(OPotionsAPI)写法来写的
请添加图片描述
Options类型的 API,数据、方法、计算属性等,是分散在:datamethodscomputed中的,若想新增或者修改一个需求,就需要分别修改:datamethodscomputed,不便于维护和复用。

组合式API的优势:可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。

Vue3核心语法

setup函数

语法模版:

<script lang="ts">
	export default{
    	name:'',
    	setup(){
        	//声明数据
        	let 变量名 = 变量值
        	//声明方法
          function 方法名(){
            	//方法体
          }
        return{变量名,方法名}// 返回一个对象,对象中的内容,模板中可以直接使用
      }
  }
</script>

例子:

<script lang="ts">
  export default {
    name:'Person',
    setup(){
      // 数据,原来写在data中(注意:此时的name、age、tel数据都不是响应式数据)
      let name = '张三'
      let age = 18
      let tel = '13888888888'

      // 方法,原来写在methods中
      function changeName(){
        name = 'zhang-san' //注意:此时这么修改name页面是不变化的
        console.log(name)
      }
      function changeAge(){
        age += 1 //注意:此时这么修改age页面是不变化的
        console.log(age)
      }
      function showTel(){
        alert(tel)
      }
      return {name,age,tel,changeName,changeAge,showTel}
    }
  }
</script>

⚠️注意:setup函数中的this是undefined,Vue3中已经弱化this了。

setup的返回值

  • 若返回的一个对象,这对象的属性、方法等,在模版中均可以直接使用。

  • 若返回的是一个函数,则可以自定义渲染内容,代码如下:

    setup(){
      return ()=> '你好啊!'	//这样会直接覆盖所在组件和样式
    }
    

setup和选项式的区别

  • Vue2 的配置(datamethos…)中可以访问到 setup中的属性、方法。
  • 但在setup不能访问到Vue2的配置(datamethos…)。
  • 如果与Vue2冲突,则setup优先。

setup语法糖

setup函数有一个语法糖,这个语法糖,可以让我们把setup独立出去。

语法模版:

<script lang="" setup>
	//写数据
  //写方法
  //不用写return了
</script>

请添加图片描述

错误写法❌:

在这里插入图片描述

例子:

<!-- 原本的写法 -->
<template>
  <div class="person">
    <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:'Person',
  }
</script>

<!-- 下面的写法是setup语法糖 -->
<script setup lang="ts">
  console.log(this) //undefined
  let name = '张三'
  let age = 18
  let tel = '13888888888'
  function changName(){
    name = '李四'
  }
  function changAge(){
    console.log(age)
    age += 1
  }
  function showTel(){
    alert(tel)
  }
</script>

指定组件名称

上述代码中,我们还需要编写一个不写setupscript标签,去注定组件名称。

<script lang="ts">
  export default {
    //需要编写一个不写`setup`的`script`标签,去注定组件名称
    name:'Person',
  }
</script>

<script setup lang="ts">
	...
</script>

这样就很麻烦,所以我们可以借助vite中的插件简化:

  1. 安装插件

    npm i vite-plugin-vue-setup-extend -D
    
  2. 配置vite.config.ts

    import { defineConfig } from 'vite'
    import VueSetupExtend from 'vite-plugin-vue-setup-extend'
    
    export default defineConfig({
      plugins: [ VueSetupExtend() ]
    })xxxxxxxxxx import { defineConfig } from 'vite'import VueSetupExtend from 'vite-plugin-vue-setup-extend'export default defineConfig({  plugins: [ VueSetupExtend() ]})
    
  3. 使用插件

    //在原来的语法糖中使用
    <script setup lang="ts" name="Person">
    

响应式数据

ref函数

定义基本类型

作用:创建基本类型的响应式数据

语法:let 变量名 = ref(初始值)

使用方法:

1. 引入ref
import {ref} from 'vue'

2. 使用ref
let 变量名 = ref(初始值)

3. 使用响应式数据:
	变量名.value = 变量值

返回值:是一个RefImpl的实例对象,简称ref对象refref对象的value属性是响应式的

注意⚠️:

  • JS中操作数据需要:xxx.value,但模板中不需要.value,直接使用即可。
  • 对于let name = ref('张三')来说,name不是响应式的,name.value是响应式的。

例子:

<template>
  <div class="person">
    <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" name="Person">
  import {ref} from 'vue'
  // name和age是一个RefImpl的实例对象,简称ref对象,它们的value属性是响应式的。
  let name = ref('张三')
  let age = ref(18)
  // tel就是一个普通的字符串,不是响应式的
  let tel = '13888888888'

  function changeName(){
    // JS中操作ref对象时候需要.value
    name.value = '李四'
    console.log(name.value)

    // 注意:name不是响应式的,name.value是响应式的,所以如下代码并不会引起页面的更新。
    // name = ref('zhang-san')
  }
  function changeAge(){
    // JS中操作ref对象时候需要.value
    age.value += 1 
    console.log(age.value)
  }
  function showTel(){
    alert(tel)
  }
</script>
定义对象类型

ref接收的数据可以是:基本类型、对象类型

如果ref接受的是对象类型,内部其实是调用了reactive函数。所以同样也是可以处理深层次

reactive函数

作用:定义一个响应式对象(reactive只能处理对象类型)

语法:let 对象名 = reactive(原对象)

使用方法:

1. 引入reactive
import {reactive} from 'vue'

2. 使用reactive
let 对象名 = reactive(原对象)

3. 替换整体对象
Object.assign(对象名,{属性名:属性值,...})

返回值:一个Proxy的实例对象,简称:响应式对象。

注意:reactive定义的响应式数据是“深层次”的。

ref和reactive的区别

宏观角度对比:

  1. ref用来定义:基本类型数据对象类型数据

  2. reactive用来定义:对象类型数据

细节对比:

  1. ref创建的变量必须使用.value(可以使用volar插件自动添加.value)。
    在这里插入图片描述

  2. reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。

使用原则:

  1. 若需要一个基本类型的响应式数据,必须使用ref
  2. 若需要一个响应式对象,层级不深,refreactive都可以。
  3. 若需要一个响应式对象,且层级较深,推荐使用reactive

toRef和toRefs

作用:将一个响应式对象中的每一个属性,转换为ref对象。toRefstoRef功能一致,但toRefs可以批量转换。

语法:

  • toRefs(对象名)
  • toRef(对象名,属性名)

使用方法:

1. 导入
 import {ref,reactive,toRefs,toRef} from 'vue'

2. 使用toRefs将解构的属性批量取出
let {属性值1,属性值2} = toRefs(对象名)
let 属性值 = toRef(对象名,属性名)

例子:

<template>
  <div class="person">
    <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 lang="ts" setup name="Person">
  import {ref,reactive,toRefs,toRef} from 'vue'

  // 数据
  let person = reactive({name:'张三', age:18, gender:'男'})
	
  // 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
  let {name,gender} =  toRefs(person)
	
  // 通过toRef将person对象中的gender属性取出,且依然保持响应式的能力
  let age = toRef(person,'age')

  // 方法
  function changeName(){
    name.value += '~'
  }
  function changeAge(){
    age.value += 1
  }
  function changeGender(){
    gender.value = '女'
  }
</script>

计算属性Computed

作用:Computed就是依赖的属性发生变化后会被重新计算,根据已有数据计算出新数据(和Vue2中的computed作用一致)。

回顾vue2的计算属性:

<script>
	export default{
 	computed:{
     	//配置项
   }
}
</script>

语法模版:

<script>
	import {computed} from 'vue'
  let 变量名 = computed(() =>{
    return 方法体
  })
</script>

计算属性的get和set

按照上面的语法模版,计算属性是只可读不可修改的,要想让计算属性即可读又可以写,就需要用到getset方法。

语法模版:

<script>
	import {computed} from 'vue'
  let 变量名 = computed(() =>{
    //读取
    get(){ }
    set(){ }
  })
</script>

监听属性watch

作用:监视数据的变化(和Vue2中的watch作用一致)。

特点:Vue3中的watch只能监视以下四种数据

  1. ref定义的数据。
  2. reactive定义的数据。
  3. 函数返回一个值(getter函数)。
  4. 一个包含上述内容的数组。

使用方法:

  1. 导入watch

    import {watch} from 'vue'
    
  2. 使用watch

    watch(监视对象,(newValue,oldValue)=>{
    	//回调函数
    }),{配置对象}
    
  3. 解除监视stopWatch()

    const stopWatch = watch(监视对象,(newValue,oldValue)=>{
    	//回调函数
    	if(newValue >= 10){
    		stopWatch()
    	}
    })
    

监视ref定义的基本类型

监视ref定义的【基本类型】数据:直接写数据名即可,监视的是其value值的改变。

监视ref定义的数据的时候不用写.value

例子:

<template>
  <div class="person">
    <h1>情况一:监视【ref】定义的【基本类型】数据</h1>
    <h2>当前求和为:{{sum}}</h2>
    <button @click="changeSum">点我sum+1</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref,watch} from 'vue'
  // 数据
  let sum = ref(0)
  // 方法
  function changeSum(){
    sum.value += 1
  }
  // 监视,情况一:监视【ref】定义的【基本类型】数据
  const stopWatch = watch(sum,(newValue,oldValue)=>{
    console.log('sum变化了',newValue,oldValue)
    if(newValue >= 10){
      stopWatch()
    }
  })
</script>

监视ref定义的对象类型

监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。

深度监视:

watch(监视对象,(newValue,oldValue)=>{
	//回调函数
}),{deep:true}

⚠️注意:

  • 若修改的是ref定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象。

  • 若修改整个ref定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。

例子:

<template>
  <div class="person">
    <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 lang="ts" setup name="Person">
  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}
  }
  /* 
    监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视
    watch的第一个参数是:被监视的数据
    watch的第二个参数是:监视的回调
    watch的第三个参数是:配置对象(deep、immediate等等.....) 
  */
  watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
  },{deep:true})
  
</script>

监听reactive定义的对象类型

监视reactive定义的【对象类型】数据,且默认开启了深度监视。(隐式的创建了深度监听)。

例子:

<template>
  <div class="person">
    <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 lang="ts" setup name="Person">
  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
  }

  // 监视,情况三:监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的
  watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
  })
  watch(obj,(newValue,oldValue)=>{
    console.log('Obj变化了',newValue,oldValue)
  })
</script>

监听响应式对象的中的某个属性

监视refreactive定义的【对象类型】数据中的某个属性,注意点如下:

  1. 基本类型的监听,需要写成函数形式
    在这里插入图片描述

  2. 对象类型的监听,可以直接.属性,但是最好写成函数形式。如果对象坚硬的是地址值,需要管制对象内部,需要手动开启深度监听。

    watch(()=>person.car,(newValue,oldValue)=>{
        console.log('person.car变化了',newValue,oldValue)
      },{deep:true})
    

例子:

<template>
  <div class="person">
    <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 lang="ts" setup name="Person">
  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,(newValue,oldValue)=>{
    console.log('person.name变化了',newValue,oldValue)
  }) */

  // 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
  
</script>

监听多个数据

直接使用一个数组包裹住[]

例子:

<template>
  <div class="person">
    <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 lang="ts" setup name="Person">
  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>

watchEffect

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。

watch对比watchEffect

  1. 都能监听响应式数据的变化,不同的是监听数据变化的方式不同

  2. watch:要明确指出监视的数据

  3. watchEffect:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。

例子:

<template>
  <div class="person">
    <h1>需求:水温达到50℃,或水位达到20cm,则联系服务器</h1>
    <h2 id="demo">水温:{{temp}}</h2>
    <h2>水位:{{height}}</h2>
    <button @click="changePrice">水温+1</button>
    <button @click="changeSum">水位+10</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref,watch,watchEffect} from 'vue'
  // 数据
  let temp = ref(0)
  let height = ref(0)

  // 方法
  function changePrice(){
    temp.value += 10
  }
  function changeSum(){
    height.value += 1
  }

  // 用watch实现,需要明确的指出要监视:temp、height
  watch([temp,height],(value)=>{
    // 从value中获取最新的temp值、height值
    const [newTemp,newHeight] = value
    // 室温达到50℃,或水位达到20cm,立刻联系服务器
    if(newTemp >= 50 || newHeight >= 20){
      console.log('联系服务器')
    }
  })

  // 用watchEffect实现,不用
  const stopWtach = watchEffect(()=>{
    // 室温达到50℃,或水位达到20cm,立刻联系服务器
    if(temp.value >= 50 || height.value >= 20){
      console.log(document.getElementById('demo')?.innerText)
      console.log('联系服务器')
    }
    // 水温达到100,或水位达到50,取消监视
    if(temp.value === 100 || height.value === 50){
      console.log('清理了')
      stopWtach()
    }
  })
</script>

标签中的ref属性

作用:用于注册模板引用。

特点:

  1. 用在普通DOM标签上,获取的是DOM节点。

    <template>
      <div class="person">
        <h1 ref="title1">尚硅谷</h1>
        <h2 ref="title2">前端</h2>
        <h3 ref="title3">Vue</h3>
        <input type="text" ref="inpt"> <br><br>
        <button @click="showLog">点我打印内容</button>
      </div>
    </template>
    
    <script lang="ts" setup name="Person">
      import {ref} from 'vue'
    	
      let title1 = ref()
      let title2 = ref()
      let title3 = ref()
    
      function showLog(){
        // 通过id获取元素
        const t1 = document.getElementById('title1')
        // 打印内容
        console.log((t1 as HTMLElement).innerText)
        console.log((<HTMLElement>t1).innerText)
        console.log(t1?.innerText)
        
    		/************************************/
    		
        // 通过ref获取元素
        console.log(title1.value)
        console.log(title2.value)
        console.log(title3.value)
      }
    </script>
    
  2. 用在组件标签上,获取的是组件实例对象。

    <!-- 父组件App.vue -->
    <template>
      <Person ref="ren"/>
      <button @click="test">测试</button>
    </template>
    
    <script lang="ts" setup name="App">
      import Person from './components/Person.vue'
      import {ref} from 'vue'
    
      let ren = ref()
    
      function test(){
        console.log(ren.value.name)
        console.log(ren.value.age)
      }
    </script>
    
    
    <!-- 子组件Person.vue中要使用defineExpose暴露内容 -->
    <script lang="ts" setup name="Person">
      import {ref,defineExpose} from 'vue'
    	// 数据
      let name = ref('张三')
      let age = ref(18)
      /****************************/
      // 使用defineExpose将组件中的数据交给外部
      defineExpose({name,age})
    </script>
    

生命周期

概念:Vue组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子。

时刻:生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。

vue2的生命周期:

  • 创建阶段:beforeCreate(创建前)、created(创建完毕)

  • 挂载阶段:beforeMount(挂载前)、mounted(挂载完毕)

  • 更新阶段:beforeUpdate(更新前)、updated(更新完毕)

  • 销毁阶段:beforeDestroy(销毁前)、destroyed(销毁完毕)

vue2 中的生命周期函数卸载methods方法中。

vue3的生命周期:

  • 创建阶段:setup

  • 挂载阶段:onBeforeMountonMounted

  • 更新阶段:onBeforeUpdateonUpdated

  • 卸载阶段:onBeforeUnmountonUnmounted

使用方法:

1. 导入
	import {生命周期函数} from 'vue'
2. 使用
	生命周期函数(()=>{
		//方法体
	})

例子:

<template>
  <div class="person">
    <h2>当前求和为:{{ sum }}</h2>
    <button @click="changeSum">点我sum+1</button>
  </div>
</template>

<!-- vue3写法 -->
<script lang="ts" setup name="Person">
  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>

自定义hooks

属于是一个模块化开发,本质是一个函数,把setup函数中使用的Composition API进行了封装,类似于vue2.x中的mixin

自定义hook的优势:复用代码, 让setup中的逻辑更清楚易懂。

例子:

  • useSum.ts中内容如下:

    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中内容如下:

    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}
    }
    
  • 组件中具体使用:

    <template>
      <h2>当前求和为:{{sum}}</h2>
      <button @click="increment">点我+1</button>
      <button @click="decrement">点我-1</button>
      <hr>
      <img v-for="(u,index) in dogList.urlList" :key="index" :src="(u as string)"> 
      <span v-show="dogList.isLoading">加载中......</span><br>
      <button @click="getDog">再来一只狗</button>
    </template>
    
    <script lang="ts">
      import {defineComponent} from 'vue'
    
      export default defineComponent({
        name:'App',
      })
    </script>
    
    <script setup lang="ts">
      import useSum from './hooks/useSum'
      import useDog from './hooks/useDog'
    	
      let {sum,increment,decrement} = useSum()
      let {dogList,getDog} = useDog()
    </script>
    

路由

  1. 安装Vue-router

    npm i vue-router
    
  2. src下新建一个router文件夹在里面书写路由代码

  3. 创建路由器,并暴露出去

    //第一步:引入creatRouter
    import {createRouter,createWebHistory} from 'vue-router'
    
    //第三步:引入可能呈现的组件
    import Home from '@/.vue路径'
    
    //第二步:创建路由器
    const router = createRouter({
    history:createWebHistory(),//指定工作模式
    	routes:[
    		{
    			path:'/home',
    			component:Home
    		},
    	]
    })
    
    //第四步:暴露路由
    export default router
    
  4. main.js中配置路由器

    // 引入createApp用于创建应用
    import {createApp} from 'vue'
    // 引入App根组件
    import App from './App.vue'
    
    //第一步:引入路由器
    import router from './router'
    
    //创建一个应用
    const app = createApp(App)
    
    //第二步:使用路由器
    app.use(router)
    
    //挂载整个应用到app容器中
    app.mount('#app')
    
  5. App.vue展示

    <template>
      	<RouterView</RouterView>
    </template>
    <script>
      //第一步导入路由
      	import {RouterLink,RouterView} from 'vue-router'  
    </script>
    
  6. 实现路由跳转

    <template>
      <!-- 导航区 -->
      <RouterLink to="/文件路径" >首页</RouterLink>
      <!-- 展示区 -->
      	<RouterView</RouterView>
    </template>
    <script>
      //第一步导入路由
      	import {RouterLink,RouterView} from 'vue-router'  
    </script>
    
  7. 使用active-class,实现点击切换class

    <RouterLink to="/home" active-class="active">首页</RouterLink>
    

注意⚠️:

  1. 路由组件通常存放在pagesviews文件夹,一般组件通常存放在components文件夹。

  2. 通过点击导航,视觉效果上“消失” 了的路由组件,默认是被卸载掉的,需要的时候再去挂载

路由工作模式

  1. history模式

    • 优点:URL更加美观,不带有#,更接近传统的网站URL

    • 缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404错误。

      const router = createRouter({
      	history:createWebHistory(), //history模式
      	/******/
      })
      
  2. hash模式

    • 优点:兼容性更好,因为不需要服务器端处理路径。

    • 缺点:URL带有#不太美观,且在SEO优化方面相对较差。

      const router = createRouter({
      	history:createWebHashHistory(), //hash模式
      	/******/
      })
      

to的两种写法

  1. 字符串写法:

    <!-- 第一种:to的字符串写法 -->
    <router-link active-class="active" to="/home">主页</router-link>
    
  2. 对象写法:

    <!-- 第二种:to的对象写法 -->
    <router-link active-class="active" :to="{path:'/path路径 或者 名字跳转'}">Home</router-link>
    

命名路由

作用:可以简化路由跳转及传参(后面就讲)。

给路由规则命名:

routes:[
  {
    name:'zhuye',
    path:'/home',
    component:Home
  },
]

嵌套路由

写法注意:

  • 子集不用写/

使用方法:

  1. 配置路由规则,使用children配置

    {
      name:'',
      path:'/home',
      component:Home,
      children:[
        {
        path:'子路由',	//子集不用写`/`
        component:子路由
        }
    	]
    }
    
  2. 跳转路由

    <router-link to="/父级路由/子集路由">xxxx</router-link>
    <!-- 或 -->
    <router-link to="{path:'/父级路由/子集路由'}">xxxx</router-link>
    

路由传参

query参数

语法:

<router-link to="/父级路由/子集路由?后面传递query参数">xxxx</router-link>

使用方法:

为了让query参数传递的是一个动态的值,我们需要用到js的模版字符串,来动态获取参数、

  1. to的前面加一个冒号,是该句话变成表达式

    <router-link :to="/父级路由/子集路由?后面传递query参数">xxxx</router-link>
    
  2. 使用反引号`` 将to的值引起来,然后在里面使用模版字符串${}

    <router-link :to="`/父级路由/子集路由?id=${获取id的值.id}`">xxxx</router-link>
    
  3. 如果后面还有参数可以使用&拼接

    <router-link to="/news/detail?a=1&b=2&content=欢迎你">
    	跳转
    </router-link>
    
  4. 精简写法,使用to的对象写法

    <RouterLink 
      :to="{
        //name:'xiang', //用name也可以跳转
        path:'/news/detail',
        query:{
          id:news.id,
          title:news.title,
          content:news.content
        }
      }"
    >{{news.title}}
    </RouterLink>
    

获取query参数的方法:

  1. 引入useRoute

    import {useRoute} from 'vue-router'
    
  2. 创建一个useRoute对象

    let route =useRoute()
    
  3. 获取query参数

    route.query.属性名
    
  4. 还可以搭配toRefs解构出Route对象

    <template>
    	<p>
        query.属性名	<!-- 第三步:去掉route -->
      </p>
    </template>
    
    <script>
      import {useRoute} from 'vue-router'
    	import {toRefs} from 'vue'	//第一步:导入toRefs
      
      let route =useRoute()
      let {query} =  toRefs(route)	//第二步:搭配`toRefs`解构出Route对象
    </script>
    

params参数

语法:

<router-link to="/父级路由/子集路由/后面传递params参数1/params参数2/...">xxxx</router-link>

使用方法:

  1. 传递参数

    <!-- 跳转并携带params参数(to的字符串写法) -->
    <RouterLink :to="`/news/detail/001/新闻001/内容001`">{{news.title}}</RouterLink>
    
    <!-- 跳转并携带params参数(to的对象写法) -->
    <RouterLink 
      :to="{
        name:'xiang', //用name跳转,只能用name,不能用path
        params:{
          id:news.id,
          title:news.title,
          content:news.title
        }
      }"
    >
      {{news.title}}
    </RouterLink>
    
  2. 配置路由

    在这里插入图片描述

  3. 接收参数

    import {useRoute} from 'vue-router'
    const route = useRoute()
    // 打印params参数
    console.log(route.params)
    

注意⚠️:

  1. 传递params参数时,若使用to的对象写法,必须使用name配置项,不能用path

  2. 传递params参数时,需要提前在规则中占位。

  3. params传参不能传递对象和数组。
    在这里插入图片描述

  1. 如果有些参数可传可不传,则需要在路由规则中的占位后面加一个
    在这里插入图片描述

路由的props

params参数作为props

作用:让路由组件更方便的收到参数,可以将路由收到得到所有params参数作为props传给路由组件。

使用方法:直接在路由规则后面添加一个props;true

children:[
  {
    path:'子路由',	//子集不用写`/`
    component:子路由,
		props:true,		
  }
]

例子:

在这里插入图片描述
直接使用:

在这里插入图片描述

自定义props

自定义写法就可以指定是使用params参数还是query参数。

函数写法

作用:把返回的对象中每一组key-value作为props传给路由组件

语法:

props(route){
	return Route.query
}

例子:

在这里插入图片描述

对象写法

作用:把对象中的每一组key-value作为props传给路由组件

props:{
	a:100,
	b:200,
	...
}

不推荐❌这种写法,因为会把数据写死。

replace属性

作用:控制路由跳转时操作浏览器历史记录的模式。

语法:在RouterLink标签中添加replace属性即可。

<RouterLink replace .......>News</RouterLink>

浏览器的历史记录有两种写入方式:分别为pushreplace

  • push是追加历史记录(默认值)。
  • replace是替换当前记录。

编程式导航

知识引入:在上面的页面中,都是使用<RouterLink>来实现页面跳转的,但是<RouterLink>是vue里面的标签,并不是浏览器中所谓的html标签,所以浏览器无法识别,只有通过vue最终将<RouterLink>转化为a标签。所以如果你只会使用<RouterLink>来实现跳转,那么就说明你的页面全是a标签,但是这样就会限制一些功能,导致一些需求无法实现。所以这就引出了我们的编程式导航来实现。

简单来说就是脱离<RouterLink>实现跳转。

使用方法:

  1. 导入useRouter

    import {useRouter} from 'vue-router'
    
  2. 调用路由器

    const router = useRouter()
    
  3. 实现跳转

    router.push('/路径')
    <!--可以是push跳转或者replace跳转-->
    

例子:实现一个定时器跳转
在这里插入图片描述

router.push()括号内的参数与<RouterLink>中的to写法一样:

  1. 字符串写法
  2. 对象写法

route和router

路由组件的两个重要的属性:$route$router变成了两个hooks

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)

重定向

作用:将特定的路径,重新定向到已有路由。

语法:

{
    path:'/',
    redirect:'/路径'
}

Pinia状态管理

多个组件共享数据,才使用到pinia。

搭建pinia环境

搭建方法:

  1. 安装pinia环境

    npm install pinia
    
  2. 引入pinia

    import { createApp } from 'vue'
    import App from './App.vue'
    
    /* 第一步:引入createPinia,用于创建pinia */
    import { createPinia } from 'pinia'
    
    /* 第二步:创建pinia */
    const pinia = createPinia()
    const app = createApp(App)
    
    /* 第三步:使用插件 */{}
    app.use(pinia)
    app.mount('#app')
    

存储数据

Store是一个保存:状态业务逻辑 的实体,每个组件都可以读取写入它。

它有三个概念:stategetteraction,相当于组件中的: datacomputedmethods

使用方法:

  1. 新建一个store文件夹,用来存储数据

  2. 引入defineStore用于创建store

    import {defineStore} from 'pinia'
    
    // 定义并暴露一个store
    export const useCountStore = defineStore('count',{
      // 动作
      actions:{},
      // 状态
      state(){
        return {
          sum:6
        }
      },
      // 计算
      getters:{}
    })
    
  3. 组件中使用state数据

    <template>
      <h2>当前求和为:{{ sumStore.sum }}</h2>
    </template>
    
    <script setup lang="ts" name="Count">
      // 引入对应的useXxxxxStore	
      import {useSumStore} from '@/store/sum'
      
      // 调用useXxxxxStore得到对应的store
      const sumStore = useSumStore()
    </script>
    

修改数据

第一种方法:直接修改法

countStore.sum = 666

第二种方法:批量修改法

countStore.$patch({
  sum:999,
  school:'atguigu'
})

第三种方式:借助action修改,在action当中可以编写一些业务逻辑

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  // actions里面放置的是一个一个的方法,用于响应组件中的“动作”
  /*
  actions: {
    increment(){
      	console.log("increment被调用了",value)
      	*/
    }
    //加
    increment(value:number) {
      if (this.sum < 10) {
        //操作countStore中的sum,this是当前的store
        this.sum += value
      }
    },
    //减
    decrement(value:number){
      if(this.sum > 1){
        this.sum -= value
      }
    }
  },
  /*************/
})

然后再组件中调用action即可

// 使用countStore
const countStore = useCountStore()

// 调用对应action
countStore.incrementOdd(n.value)

storeToRefs

借助storeToRefsstore中的数据转为ref对象,方便在模板中使用。

注意:pinia提供的storeToRefs只会将数据做转换,而VuetoRefs会转换store中数据。

<template>
	<div class="count">
		<h2>当前求和为:{{sum}}</h2>
	</div>
</template>

<script setup lang="ts" name="Count">
  import { useCountStore } from '@/store/count'
  /* 引入storeToRefs */
  import { storeToRefs } from 'pinia'

	/* 得到countStore */
  const countStore = useCountStore()
  /* 使用storeToRefs转换countStore,随后解构 */
  const {sum} = storeToRefs(countStore)
</script>

getters

概念:当state中的数据,需要经过处理后再使用时,可以使用getters配置。

使用方法:

  1. 追加getters配置

    // 引入defineStore用于创建store
    import {defineStore} from 'pinia'
    
    // 定义并暴露一个store
    export const useCountStore = defineStore('count',{
      // 动作
      actions:{
        /************/
      },
      // 状态
      state(){
        return {
          sum:1,
          school:'atguigu'
        }
      },
      // 计算
      getters:{
        bigSum:(state):number => state.sum *10,
        upperSchool():string{
          return this. school.toUpperCase()
        }
      }
    })
    
  2. 组件中读取数据

    const {increment,decrement} = countStore
    let {sum,school,bigSum,upperSchool} = storeToRefs(countStore)
    

侦听state

通过store的$subscribe()方法侦听state及其变化。

语法:

store.$subsctibe((mutate,state)=>{
	// 方法体
})
  • mutate:本次修改的信息
  • state:真正的数据

应用场景:

可以用来保存localStorage

talkStore.$subscribe((mutate,state)=>{
	localStorage.setItem('talk',JSON.stringify(talkList.value))
})

需要对store中的数据进行修改

原来:

在这里插入图片描述
修改后:
在这里插入图片描述

解决一开始没有数据的问题:
在这里插入图片描述

store组合式写法

先来看见选项式写法:

在这里插入图片描述

组合式写法:

import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'
import {reactive} from 'vue'

export const useTalkStore = defineStore('talk',()=>{
  // talkList就是state
  const talkList = reactive(
    JSON.parse(localStorage.getItem('talkList') as string) || []
  )

  // getATalk函数相当于action
  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}
})

组件通信

Vue3组件通信和Vue2的区别:

  • 移出事件总线,使用mitt代替。
  • vuex换成了pinia
  • .sync优化到了v-model里面了。
  • $listeners所有的东西,合并到$attrs中了。
  • $children被砍掉了。

常见使用方法:

在这里插入图片描述

props

概述:props是使用频率最高的一种通信方式,常用与 :父 ↔ 子

  • 父传子:属性值是非函数
  • 子传父:属性值是函数

父传子案例:

在这里插入图片描述

子传父案例:

在这里插入图片描述

自定义事件

概述:自定义事件常用于:子 => 父。

使用方法:

  1. 在父组件中给子组件绑定自定义事件

    <Child @send-toy="toy = $event"/>
    
  2. 子组件声明事件

    const emit = defineEmits(['send-toy'])
    
  3. 子组件中,触发事件

区分原生事件和自定义事件:

  • 原生事件:
    • 事件名是特定的(clickmosueenter等)
    • 事件嗲了$event:是包含事件相关的信息对象(pageXpageYtargetkeyCode
  • 自定义事件:
    • 事件名是任意名称
    • 事件对象$event:是调用emit的时候所提供的数据,可以是任意类型。

案例:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

事件名命名规范:

官方推荐使用肉串命名法来命名事件名。

mitt

概述:与消息订阅与发布(pubsub)功能类似,可以实现任意组件间通信。

配置方法:

  1. 安装mitt

    npm i mitt
    
  2. 新建文件:src\utils\emitter.ts,用来存放mitt文件

  3. 编写mitt模版

    // 引入mitt 
    import mitt from "mitt";
    // 创建emitter
    const emitter = mitt()
    // 创建并暴露mitt
    export default emitter
    
    精简写法:
    import mitt from "mitt";
    export default = mitt()
    
  4. main.js中引入emitter

    import emitter from '@/路径'
    

使用方法:

  1. 提供数据的组件,在合适的时候触发事件。
    在这里插入图片描述

  2. 在组件卸载的时候最后解绑一下mitt事件,这样做的目的是减小内存。

    onUnmounted(()=>{
    	emitter.off('事件名')
    })
    

mitt绑定事件

语法:

emitter.on('事件名',()=>{
	//方法体
})

mitt触发事件

语法:

emitter.emit('事件名')

mitt解绑事件

语法:

emitter.off('事件名')
//全部解绑
emitter.all.clear()

$attrs

概述:$attrs是一个对象,包含所有父组件传入的标签属性,$attrs用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。

使用方法:

在这里插入图片描述

r e f s 、 refs、 refsparent

概述:

  • $refs:值为对象,包含所有被ref属性标识的DOM元素或组件实例。【用于 :父→子
  • $parent:值为对象,当前组件的父组件实例对象。【用于:子→父**】

祖孙通信——provide、inject

概述:实现祖孙组件直接通信。

使用方法:

  1. 在祖先组件中插入provide,向后代提供数据

    import {provide} from 'vue';	//导入provide
    provide('变量标识名',变量)	//一般来说变量标识名和变量名相同
    
  2. 在后代组件中是通过inject配置来声明接收数据

    import {inject} from 'vue';	//导入inject
    let 接收变量名 = inject('变量标识名',吗,默认值)	//一般来说接收变量名和变量标识名相同
    

v-model (了解)

(?_?)这一部分没看懂。

概述:实现 父↔子 之间相互通信,经常用自定义UI组件库。

  1. 前序知识 —— v-model的本质

    <!-- 使用v-model指令 -->
    <input type="text" v-model="userName">
    
    <!-- v-model的本质是下面这行代码 -->
    <input 
      type="text" 
      :value="userName" 
      @input="userName =(<HTMLInputElement>$event.target).value"
    >
    
  2. 组件标签上的v-model的本质::moldeValueupdate:modelValue事件。

    <!-- 组件标签上使用v-model指令 -->
    <AtguiguInput v-model="userName"/>
    
    <!-- 组件标签上v-model的本质 -->
    <AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/>
    

    AtguiguInput组件中:

    <template>
      <div class="box">
        <!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 -->
    		<!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件-->
        <input 
           type="text" 
           :value="modelValue" 
           @input="emit('update:model-value',$event.target.value)"
        >
      </div>
    </template>
    
    <script setup lang="ts" name="AtguiguInput">
      // 接收props
      defineProps(['modelValue'])
      // 声明事件
      const emit = defineEmits(['update:model-value'])
    </script>
    
  3. 也可以更换value,例如改成abc

    <!-- 也可以更换value,例如改成abc-->
    <AtguiguInput v-model:abc="userName"/>
    
    <!-- 上面代码的本质如下 -->
    <AtguiguInput :abc="userName" @update:abc="userName = $event"/>
    

    AtguiguInput组件中:

    <template>
      <div class="box">
        <input 
           type="text" 
           :value="abc" 
           @input="emit('update:abc',$event.target.value)"
        >
      </div>
    </template>
    
    <script setup lang="ts" name="AtguiguInput">
      // 接收props
      defineProps(['abc'])
      // 声明事件
      const emit = defineEmits(['update:abc'])
    </script>
    
  4. 如果value可以更换,那么就可以在组件标签上多次使用v-model

    <AtguiguInput v-model:abc="userName" v-model:xyz="password"/>
    

(?_?)

$event到底是啥?啥时候能用.target

  • 对于原生事件,$event就是事件对象–>能用.target
  • 对于自定义事件,$event就是触发事件,所以传递的是数据–>不能.target

插槽

默认插槽

前置知识:
在这里插入图片描述

在这里插入图片描述

具名插槽

语法:

<组件名 v-slot:插槽名称>标签结构</组件名>

推荐写法:
<组件名>
  <template v-slot:插槽名称>标签结构</template>
</组件名>

使用:
<slot name="插槽名称">内容</slot>

语法糖:#代表v-slot:

作用域插槽

概述:数据在组件的自身,单根据数据生成的结构需要组件的使用者来决定。UI组件库大量使用作用域插槽来实现。

使用方法:

  1. 在子组件的插槽中传值给父组件

    <slot :传值="变量" ></slot>
    
  2. 父组件中接收插槽穿过来的数据

    <template v-slot="params">
    	//标签内容
    </template>
    

案例:

父组件中:
      <Game v-slot="params">
      <!--可以直接解构出来:<Game v-slot="{games}">-->
      <!-- <Game v-slot:default="params"> -->
      <!-- <Game #default="params"> -->
        <ul>
          <li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
          <!--解构出来使用:v-for="g in games"-->
        </ul>
      </Game>

子组件中:
      <template>
        <div class="category">
          <h2>今日游戏榜单</h2>
          <slot :games="games" a="哈哈"></slot>
        </div>
      </template>

      <script setup lang="ts" name="Category">
        import {reactive} from 'vue'
        let games = reactive([
          {id:'asgdytsa01',name:'英雄联盟'},
          {id:'asgdytsa02',name:'王者荣耀'},
          {id:'asgdytsa03',name:'红色警戒'},
          {id:'asgdytsa04',name:'斗罗大陆'}
        ])
      </script>

其他API

shallowRef与shallowReactive

shallowRef

作用:创建一个响应式数据,但只对顶层属性进行响应式处理。

语法:

let 变量名 = shallowRef()

特点:只跟踪引用值的变化,不关心值内部的属性变化。

案例:

在这里插入图片描述

shallowReactive

作用:创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成响应式的。

语法:

const myObj = shallowReactive({ ... });

特点:对象的顶层属性是响应式的,但嵌套对象的属性不是。比shallowRef性能比较好。

总结(摘自官方)

通过使用 shallowRef()shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提升性能。

readonly和shallowReadonly

readonly

作用:用于创建一个对象的深只读副本。

语法:readonly(变量名)

使用方法:

const original = reactive({ ... });
const readOnlyCopy = readonly(original);

特点:

  • 对象的所有的嵌套属性都将变为只读
  • 任何仓储处修改这个对象的操作都会被阻止(在开发模式下,还会在控制台发出警告)

应用场景:

  • 创建不可变的状态快照。
  • 保护全局状态或配置不被修改

shallowReadonly

作用:与readonly类似,但只有作用于对象的顶层属性。

语法:shallowReadonly()

使用方法:

const original = reactive({ ... });
const shallowReadOnlyCopy = shallowReadonly(original);

特点:

  • 只将对象的顶层属性设置为只读,对象内部的嵌套属性仍然可变。
  • 适用于只需保护对象顶层属性的场景。

toRaw和markRaw

toRaw

作用:用于获取一个响应式对象的原始对象, toRaw 返回的对象不再是响应式的,不会触发视图更新。

案例:

在这里插入图片描述

使用场景:

在需要将响应式对象传递给非 Vue 的库或外部系统时,使用 toRaw 可以确保它们收到的是普通对象。

在这里插入图片描述

markRaw

作用:标记一个对象,使其永远不会变成响应式。

语法:markRaw()

案例:

/* markRaw */
let citys = markRaw([
  {id:'asdda01',name:'北京'},
  {id:'asdda02',name:'上海'},
  {id:'asdda03',name:'天津'},
  {id:'asdda04',name:'重庆'}
])
// 根据原始对象citys去创建响应式对象citys2 —— 创建失败,因为citys被markRaw标记了
let citys2 = reactive(citys)

自定义Ref

引入:使用Vue提供的默认ref定义响应式数据,数据一变,页面就更新

作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制。

语法:customRef()

模版:

let msg = customRef((track,trigger)=>{
// track(跟踪)、trigger(触发)
    return{
		// get在被msg读取的时候调用
    get(){
			track()	//告诉Vue数据msg很重要,你要对msg进行持续关注,一旦msg变化就去更新
      return 返回值
    },
		// set在被msg修改的时候调用
    set(value){	//value是修改值
      //方法体
			trigger()	//通知Vue一下数据msg变化了
    }
  }
})

实现一个防抖效果Hooks

import {customRef } from "vue";
// initValue:初始时间
// delay:延迟时间
export default function(initValue:string,delay:number){
  let msg = customRef((track,trigger)=>{
    let timer:number
    return {
      get(){
        track() // 告诉Vue数据msg很重要,要对msg持续关注,一旦变化就更新
        return initValue
      },
      set(value){
        clearTimeout(timer)
        timer = setTimeout(() => {
          initValue = value
          trigger() //通知Vue数据msg变化了
        }, delay);
      }
    }
  }) 
  return {msg}
}

使用Hooks:

在这里插入图片描述

Teleport传送(?_?)

概念:Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

案例:

<teleport to='body' >
    <div class="modal" v-show="isShow">
      <h2>我是一个弹窗</h2>
      <p>我是弹窗中的一些内容</p>
      <button @click="isShow = false">关闭弹窗</button>
    </div>
</teleport>

Suspense(?_?)

等待异步组件时渲染一些额外内容,让应用有更好的用户体验。

使用步骤:

  • 异步引入组件
  • 使用Suspense包裹组件,并配置好defaultfallback

案例:

import { defineAsyncComponent,Suspense } from "vue";
const Child = defineAsyncComponent(()=>import('./Child.vue'))
<template>
    <div class="app">
        <h3>我是App组件</h3>
        <Suspense>
          <template v-slot:default>
            <Child/>
          </template>
          <template v-slot:fallback>
            <h3>加载中.......</h3>
          </template>
        </Suspense>
    </div>
</template>

全局API转移到应用对象

app.component:全局注册组件

请添加图片描述

app.config:全局配置对象

请添加图片描述

app.directive:注册全局指令

请添加图片描述

请添加图片描述

app.mount:挂载应用

app.unmount:卸载应用

app.use:安装插件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2138588.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#学习系列之Gmap地图界面上的实时绘制问题

C#学习系列之Gmap地图界面上的实时绘制问题 前言总结 前言 在地图控件上增加绘制不规则图形&#xff0c;在之前的经验来看&#xff0c; System.InvalidOperationException:“无法使用 DependencyObject&#xff0c;它属于其父 Freezable 之外的其他线程。” 其实就是ui线程中…

9.15javaweb项目总结

1.贴吧界面算是完成了基本的 能通过url打开多个贴吧信息的界面了&#xff0c;界面水平不是很高&#xff0c;界面还有待提升&#xff0c;然后该界面的功能点还差点有点远&#xff0c;完成度不是很高。 2.解决了关注的功能问题 要考虑的地方有点多&#xff0c;最简单的就是点击…

SpringSecurity 5

springSecurity是spring的一个顶级项目 也是一个安全框架&#xff0c;可以在spring框架中直接引用。 springSecurity基于RBAC用来处理登录功能和各种权限校验。 〇、配置和运行springSecurity 导入security启动器和web启动器&#xff0c;写一个springBoot的启动类&#xff0c;可…

VMamba: Visual State Space Model 论文总结

题目&#xff1a;VMamba: Visual State Space Model&#xff08;视觉状态空间模型&#xff09; 论文&#xff1a;[2401.10166] VMamba: Visual State Space Model (arxiv.org) 源码&#xff1a;https://arxiv.org/pdf/2401.10166 (github.com) 目录 一、摘要 二、引言 三、方…

URP 线性空间 ui资源制作规范

前言&#xff1a; 关于颜色空间的介绍&#xff0c;可参阅 unity 文档 Color space URP实现了基于物理的渲染&#xff0c;为了保证光照计算的准确&#xff0c;需要使用线性空间&#xff1b; 使用线性空间会带来一个问题&#xff0c;ui资源在unity中进行透明度混合时&#xff…

嵌入式常用轻量级校验算法

在嵌入式中涉及通信基本都需要用到校验算法&#xff0c;如&#xff1a;UART常用的奇偶校验、CAN通信常用的CRC校验等等。下面我们将介绍几种常用的校验算法&#xff1a; 一、校验和 校验和是最基本&#xff0c;也是嵌入式软件工程师最常用的一种校验算法&#xff0c;其实现方法…

jdk知识

jdk,jre,jvm jdk>jre>jvm jdk的bin目录下有编译工具&#xff0c;平时写完java文件用jdk编译&#xff1b; jre的lib文件夹里面是java的jar包(.class文件)&#xff0c;用来给jvm运行编译后的.class文件。 jvm&#xff1a;运行.class文件&#xff0c;.class文件读入到虚拟机…

JVM 垃圾回收机制和GC案例分析

1. 引言 Java 虚拟机&#xff08;JVM&#xff09;的垃圾回收&#xff08;Garbage Collection, GC&#xff09;机制&#xff0c;是自动内存管理的重要组成部分。它通过回收不再使用的对象&#xff0c;避免手动释放内存的麻烦。然而&#xff0c;随着系统复杂性的增加&#xff0c…

C/C++——野指针处理

在C++中,“野指针”(dangling pointer)指的是指向已释放或无效内存的指针。使用野指针可能导致程序崩溃或产生未定义行为。避免野指针的关键在于确保指针始终指向有效内存。下面是一些避免野指针的方法和最佳实践: 1、释放内存后置空指针 当释放掉分配的动态内存后,将指…

文本到3D生成

文本到3D生成是一种通过文本描述直接创建三维数字模型的技术。这种技术能够将语言描述转换成可视化的三维模型&#xff0c;使得内容创作者和设计师可以直接从概念阶段跳转到三维可视化&#xff0c;大大加快创作流程并提供更直观的设计和修改过程。 该技术的核心应用之一是基于…

【乐吾乐大屏可视化组态编辑器】使用手册

1 总览 开始设计&#xff1a;大屏可视化设计器 - 乐吾乐Le5le 1.1 画布 画布即绘画区域&#xff0c;将图形拖拽到画布进行编辑&#xff0c;绘制大屏。 1.2 菜单栏 顶部菜单导航&#xff0c;一级菜单可设置Logo、公司名称、文件编辑、常用编辑、查看、帮助&#xff0c;设置大…

MySQL篇(SQL - 分类)(持续更新迭代)

目录 简介 一、DDL 1. 简介 2. 数据库操作 2.1. 查库 查询所有数据库 查询当前数据库 2.2. 创库 2.3. 删库 2.4. 切库 3. 表操作 3.1. 查询 查询当前数据库所有表 查看指定表结构 查询指定表的建表语句 3.2. 创表 3.3. 改表 添加字段 修改数据类型 修改字段…

unity3d入门教程六

unity3d入门教程六 15.1预制体15.2编辑预制体15.3在场景中编辑15.4动态创建实例15.5实例的销毁16.1&#xff08;练习&#xff09;子弹发射16.2定时器16.3键盘事件 15.1预制体 火神山10天建成&#xff0c;使用了预制体技术 一个个小房间都是事先建造好的&#xff0c;最后吊车装…

Acrobat 2022 安装

软件介绍 Adobe Acrobat 是由Adobe公司开发的一款PDF&#xff08;Portable Document Format&#xff0c;便携式文档格式&#xff09;编辑软件。借助它&#xff0c;可以以PDF格式制作和保存文档&#xff0c;以便于浏览和打印&#xff0c;同时还可以使用一些高级工具来创建、编辑…

黑马十天精通MySQL知识点

一. MySQL概述 安装使用 MySQL安装完成之后&#xff0c;在系统启动时&#xff0c;会自动启动MySQL服务&#xff0c;无需手动启动。 也可以手动的通过指令启动停止&#xff0c;以管理员身份运行cmd&#xff0c;进入命令行执行如下指令&#xff1a; 1 、 net start mysql80…

Autosar E2E通信保护简介

文章目录 前言一、E2E基本概念二、为什么要做E2E?三、E2E保护的三种不同实现方式E2E TransformerE2E Protection Wrapper(E2EPW)COM E2E Callout四、E2E ProfileE2E Profile 01 机制E2E Profile 02 机制E2E Profile 04 机制E2E Profile 05 机制E2E Profile 06 机制E2E Profi…

828华为云征文|部署在线文件管理器 Spacedrive

828华为云征文&#xff5c;部署在线文件管理器 Spacedrive 一、Flexus云服务器X实例介绍1.1 云服务器介绍1.2 产品优势1.3 计费模式 二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置 三、部署 Spacedrive3.1 Spacedrive 介绍3.2 Docker 环境搭建3.3 Spac…

38900 机动车安全检测

参考文章&#xff1a;https://www.zhihu.com/question/458001000 机动车检查 《道路交通安全法实施条例》有关规定第十六条&#xff1a;机动车应当从注册登记之日起&#xff0c;按照下列期限进行安全技术检验&#xff1a; &#xff08;一&#xff09;营运载客汽车5年以内每年…

CSS 圆角渐变边框

<div class"contact-box"><div class"contact-item">联系我们</div> </div>.contact-item{width: 194px;height: 48px;border-radius: 20px 20px 20px 20px;background-color: #000000;color: #BDBDBD;font-weight: 500;font-size…

828华为云征文 | 华为云X实例服务器上部署知识图谱项目的详细指南

前言 知识图谱作为数据整合、语义分析和人工智能的重要基础&#xff0c;逐渐被广泛应用于各类领域。其通过结构化数据和关系映射&#xff0c;帮助用户更好地理解数据背后的意义。要成功构建和部署知识图谱项目&#xff0c;强大的计算资源和高效的存储查询能力至关重要。华为云X…