Vue3基础知识(语法糖版)

341 阅读22分钟

前言

该文章记录一些Vue3的一些基本知识,后续将会逐步增加,所有内容均从网上整理而来,加上自己得理解做一个整合,方便工作中使用。

一、VUE3简介

1. vue3优点

  • 打包体积减小
  • 渲染速度加快
  • 内存减少

2. 源码升级

  • 使用Proxy代替defineProperty实现响应式
  • 重写虚拟DOM的实现和Tree-Shaking
  • 支持TypeScript

3. 新特性

  1. Composition API(组合api)
    • setup 配置
    • ref与reactive
    • watch与watchEffect
    • provide与inject
  2. 新的内置组件
    • Fragment
    • Teleport
    • Suspense
  3. 其他改变
    • 新的生命周期钩子
    • data 选项应始终被声明为一个函数
    • 移除keyCode支持作为 v-on 的修饰符

4. vscode安装新插件

  1. Vue Languague Features (Volar)
  2. TypeScript Vue Plugin (Volar)

二、VUE3项目创建

1. vue-cli 创建 官方文档

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create <project-name>
## 启动
cd <project-name>
npm run serve

2.使用 vite 创建 官方文档

## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev

三、VUE3基础用法

1、setup函数

1-1、setup函数介绍

  • Vue3.0中一个新的配置项,值为一个函数,setup中this是undefined,setup函数会在beforeCreate之前调用,是所有生命周期函数之前执行。
  • 组件中所用到的:数据、方法等等,均要配置在setup中
  • setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
    2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)
  • 注意点:
    1. 不要与Vue2.x配置混用
      • Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法
      • 但在setup中不能访问到Vue2.x配置(data、methos、computed...)
      • 如果有重名, setup优先
    2. setup不能是一个async函数(有特殊情况可以:使用Suspense和异步加载组件配合使用)

1-2、普通使用setup函数

<template>
    <!-- vue3可以不用一个根目录包裹 -->
    <div>{{name}}</div>
    <button @click="sayHello">打招呼</button>
</template>

<script>
  //setup函数如果想返回渲染函数,需要引入h函数
  //import { h } from 'vue'
  export default {
    name: 'App',
    //此处只是展示一下setup,暂不考虑数据响应式
    setup() {
      //在setup中配置data
      let name = '胡歌'
      
      //在setup中配置methods
      function sayHello() {
        console.log(`${name}向你问好!`)
      }
      
      //setup函数需要返回值,在模板中均可以直接使用
      return {
        name,
        sayHello
      }
      
      //如果要返回一个渲染函数
      //需要引入h函数
      //return () => h('h1', '返回渲染函数') //渲染一个H1标签,内容为'返回渲染函数'
    }
  }
</script>

1-3、语法糖使用setup函数

<template>
  <div class="home">{{ name }}---{{ age }}</div>
  <button @click="count()">计算</button>
</template>

//语法糖:在script标签中加入setup,不在需要return变量和方法出去,模板依然可以使用
<script lang='ts' setup>
  import { ref } from 'vue';
  
  /* 使用defineOptions宏函数设置组件名字 */
  defineOptions({
    name: 'Login'
  })
  
  let name = '李白'
  let age=ref(10)
  const count = () => {
    age.value+=10
  }
</script>

2.VUE生命周期

2-1.生命周期介绍

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

2-2.Vue2的生命周期

创建阶段:beforeCreatecreated

挂载阶段:beforeMountmounted

更新阶段:beforeUpdateupdated

销毁阶段:beforeDestroydestroyed

2-3.Vue3的生命周期

创建阶段:setup

挂载阶段:onBeforeMountonMounted

更新阶段:onBeforeUpdateonUpdated

卸载阶段:onBeforeUnmountonUnmounted

注意: 子组件先挂载完毕,父组件后挂载完毕

2-4.常用钩子

常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)

2-5.使用案例

<script lang="ts" setup>
  import { 
    onBeforeMount, 
    onMounted, 
    onBeforeUpdate, 
    onUpdated, 
    onBeforeUnmount, 
    onUnmounted 
  } from 'vue'

  // 生命周期钩子
  onBeforeMount(()=>{
    console.log('挂载之前')
  })
  onMounted(()=>{
    console.log('挂载完毕')
  })
  onBeforeUpdate(()=>{
    console.log('更新之前')
  })
  onUpdated(()=>{
    console.log('更新完毕')
  })
  onBeforeUnmount(()=>{
    console.log('卸载之前')
  })
  onUnmounted(()=>{
    console.log('卸载完毕')
  })
</script>

3.【ref函数与reactive函数】

3-1.【ref 创建:基本类型的响应式数据】

  • 作用:定义响应式变量。
  • 语法:let xxx = ref(初始值)
  • 返回值:一个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">
  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>

3-2.【ref 创建:对象类型的响应式数据】

  • 其实ref接收的数据可以是:基本类型对象类型
  • ref接收的是对象类型,内部其实也是调用了reactive函数。
<template>
  <div class="person">
    <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 lang="ts" setup name="Person">
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
      }
    }
  }
})

console.log(car)

function changeCarPrice() {
  car.value.price += 10
}
function changeFirstGame() {
  games.value[0].name = '流星蝴蝶剑'
}
function test(){
  obj.value.a.b.c.d = 999
}
</script>

3-3.【reactive 创建:对象类型的响应式数据】

  • 作用:定义一个响应式对象(基本类型不要用它,要用ref,否则报错)
  • 语法:let 响应式对象= reactive(源对象)
  • 返回值:一个Proxy的实例对象,简称:响应式对象。
  • 注意点:reactive定义的响应式数据是“深层次”的。
<template>
  <div class="person">
    <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 lang="ts" setup name="Person">
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
}

//注意:使用reactive生成的对象,如果想整个对象重新替换,使用Object.assign();ref生成的对象直接.value=xxx替换
function changeCar(){
    Object.assign(car,{brand: '奥迪', price: 1000}) //正确,页面更新
    car={brand: '奥迪', price: 1000}//页面不更新
    car=reactive({brand: '奥迪', price: 1000})//页面不更新
}

</script>

3-4.【ref 对比 reactive】

  • 宏观角度看:
  1. ref用来定义:基本类型数据对象类型数据
  2. reactive用来定义:对象类型数据
  • 区别:
  1. ref创建的变量必须使用.value(可以使用volar插件自动添加.value)。
  2. reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。
  • 使用原则:
  1. 若需要一个基本类型的响应式数据,必须使用ref
  2. 若需要一个响应式对象,层级不深,refreactive都可以。
  3. 若需要一个响应式对象,且层级较深,推荐使用reactive

3-5.【标签的 ref 属性】

  • 作用1:用在普通DOM标签上,获取的是DOM节点
//用在普通DOM标签上,获取的是DOM节点
<template>
  <h1 ref="personName">姓名:{{ name }}</h1>
  <h2>年龄:{{age }}</h2>
  <h3>职业:{{career }}</h3>
  <button @click="showName">点击</button>
  
  <input ref="inpRef" type="text">
</template>

<script lang='ts' setup>
import { ref,onMounted } from 'vue';

let inpRef = ref()
onMounted(() => {
  //元素加载完毕,就可以获得改元素了
  inpRef.value.focus() //input框聚焦
})

let name = ref('胡歌')
let age = ref(100)
let career = ref('演员')
// 在HTML元素上设置ref,变量名相同,即可获取DOM元素 
let personName = ref()

function showName() {
  //personName.value打印出来时是DOM元素
  console.log(personName.value)
}
//导出需要暴露出去的数据或方法
defineExpose({
  name,
  age,
  career
})
</script>
  • 作用2:用在组件标签上,获取的是组件实例对象
<template>
  <HomeView ref="homeView" />
  <button @click="showLog">获取组件节点</button>
</template>

<script lang='ts' setup>
import HomeView from './views/HomeView.vue';
import { ref } from 'vue';

let homeView = ref()

function showLog() {
  //homeView.value打印的是HomeView组件实例,但是不能直接获取其内部数据、方法,
  //需要子组件defineExpose({})暴露出来
  console.log(homeView.value)
  console.log(homeView.value.name) //输出胡歌
  console.log(homeView.value.age) //输出100
}
</script>

4.【toRefs 与 toRef】

  • 作用:将一个响应式对象中的每一个属性,转换为ref对象。
  • 备注:toRefstoRef功能一致,但toRefs可以批量转换。
  • 语法如下:
<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>

5.【计算属性-computed】

//注意:不要在computed函数中去写操作dom、调接口
<template>
  <div class="person">
    <div>姓:<input type="text" v-model="firstName"></div>
    <div>名:<input type="text" v-model="lastName"></div>
    <div>全名:<span>{{ fullName }}</span></div>
    <div>全名2:<span>{{ fullName2 }}</span></div>
    <div>全名3:<span>{{ fullName3('李明') }}</span></div>
    <br>
    <button @click="changeFullName">修改全名2改为:li-si</button>
  </div>
</template>

<script setup lang="ts" name="App">
import { ref, computed } from 'vue'

let firstName = ref('zhang')
let lastName = ref('san')

// 计算属性——只读取,不修改
let fullName = computed(()=>{
  return firstName.value + '-' + lastName.value
})

// 计算属性——既读取又修改
let fullName2 = 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() {
  fullName2.value = 'li-si'
} 

// 计算属性-带参数
let fullName3 = computed(() => (val: string) => {
  return firstName.value + '-' + lastName.value + val
})
</script>

6.【监视-watch函数】

  • 作用:监视数据的变化(和Vue2中的watch作用一致)
  • 特点:Vue3中的watch只能监视以下四种数据
    • ref定义的数据。
    • reactive定义的数据。
    • 函数返回一个值(getter函数)。
    • 一个包含上述内容的数组。

6-1.【情况一:监视ref定义的【基本类型】数据】

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

<script lang="ts" setup>
import { ref, watch } from 'vue'

let sum = ref(0)

function changeSum() {
  sum.value += 1
}
// 监视【ref】定义的【基本类型】数据--watch(监听对象,回调函数,配置项)
// 停止监视,满足需要的条件,执行watch函数返回值(函数)即可停止监视

const stopWatch = watch(sum, (newValue, oldValue) => {
  console.log('sum变化了', newValue, oldValue)
  if (newValue >= 10) {
    stopWatch()
  }
},{
    immediate: true//注意:立即监视,默认immediate:false
})

</script>
<template>
  <div class="person">
    <h1>情况二:监视 【多个】【ref】定义的【基本类型】数据</h1>
    <button @click="changeName">改变姓名</button>
    <button @click="changeAge">改变年龄</button>
  </div>
</template>

<script lang="ts" setup>
import { ref, watch } from 'vue'

let name = ref('胡歌')
let age = ref(20)

function changeName() {
  name.value+='-'
}
function changeAge() {
  age.value += 1
}
//监视【多个】写法---watch([监听对象,监听对象],回调函数,配置项)
watch([name, age], (newValue, oldValue) => {
  console.log(newValue, oldValue)
},{immediate:true})

// 合并监听-返回值不一样, 以数组的方式返回所有被监听的对象,无论是否发生改变
// immediate:true立即监听-输出:     (2) ['胡歌',20] []
// 改变name-输出:                  (2) ['胡歌-',20] (2) ['胡歌',20]
// 点击age-输出:                   (2) ['胡歌-',21] (2) ['胡歌-',20]
</script>

6-2.【情况二:监视ref定义的【对象类型】数据】

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

/*
情况一:监视整个person, 未开启深度监视
使用changeName、changeAge方法修改单个属性值,【不会触发监视】;
使用changePerson方法改变整个对象,【会触发监视】
*/
watch(person, (newValue, oldValue) => {
  console.log('person变化了', newValue, oldValue)
})

/*
情况二:监视整个person, 开启深度监视deep: true, 
使用changeName、changeAge方法修改属性值,触发监视,但是【newValue, oldValue返回值是一样的】;
使用changePerson方法改变整个对象,也会触发监视,【newValue, oldValue返回值不一样的】
*/
watch(person, (newValue, oldValue) => {
  console.log('person变化了', newValue, oldValue)
}, { deep: true })

</script>

6-3.【情况三:监视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>
  </div>
</template>

<script lang="ts" setup name="Person">
import { reactive, watch } from 'vue'

let person = reactive({
  name: '张三',
  age: 18
})

function changeName() {
  person.name += '~'
}
function changeAge() {
  person.age += 1
}
function changePerson() {
  Object.assign(person, { name: '李四', age: 80 })
}

/*
 监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的;
 无论怎么修改对象都会被监听,且【newValue, oldValue都是最新值】
*/
watch(person, (newValue, oldValue) => {
  console.log('person变化了', newValue, oldValue)
})
</script>

6-4.【情况四:监视refreactive定义的【对象类型】数据中的某个属性】

  • 若该属性值不是【对象类型】,需要写成函数形式。
  • 若该属性值是依然是【对象类型】,推荐写成函数。

结论:监视的要是对象里的属性,那么最好写函数式,注意点:若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监视。

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

  /*
  监视响应式对象中的某个属性,且该属性是【对象类型】的,可以直接写,也能写函数,更推荐写函数
  单个属性变化时,newValue,oldValue一样,均为新值
  当整个对象变化时,newValue,oldValue不一样
  */
  watch(()=>person.car,(newValue,oldValue)=>{
    console.log('person.car变化了',newValue,oldValue)
  },{deep:true})
  
</script>

6-5.【情况五:监视refreactive定义的【对象类型】数据中的多个属性】

<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([()=>对象.xx , ()=>对象.xx,···],()=>{xxx},{deep:true})
  watch([()=>person.name,()=>person.car],(newValue,oldValue)=>{
    console.log('person.car变化了',newValue,oldValue)
  },{deep:true})

</script>

7.【监视进阶方法:watchEffect函数】

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

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

// 方法
function changeTemp() {
  temp.value += 10
}
function changeHeight() {
  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实现,不用指明监视谁,类似computed,用到谁监视谁;
自动调用一次,如果没有使用变量,什么都不监视;
注意如果是复杂类型,没有深度监视,必须使用具体属性
*/
const stopWtach = watchEffect(() => {
  // 室温达到50℃,或水位达到20cm,立刻联系服务器
  if (temp.value >= 50 || height.value >= 20) {
    console.log('联系服务器')
  }
  // 水温达到100,或水位达到50,取消监视
  if (temp.value === 100 || height.value === 50) {
    console.log('清理了')
    stopWtach()
  }
})
</script>

8.【组件传值】

8-1.【props传值:父子组件相互传递】

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

index.ts设置类型

export interface PersonInter{
  id: string,
  name: string,
  age:number
}

export type Persons = Array<PersonInter>

父组件.vue中代码:

<template>
  <HomeView :personList="personList" />
</template>

<script lang='ts' setup>
import HomeView from '@/views/HomeView.vue'
import {type Persons} from '@/types'
import { reactive } from 'vue';

let personList = reactive<Persons>([
  { id: 'xxxxx01', name: '胡歌', age: 19 },
  { id: 'xxxxx02', name: '霍建华', age: 20 },
  { id: 'xxxxx03', name: '刘思思', age: 21 }
])
</script>
    

子组件.vue中代码:

<template>
  <ul>
    <li v-for="item in personList" :key="item.id">
    {{ item.name }} --- {{ item.age }}
    </li>
  </ul>
</template>

<script lang='ts' setup>
import { type Persons } from '@/types'
/* 宏函数不用引入 withDefaults(),defineProps() */

/* 只接收prop*/
// defineProps(['personList'])

/* 接收+传递类型  */
// defineProps<{personList:Persons}>()

/* 接收+限制类型+指定默认值+限制必要性 */
let {personList} = withDefaults(defineProps<{personList?:Persons}>(), {
  personList: () => [{ id: 'xxxxx00', name: '默认', age: 1 }]
})

/* defineProps宏函数 其他写法 */
defineProps({
 hobby: String, //简写
 nums: {
   type: Number,   //配置类型
   required: true, //配置是否必传
   default:500     //配置默认值
 }
})
</script>    
  • 子传父:属性值是函数

父组件.vue

<template>
  <h2>子组件传递--{{ toy }}</h2>
  <HomeView :sendToy="getToy" />
</template>

<script lang='ts' setup>
import HomeView from '@/views/HomeView.vue'
import { ref } from 'vue';

let toy = ref('')

function getToy(value:string) {
  toy.value=value
}
</script>

子组件.vue

<template>
  <button @click="sendToy(toy)">点我传递内容给父组件</button>
</template>

<script lang='ts' setup>
import { ref } from 'vue';

let toy=ref('玩具车')

defineProps(['sendToy'])
</script>

8-2.【自定义事件:子传父】

子组件.vue,声明事件

<template>
  <button @click="clickHandler">点我传递内容给父组件</button>
</template>

<script lang='ts' setup>
import { ref } from 'vue';

let toy = ref('玩具车')

/* 声明事件 使用宏函数defineEmits([xxx,xxx])*/
const emit = defineEmits(['send-toy'])

function clickHandler() {
  emit('send-toy',toy.value)
}

</script>

父组件.vue,绑定自定义事件

<template>
  <h2>子组件传递--{{ toy }}</h2>
  <!-- 给子组件绑定 自定义事件 -->
  <HomeView @send-toy="getToy" />
</template>

<script lang='ts' setup>
import HomeView from '@/views/HomeView.vue'
import { ref } from 'vue';

let toy = ref('')
/* 处理子组件传递的数据 */
function getToy(value:string) {
  toy.value=value
}
</script>

8-3.【provide、inject】

  • 概述:实现祖孙组件直接通信,只要是后代组件,都可以接收到。

  • 具体使用:provide(key,value)/ inject(key,默认值)

    • 在祖先组件中通过provide配置向后代组件提供数据
    • 在后代组件中通过inject配置来声明接收数据

grandpa.vue

<template>
  <HomeView />
  <div class="father">
    <h3>祖先组件</h3>
    <h4>资产:{{ money }}万元</h4>
    <h4>汽车:{{ car.brand }}---价格:{{ car.price }}</h4>
    <button @click="money += 1">资产+1</button>
    <button @click="car.price += 1">汽车价格+1</button>
    <Child/>
  </div>
</template>

<script lang='ts' setup>
import HomeView from '@/views/HomeView.vue'
import { ref, reactive, provide } from "vue";
// 数据
let money = ref(100)
let car = reactive({
  brand: '奔驰',
  price: 100
})
// 用于更新money的方法
function updateMoney(value: number) {
  money.value -= value
}
// 提供数据
provide('moneyContext', { money, updateMoney })
provide('car', car)
</script>

grandson.vue

<template>
  <h3>我是孙组件</h3>
  <h4>资产:{{ money }}万元</h4>
  <h4>汽车:{{ car.brand }}---价格:{{ car.price }}</h4>
  <button @click="updateMoney(6)">花爷爷的钱</button>
</template>

<script lang='ts' setup>
import { inject } from "vue";

//注入数据,既可以接收祖先传递的数据、方法,也可以利用方法传递数据给祖先组件
const { money, updateMoney } = inject('moneyContext', { money: 0, updateMoney: (value: number) => { } })
const car = inject('car', { brand: '未知', price: 0 })
</script>

8-4.【mitt-任意组件间通信】

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

第一步:新建文件--src\utils\emitter.ts

// 引入mitt 
import mitt from "mitt";

// 创建emitter
const emitter = mitt()

/*
  // 绑定事件
  emitter.on('text',(value)=>{
    console.log('text事件被触发',value)
  })

  // 触发事件
  emitter.emit('text',666)

  // 解绑事件
  emitter.off('text')

  // 清理全部事件
  emitter.all.clear()
*/

// 创建并暴露mitt
export default emitter

第二步:在接收数据的组件中-绑定事件、同时在组件卸载前解绑事件

<template>
  <h2>接收玩具---{{ toy }}</h2>
</template>

<script lang='ts' setup>
import emitter from '@/utils/emitter'
import { ref,onUnmounted } from "vue";
import type { Handler } from "mitt";
let toy = ref('')
//TS类型判断
const handleSendToy: Handler<unknown> = (value: unknown) => {
  toy.value = value as string;
}
// 绑定事件
emitter.on('send-toy', handleSendToy) 

// 解绑事件
onUnmounted(() => {
  emitter.off('send-toy')
})
</script>

第三步:提供数据的组件,在合适的时候触发事件

<template>
  <button @click="sendToy">点击发送玩具</button>
</template>

<script lang='ts' setup>
import { ref } from "vue";
import emitter from '@/utils/emitter'

let toy = ref('奥特曼')
function sendToy() {
  emitter.emit('send-toy',toy.value)
}
</script>    

8-5.【v-model--父子组件通信】

  • v-model本质:
<!-- 使用v-model指令 -->
<input type="text" v-model="userName">

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

一、基础写法

<template>
  <h1>{{ firstName }}</h1>
  <HomeView v-model:modelValue="firstName" />
</template>

<script lang='ts' setup>
import { ref } from "vue";
import HomeView from "./views/HomeView.vue";

let firstName = ref('胡歌')
</script>
<template>
  <input type="text" 
         :value="modelValue" 
         @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)"
   >
</template>

<script lang='ts' setup>

//接收props,v-model="firstName"===v-model:modelValue="firstName",所以默认为modelValue
defineProps(['modelValue'])

//声明事件, 默认为'update:modelValue'
const emit = defineEmits(['update:modelValue'])

</script>    

二、重命名写法

<template>
  <h1>{{ firstName }}</h1>
  <HomeView v-model:firstName="firstName" />
</template>
<template>
  <input type="text" 
         :value="firstName" 
         @input="emit('update:firstName', (<HTMLInputElement>$event.target).value)"
   >
</template>

<script lang='ts' setup>

//接收props,v-model:firstName="firstName"
defineProps(['firstName'])

//声明事件, 重名为'update:firstName'
const emit = defineEmits(['update:firstName'])

</script>    

三、使用defineModel宏函数--测试阶段

vite.config.js文件设置--defineModel:true

//此时vue版本为3.3.4, defineModel宏函数还是测试阶段,需要单独配置才能使用

export default defineConfig({
  plugins: [
    vue({
      script: {
        defineModel:true; //开启
      }
    }),
  ],
})

父组件.vue

<template>
  <h1>{{ firstName }}</h1>
  <HomeView v-model="firstName" />
</template>

<script lang='ts' setup>
import { ref } from "vue";
import HomeView from "./views/HomeView.vue";

let firstName = ref('胡歌')

</script>

子组件.vue

<template>
  <input v-model="firstName">
</template>

<script lang='ts' setup>

//此时vue版本为3.3.4, defineModel宏函数还是测试阶段,需要单独配置才能使用
import { defineModel } from 'vue';
//firstName在父组件、子组件完成双向绑定,当子组件中input值修改时,所有使用firstName的都会跟着改变.
const firstName = defineModel()

</script>

8-6.【$refs 和 $parent :父子组件相互通信】

  1. 概述:

    • $refs用于 :父→子。
    • $parent用于:子→父。
  2. 原理如下:

    属性说明
    $refs值为对象,包含所有被ref属性标识的DOM元素或组件实例。
    $parent值为对象,当前组件的父组件实例对象。
<template>
  <h2>房产:{{ house }} 套</h2>
  <!-- $refs 获取该组件内所有 ref标记的子组件的实例对象  -->
  <button @click="changeToy($refs)">改变儿子玩具</button>
  <HomeView ref="homeRef" />
</template>

<script lang='ts' setup>
import { ref } from "vue";
import HomeView from "./views/HomeView.vue";

let house = ref(5)
let homeRef=ref()

function changeToy(refs:any) {
  refs.homeRef.toy = '小汽车' //等价于: homeRef.value.toy= '小汽车'
}

defineExpose({ house })
</script>
    
<template>
  <h2>玩具:{{ toy }}</h2>
  <!-- $parent 获取父组件的实例对象 -->
  <button @click="getHouse($parent)">获取父亲房产</button>
</template>

<script lang='ts' setup>
import { ref } from 'vue';

let toy =ref('奥特曼')

function getHouse(parent: any) {
  parent.house -= 1
}
defineExpose({toy})
</script>    

四、自定义hook

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装,类似于vue2.x中的mixin

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

组件.vue

<template>
  <h2>当前求和为:{{ sum }} --- 双倍{{ doubleSum }}</h2>
  <button @click="increment">点我+1</button>
  <button @click="decrement">点我-1</button>
  <hr>
  <img v-for="(u, index) in dogList" :key="index" :src="(u as string)"> 
  <button @click="getDog">再来一只狗</button>
</template>

<script lang='ts' setup>
/* 引入hooks */
import useCount from "@/hooks/useCount";
import useDog from "@/hooks/useDog";

const { sum, increment, decrement, doubleSum } = useCount()
const { dogList, getDog } = useDog()
</script>    

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

useCount.ts

import { ref, onMounted,computed } from 'vue'

export default function () {
  let sum = ref(0)

  const increment = () => {
    sum.value += 1
  }
  const decrement = () => {
    sum.value -= 1
  }

  const doubleSum = computed(() => {
    return sum.value*2
  })

  onMounted(() => {
    increment()
  })

  //向外部暴露数据
  return { sum, increment, decrement, doubleSum }
}		    

五、插槽

1.【默认插槽】

<template>
  <HomeView title="游戏推荐">
    <ul>
      <li v-for="item in games" :key="item.id">{{ item.name }}</li>
    </ul>
  </HomeView>
  <HomeView title="图片推荐">
    <img :src="imgUrl" alt="">
  </HomeView>
  <HomeView title="视频推荐">
    <video :src="videoUrl" controls></video>
  </HomeView>

</template>

<script lang='ts' setup>
import { reactive, ref } from "vue";
import HomeView from "./views/HomeView.vue";

let games = reactive([
  { id: 'xx01', name: '英雄联盟' },
  { id: 'xx02', name: '穿越火线' },
  { id: 'xx03', name: 'DNF' }
])
let imgUrl=ref('https://www.bilibili.com/')
let videoUrl=ref('https://www.bilibili.com/')

</script>    
<template>
  <h2>{{ title }}</h2>
  <!-- 占位符 -->
  <slot> <div>默认内容</div> </slot>
</template>

<script lang='ts' setup>

defineProps(['title'])

</script>    

2.【具名插槽:父组件将内容传递给子组件对应slot标签上】

<template>
  <!-- 具名插槽写法1 v-slot:名字 -->
  <!-- 具名插槽写法2 #名字 -->
  <!-- 具名插槽只能写在组件标签/template标签上 -->
  <HomeView>
    <template v-slot:slotName>
      <h2>游戏推荐</h2>
    </template>
    <template v-slot:slotContent>
      <ul>
        <li v-for="item in games" :key="item.id">{{ item.name }}</li>
        </ul>
    </template>
  </HomeView>
  <HomeView>
    <template v-slot:slotName>
      <h2>图片推荐</h2>
    </template>
    <template v-slot:slotContent>
      <img :src="imgUrl" alt="">
    </template>
  </HomeView>
  <HomeView>
    <template #slotName>
      <h2>视频推荐</h2>
    </template>
    <template #slotContent>
      <video :src="videoUrl" controls></video>
    </template>
  </HomeView>

</template>

<script lang='ts' setup>
import { reactive, ref } from "vue";
import HomeView from "./views/HomeView.vue";

let games = reactive([
  { id: 'xx01', name: '英雄联盟' },
  { id: 'xx02', name: '穿越火线' },
  { id: 'xx03', name: 'DNF' }
])
let imgUrl=ref('https://www.bilibili.com/')
let videoUrl=ref('https://www.bilibili.com/')

</script>    
<template>
  <slot name="slotName"> 默认标题 </slot>
  <slot name="slotContent"> 默认内容 </slot>
</template>    

3.【作用域插槽:数据在子组件,父组件根据传递过来的数据生成不同内容 】

<template>
  <!-- 通过v-slot="获取一个对象,包裹所有slot传递过来的信息" -->
  <HomeView>
    <template v-slot="params">
      <h2>{{ params.title }}1</h2>
      <ul>
        <li v-for="item in params.youxi" :key="item.id">{{ item.name }}</li>
      </ul>
    </template>
  </HomeView>

  <HomeView>
    <!-- 可以直接使用 对象结构 -->
    <template v-slot="{youxi,title}">
      <p>{{ title }}2</p>
      <ol>
        <li v-for="item in youxi" :key="item.id">{{ item.name }}</li>
      </ol>
    </template>
  </HomeView>

  <!-- 可以和具名插槽一起使用 v-slot:名字="" / #名字="" -->
  <HomeView>
    <template  v-slot:slotContent="{ youxi, title }">
      <p>{{ title }}3</p>
      <ol>
        <li v-for="item in youxi" :key="item.id">{{ item.name }}</li>
      </ol>
    </template>
  </HomeView>

  <HomeView>
    <template  #slotContent="{ youxi, title }">
      <h3>{{ title }}4</h3>
      <ul>
        <li v-for="item in youxi" :key="item.id">{{ item.name }}</li>
      </ul>
    </template>
  </HomeView>
</template>

<script lang='ts' setup>

import HomeView from "./views/HomeView.vue";

</script>    
<template>
  <slot :youxi="games" title="推荐游戏"></slot>
  <!-- 可以和具名插槽一起使用-->  
  <slot name="slotContent" :youxi="games" title="游戏排行榜"></slot>
</template>

<script lang='ts' setup>
import { reactive } from "vue";
let games = reactive([
  { id: 'xx01', name: '英雄联盟' },
  { id: 'xx02', name: '穿越火线' },
  { id: 'xx03', name: 'DNF' }
])
</script>  

六、宏函数总结

1.defineOptions

defineOptions(),可以定义任意的选项,除(props,emits,expose,slots)外

//因为使用了语法糖,使得setup同级的配置无法在使用了,所以需要一个宏函数来配置.
//必须是在vue3.3版本及以上
<script setup>
    //常见定义该组件的name
    defineOptions({
      name:'Login'
    })
</script>

2.defineProps

defineProps(),用于子组件接收父组件传递的props,模板中可直接使用,但不能直接修改值

<script lang='ts' setup>   
//接收props
defineProps(['xx','xxx',···])

//配置写法
defineProps({ 
    hobby: String, //简写 
    nums: { 
        type: Number, //配置类型 
        required: true, //配置是否必传 
        default:500 //配置默认值 
    } 
})

//ts声明方法
/* 接收+ 声明类型  */
// defineProps<{变量:类型}>()  
    
/* 接收+限制类型+指定默认值+限制必要性 */
 
//let { xx } = withDefaults(defineProps<{ xx?: 类型 }>(), {
//  给变量设置默认值  
//  xx: () => 默认值
//})    
   
</script>

3.defineEmits

defineEmits(),用于子组件接收父组件传递的Emits,模板中可直接使用其方法

<script setup>
/* 声明事件 使用宏函数defineEmits([xxx,xxx])*/ 
const emit = defineEmits(['send-toy'])

function clickHandler() { 
    emit('send-toy',toy.value)
}    
</script>

4.withDefaults

withDefaults(),用于子组件接收父组件传递的props

<script setup>

</script>

七、路由:vue-router

安装:npm install vue-router@next

1. 【两个注意点】

  • 路由组件通常存放在pagesviews文件夹,一般组件通常存放在components文件夹。
  • 通过点击导航,视觉效果上“消失” 了的路由组件,默认是被卸载掉的,需要的时候再去挂载

2. 【router基础使用】

  • Vue3中要使用vue-router的最新版本,目前是4版本。

src/router/index.ts路由配置文件

//创建一个路由器,并暴露出去

//第一步引入createRouter
import { createRouter,createWebHistory } from 'vue-router'

//创建路由器
const router = createRouter({
  //路由器的工作模式
  history: createWebHistory(),
  //设置路由对应相应组件
  routes: [
    /* 
      {
        path: '路径',
        name:'命名路由',//非必须
        component:对应的组件,
        redirect: '重定向路径',//非必须
        meta:{title:'我是某页'},//非必须,该路由页的元信息
        children:[]//非必须,子级路由配置,属性一样
      } 
    */
    {
      path: '/', 
      name: 'Home',
      component: () => import("@/views/HomeView.vue")
    },
    {
      path: '/login',
      name: 'Login',
      component: () => import("@/views/LoginView.vue")
    },
    {
      path: '/404',
      name: 'Notfind',
      component: () => import("@/views/NotFound.vue")
    },
    // ':' -- 表示这是一个动态参数
    // 'catchAll' -- 自定义名称
    // '(.*)' -- 表示匹配任意字符的正则表达式,用于捕获所有路径的通配符
    {
      //用于捕获所有错误的路由,都进入404组件
      path: '/:catchAll(.*)',
      name: '*',
      redirect: '/404'
    }
  ]
})

export default router

main.ts文件引入router

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)

app.mount('#app')

App.vue使用router

<template>
  <div class="app">
    <h2 class="title">Vue路由测试</h2>
    <!-- 导航区 -->
    <div class="navigate">
      <!-- 第一种:to的字符串写法 -->
      <RouterLink to="/home" active-class="active">首页</RouterLink>
      <!-- 第二种:to的对象写法 -->
      <RouterLink :to="{path:'/home'}" active-class="active" >Home</RouterLink>
      <RouterLink to="/news" active-class="active">新闻</RouterLink>
      <RouterLink to="/about" active-class="active">关于</RouterLink>
    </div>
    <!-- 展示区 RouterView占位-->
    <div class="main-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

<script lang="ts" setup name="App">
  import {RouterLink,RouterView} from 'vue-router'  
</script>

3. 【路由器工作模式】

history模式

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

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

hash模式

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

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

import { createRouter,createWebHistory,createWebHashHistory } from 'vue-router'

//创建路由器
const router = createRouter({
  // 1. createWebHistory(path) --  history模式 不带#
  // 2. createWebHashHistory(path) -- hash模式 带#
  // 3. path--指所有路由前的路径,默认是'/',也可以使用import.meta.env.BASE_URL 
   
  //Hash模式
  history: createWebHashHistory(),
  
  //History模式
  history: createWebHistory(),
    
  //设置路由对应相应组件
  routes: []
})

export default router

4. 【路由嵌套】

import { createRouter,createWebHashHistory } from 'vue-router'

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: () => import("@/views/HomeView.vue"),
      redirect: '/home',
      // 路由嵌套,嵌套的路由地址要完整书写,例如'/home/one'
      children: [
        {
          path: 'one',
          component:()=>import("@/views/Child/ChildOne.vue")
        },
        {
          path: 'two',
          component: () => import("@/views/Child/ChildTwo.vue")
        }
      ],
      meta: {
        title:'我是home页'
      }
    },
  ]
})

export default router    
//vue文件
//跳转路由(记得要加完整路径)
<router-link to="/home/one">xxxx</router-link>
<!-- 或 -->
<router-link :to="{path:'/home/one'}">xxxx</router-link>

5. 【路由跳转+ 传参】

5-1.RouterLink

  • 【RouterLink跳转】
//vue文件
<template>
    <!-- 第一种:to的字符串写法 -->
    <RouterLink to="/home" active-class="active">首页</RouterLink>
    <!-- 第二种:to的对象写法 path-->
    <RouterLink :to="{path:'/home'}" active-class="active" >Home</RouterLink>
    <!-- 第三种:to的对象写法 name:配置路由时的name-->
    <RouterLink :to="{name:'Home'}" active-class="active" >Home</RouterLink>      
    
    <!-- 引入路由页面 -->
    <RouterView></RouterView>
</template>

<script lang='ts' setup>
    //引入RouterLink
    import { RouterLink,RouterView } from 'vue-router';
</script>
  • 【RouterLink传参】

    • query参数
    //vue文件
    <!-- 第一种 跳转并携带query参数(to的字符串写法) -->
    <router-link to="/news/detail?id=1&title=新闻&content=欢迎你">跳转1</router-link>   
    
    <!-- 第二种 跳转并携带query参数(to的对象写法) -->
    <router-link 
      :to="{
        //name:'xiang', //用name也可以跳转
        path:'/news/detail',
        query:{
              id:1,
              title:"新闻",
              content:"发生火灾"
            }
      }"
    >
        跳转1
    </router-link> 
    
    
    //接收参数
    <script lang='ts' setup>
        import { useRoute } from 'vue-router';
        const route = useRoute()
        // 打印query参数
        console.log(route.query)
    </script>    
    
    • params参数

    在路由配置文件中提前设置好参数的key

    routes: [
        {
          path: '/home',
          name: 'Home',
          component: () => import("@/views/HomeView.vue"),
          children: [
            {
              //必须提前使用:key 提前设置好参数的key
              path: 'one/:id/:title/:content',
              component:()=>import("@/views/Child/ChildOne.vue")
            },
            {
              path: 'two/:id/:name',
              component: () => import("@/views/Child/ChildTwo.vue")
            }
          ]
        },
    ]  
    
    //vue文件
    <!-- 第一种 跳转并携带params参数(to的字符串写法) -->
    <router-link :to="`/home/one/001/新闻/内容001`">跳转</router-link>
    
    <!-- 第二种 跳转并携带params参数(to的对象写法) -->
    <router-link 
      :to="{
        name:'Home', //只能用name跳转
        params:{
              id:1,
              title:"新闻",
              content:"发生火灾"
            }
      }"
    >
        跳转2
    </router-link> 
        
    
    //接收参数的vue文件
    <script lang='ts' setup>
        import { useRoute } from 'vue-router';
        const route = useRoute()
        // 打印params参数
        console.log(route.params)
    </script>    
    

5-2.编程式导航

  • 注意:

    • 路由组件的两个重要的属性:$route$router变成了两个hooks
    • router.push()是追加历史记录(默认值)。
    • router.replace()是替换当前记录。
  • 【跳转】

    //vue文件
    <template>
      <!-- template 上可以直接使用 $router进行跳转 -->
      <button @click="$router.push('/')">首页</button>
    
      <!-- 使用方法跳转 -->
      <button @click="goToLogin">登录页</button>
    
      <!-- 引入路由页面 -->
      <RouterView></RouterView>  
    </template>
    
    <script lang='ts' setup>
        //引入RouterLink
        import { RouterView } from 'vue-router';
        //要想使用路由跳转--引入useRouter
        //要想使用路由参数--引入useRoute
        import {useRoute,useRouter} from 'vue-router'
        const router = useRouter()
        const route = useRoute()
    
        function goToLogin(){
          router.push('/login')//是追加浏览器历史记录(默认值)
          router.replace('/login')//是替换当前记录。
        }
    </script>
    
  • 传参

    跳转vue

    <template>
      <button @click="goQuery">跳转query传参</button>
      <button @click="goParams">跳转params传参</button>
    
      <RouterView></RouterView>
    </template>
    
    <script lang='ts' setup>
    import { RouterView,RouterLink,useRouter } from 'vue-router';
    
    const router= useRouter()
    
    function goQuery() {
      //query传参--name和path都可以使用
      //弊端:url地址会显示参数
      //优点:刷新页面,参数依然存在
      
      //router.replace()参数一样
      router.push({
        path: '/login',
        query: {
          id: '001',
          title: '新闻',
          content: '吃饭了吗'
        }
      })
    
    }
    function goParams() {
      //query传参--只能使用name
      //弊端:刷新页面,参数就不存在了
      //优点:url地址会隐藏参数
    
      //router.replace()参数一样
      router.push({
        name: 'Login',
        params: {
          id: '001',
          title: '新闻',
          content: '吃饭了吗'
        }
      })
    }
    </script>
    

    目的vue

    <template>
      <div class="login">
        <h2>{{ route.params.id }}</h2>
        <h2>{{ route.params.title }}</h2>
        <h2>{{ route.params.content }}</h2>
    
        <h2>{{ route.query.id }}</h2>
        <h2>{{ route.query.title }}</h2>
        <h2>{{ route.query.content }}</h2>
      </div>
    </template>
    
    <script lang='ts' setup>
    import { useRoute } from 'vue-router'
    const route = useRoute()
    </script>    
    

6. 【路由的props配置】

一般组件使用时<Dome title="biaot"/>可以直接在标签上自定义属性,组件内通过props接收;但是路由组件是通过<RouterView/>占位引入,无法直接在标签上写自定义属性,但是在路由配置时引入了组件,即可以设置自定义属性,然后通过props接收

  • query参数

    //路由的配置文件  
      routes: [
         //写法:函数写法,相当于将route.query传递给props
        {
          path: '/login',
          name: 'Login',
          component: () => import("@/views/LoginView.vue"),
          props(route){
            return route.query
          }
        }
      ]
    

    使用跳转组件

      <RouterLink to="/login?id=001&title=新闻&content=吃饭了吗">登录页</RouterLink>   
      <RouterLink :to="{
        name:'Login',
        query:{
          id:'001',
          title:'新闻',
          content:'吃饭了吗'
        }
      }"
      >登录页</RouterLink>
    

    路由组件

    <template>
      <div class="login">
          <h2>{{ id }}</h2>
          <h2>{{ title }}</h2>
          <h2>{{ content }}</h2>
      </div>
    </template>
    
    <script lang='ts' setup>
    defineProps(['id','title','content'])
    </script>    
    
  • params参数

    在路由配置文件中提前占位,设置好参数的key

    //路由的配置文件  
      routes: [
        //写法一:布尔值写法
        {
          path: '/login/:id/:title/:content',
          name: 'Login',
          component: () => import("@/views/LoginView.vue"),
          props:true
        }
        
         //写法二:函数写法,相当于将route.params传递给props
        {
          path: '/login/:id/:title/:content',
          name: 'Login',
          component: () => import("@/views/LoginView.vue"),
          props(route){
            return route.params
          }
        }
        
        //写法三:对象写法,传入固定值
        {
          path: '/login/:id/:title/:content',
          name: 'Login',
          component: () => import("@/views/LoginView.vue"),
          props:{id:1,title:'标题',content:'吃饭了吗'}
        }
      ]
    

    使用跳转组件

      <RouterLink to="/login/001/新闻/吃饭了吗">登录页</RouterLink>   
      <RouterLink :to="{
        name:'Login',
        params:{
          id:'001',
          title:'新闻',
          content:'吃饭了吗'
        }
      }"
      >登录页</RouterLink>
    

    路由组件

    <template>
      <div class="login">
          <h2>{{ id }}</h2>
          <h2>{{ title }}</h2>
          <h2>{{ content }}</h2>
      </div>
    </template>
    
    <script lang='ts' setup>
    defineProps(['id','title','content'])
    </script>    
    

7. 【动态路由】

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: () => import("@/views/HomeView.vue"),
      
    },
    {
      path: '/login',
      name: 'Login',
      component: () => import("@/views/LoginView.vue"),
      // props:true
      props(route) {
        return route.query
      },
    }
  ]
})
    
//`router.addRoute` 方法来动态添加路由
router.addRoute({
  path: '/about',
  name: 'About',
  component: () => import('@/views/Child/ChildOne.vue')
})

// 添加 About的子路由
router.addRoute('About', {
  path: 'test',
  name: 'Test',
  component: () => import('@/views/Child/ChildTwo.vue')
});

//路由添加成功后执行一些操作,可以使用 router.isReady() 方法来检查路由是否已经准备就绪
router.isReady().then(() => {
  console.log('路由添加成功后执行的操作')
}).catch(() => {
  console.log('路由添加失败后执行的操作')
})
    
export default router
  • 从后端获取数据之后,再动态添加路由
<template>
  <button @click="getRouter">调取接口,添加路由</button>
  <button @click="goAbout">去往动态路由</button>

  <RouterView></RouterView>
</template>

<script lang='ts' setup>
import { RouterView,useRouter } from 'vue-router';

const router = useRouter()

function getRouter() {
  setTimeout(() => {
    router.addRoute({
      path: '/about',
      name: 'About',
      component: () => import('@/views/Child/ChildOne.vue')
    })
    router.addRoute({
      path: '/news',
      name: 'News',
      component: () => import('@/views/Child/ChildTwo.vue')
    })
    
    router.isReady().then(() => {
      // 进行路由跳转
      router.push('/news');
    });
  }, 500);
}

function goAbout() {
  router.push('/about')
}
</script>

8. 【路由守卫】

  • 全局路由守卫和路由独享守卫
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: ()=>import("@/views/HomeView.vue"),
      // 路由独享守卫逻辑
      beforeEnter(to, from, next) {
        console.log('路由独享守卫逻辑')
        next()
      }
    },
    {
      path: '/login',
      name: 'Login',
      component: () => import("@/views/LoginView.vue"),
      // 路由独享守卫逻辑
      beforeEnter(to, from, next) {
        console.log('路由独享守卫逻辑')
        next()
      }
    }
  ]
})

// to--   对象,目的地路由的信息
// from-- 对象,出发地路由的信息
// next-- 函数,next(path) path不填就去往目的地路由,填就去往指定路由    
router.beforeEach((to, from, next) => {
  // 全局前置守卫逻辑
  console.log('路由跳转-前置守卫', to, from)
});

router.afterEach((to, from, next) => {
  // 全局后置守卫逻辑
  console.log('路由跳转-后置守卫')
});

export default router
  • 全局路由守卫和路由独享守卫
<script lang='ts' setup>
import { useRoute, onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
    
onBeforeRouteLeave((to, from) => {
  // 用于离开当前组件前
  console.log(to, from)
  const bool = window.confirm('你确定要离开当前页面吗?');
  if (!bool) { return false }
})

onBeforeRouteUpdate((to, from) => {
  // 用于在当前 路由参数等 发生改变;通常在<router-view>组件的子组件中调用
  console.log('更新', to, from)
})
</script>    

9. 【监听路由】

<template>
  <button @click="goQuery">跳转query传参</button>
  <button @click="goParams">跳转params传参</button>
  <RouterView></RouterView>
</template>

<script lang='ts' setup>
import { RouterView,useRouter, useRoute } from 'vue-router';
import { watch, onBeforeUnmount } from 'vue';

const router = useRouter()
const route = useRoute()

function goQuery() {
  router.push({
    path: '/login',
    query: {
      id: '001',
      title: '新闻',
      content: '吃饭了吗'
    }
  })

}
function goParams() {
  router.push({
    name: 'Login',
    params: {
      id: '001',
      title: '新闻',
      content: '吃饭了吗'
    }
  })
}

//监视:路由的参数、路径等
watch(() => route.query, (newValue, oldValue) => {
  console.log('监视query', newValue, oldValue)
})
watch(() => route.params, (newValue, oldValue) => {
  console.log('监视params', newValue, oldValue)
})
watch(() => route.path, (newValue, oldValue) => {
  console.log('path', '当前地址:' + newValue, '旧地址:' + oldValue)
})
</script>

八、全局数据存储-pinia

1.【安装引入】

安装:npm i pinia

//在main.js中引入
import { createApp } from 'vue'
import {createPinia} from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()
app.use(pinia).mount('#app')

2.【基础使用方法:存储+读取数据】

第一步:在/src/store/文件夹下创建TS文件,每个ts文件作为一个模块

import { defineStore } from 'pinia'
import { computed ,reactive } from 'vue'

type User = {
  name:string,
  sex: string,
  age: number,
  wealth: number,
  car: {
    brand: string,
    price:number
  }
}
interface ListInter{
  id?: number,
  name?: string
}
    
//defineStore(仓库的唯一标识,()=>{xxx})
export const useUserInfoStore = defineStore('userInfo', () => {

  //声明数据state
  const userInfo:User = reactive({
    name: '胡歌',
    sex: '男',
    age: 20,
    wealth:5000,
    car: {
      brand: '奔驰',
      price: 230000
    }
  })

  //声明修改数据的方法actions
  const changeCar = () => {
    userInfo.car = Object.assign(userInfo.car, { brand: '宝马', price: 32000 })
  }
  const changeName = (value:string) => {
    userInfo.name = value
  }
  const changeAge = () => {
    userInfo.age += 1
  }
  const changeWealth = (value:number) => {
    userInfo.wealth += value
  }

   let list: Array<ListInter> = reactive([])
    
  //声明操作数据方法,支持异步
  const getList = async () => {
    let { data: { data } } = await axios.get('http://geek.itheima.net/v1_0/channels')
    console.log(data.channels)
    list = Object.assign(list, data.channels)
    // list.value = data.channels
  }
  //声明基于数据派生的计算属性 getters
  const doubleMoney = computed(() => userInfo.wealth*3)

  //return 变量、方法
  return {
    userInfo,
    changeCar,
    changeName,
    changeAge,
    changeWealth,
    doubleMoney,
    list,
    getList
  }
})

Home.vue

<template>
  <div class="login">
    <h2>我是Login</h2>
    <div>姓名:{{ userInfo.name }}</div>
    <div>年龄:{{ userInfo.age }}</div>
    <div>财富:{{ userInfo.wealth }}</div>
    <div>
      车辆:{{ userInfo.car.brand }} -- 价格:{{ userInfo.car.price }}
    </div>
    <button @click="changeAge">修改年龄</button>
    <br>
    <button @click="changeCar">修改汽车</button>
  </div>
</template>

<script lang='ts' setup>
import { storeToRefs } from 'pinia'
import { useUserInfoStore } from '@/store/userInfo'

// 不结构接收
const userInfoStore = useUserInfoStore()

//解构,数据需要使用storeToRefs;方法可直接解构,毕竟方法又不是响应式
const { userInfo } = storeToRefs(userInfoStore)
const { changeCar, changeAge } = userInfoStore

</script>

About.vue

<template>
  <div class="home">
    <h2>我是Home</h2>
    <div>姓名:{{ userInfo.name }}</div>
    <div>年龄:{{ userInfo.age }}</div>
    <div>财富:{{ userInfo.wealth }}</div>
    <div>
      车辆:{{ userInfo.car.brand }} -- 价格:{{ userInfo.car.price }}
    </div>
    <br>
    <button @click="changeName('李白')">修改姓名</button>
    <br>
    <button @click="changeWealth(500)">修改财富</button>
  </div>
</template>

<script lang='ts' setup>
import { storeToRefs } from 'pinia'
import { useUserInfoStore } from '@/store/userInfo'

// 不结构接收
const userInfoStore = useUserInfoStore()

//解构,数据需要使用storeToRefs;方法可直接解构,毕竟方法又不是响应式
const { userInfo } = storeToRefs(userInfoStore)
const { changeName,changeWealth } = userInfoStore

</script>

3.【pinia持久化】

使用pinia存储的数据,刷新页面数据就失效了,需要使用插件,插件官网(本质是数据本地存储)

注意:

  • 只有修改了pinia-plugin-persistedstate包裹的数据,才会持久化(本地存储)
  • 如果本地缓存已有数据,pinia不会主动获取,赋值给变量初始值,需要在初值时判断是否已经有本地存储

1.安装 npm i pinia-plugin-persistedstate

2.引入

//将插件添加到 pinia 实例上
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import persisted from 'pinia-plugin-persistedstate'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()
app.use(pinia.use(persisted)).mount('#app')    

3.配置使用

//在模块中,作为defineStore函数的第三个参数
//persist:true ,如果不配置默认将所有变量存储在localStorage中
//defineStore(仓库的唯一标识,()=>{xxx}, {persist:true})

import { defineStore } from 'pinia'
import { computed ,reactive, ref } from 'vue'
import axios from 'axios'

type User = {
  name:string,
  sex: string,
  age: number,
  wealth: number,
  car: {
    brand: string,
    price:number
  }
}

interface ListInter{
  id?: number,
  name?: string
}
//defineStore(仓库的唯一标识,()=>{xxx},{配置项})
export const useUserInfoStore = defineStore('userInfo', () => {

  //声明数据state
  const userInfo:User = reactive({
    name: '胡歌',
    sex: '男',
    age: 20,
    wealth:5000,
    car: {
      brand: '奔驰',
      price: 230000
    }
  })

  //声明修改数据的方法actions
  const changeCar = () => {
    userInfo.car = Object.assign(userInfo.car, { brand: '宝马', price: 32000 })
  }
  const changeName = (value:string) => {
    userInfo.name = value
  }
  const changeAge = () => {
    userInfo.age += 1
  }
  const changeWealth = (value:number) => {
    userInfo.wealth += value
  }

  //如果想页面一刷新就先去看本地存储是否已经缓存了内容    
//let list:类型= reactive(JSON.parse(sessionStorage.getItem('list') as string) || [])
  let list: Array<ListInter> = reactive([])

  //声明操作数据方法,支持异步
  const getList = async () => {
    let { data: { data } } = await axios.get('http://geek.itheima.net/v1_0/channels')
    list = Object.assign(list, data.channels)
  }

  const doubleMoney = computed(() => userInfo.wealth*3)

  return {
    userInfo,
    changeCar,
    changeName,
    changeAge,
    changeWealth,
    doubleMoney,
    getList,
    list
  }
}, {
    
  /* 默认设置
    persist: true, //所有变量存储在localStorage中
  */  
    
  /* 统一设置存储规则
    persist: {
      key: 'user', //自定义存储在本地的key
      storage: sessionStorage,// 可以选择存储方式,localStorage | sessionStorage
      paths: ['list']//选择state中那些变量需要持久化
    } 
  */
  
  // 变量单独设置存储规则
  persist: [
    {
      key: 'info', 
      storage: localStorage,
      paths: ['userInfo']
    },
    {
      key: 'list', 
      storage: sessionStorage,
      paths: ['list']
    }
  ]
})

九、补充

1.【shallowRef和shallowReactive】

  • 作用:创建一个浅层响应式数据,只会使数据的浅层(第一层)属性变成响应式的,数据内部的嵌套属性则不会变成响应式的

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

<template>
  <div>
    <h1>ref--shallowRef</h1>
    <div>{{ user.name }}</div>
    <div>{{ user.car }}</div>
    <div>{{ user.car.brand }}</div>

    <br>

    <div>{{ user2.name }}</div>
    <div>{{ user2.car }}</div>
    <div>{{ user2.car.brand }}</div>

    <button @click="changeUser">改变user信息</button>
    <button @click="changeUser2">改变user2信息</button>
    <button @click="changeUser3">同时改变user、user2信息</button>

  </div>
</template>

<script lang='ts' setup>
import { ref,shallowRef,reactive,shallowReactive } from 'vue';


let user = ref({
  name: '胡歌',
  age: 20,
  car: {
    brand: '宝马',
    price:2000000
  }
})
let user2 = shallowRef({
  name: '胡歌2',
  age: 20,
  car: {
    brand: '宝马2',
    price: 2000000
  }
})

function changeUser() {
  //修改ref创造的数据,任何层的属性,视图都会更新
  user.value.name = '陈瑶'
  user.value.car.brand = '奔驰'
}
function changeUser2() {
  //修改shallowRef创造的数据,修改浅层(第一层)属性,视图才会更新,简单理解只能修改.value=xx
  user2.value={name:'胡汉三',age:50,car:{brand:'毛驴车',price:1000}}

  //数据变了,但视图不会更新
  /*   
    user2.value.name = "陈瑶2"
    user2.value.car.brand = '奔驰2' 
  */
}
function changeUser3() {
  //因为修改了user,视图会更新
  user.value.name = '陈瑶'
  user.value.car.brand = '奔驰'
  /* 
  修改shallowRef生成的数据的深层数据,虽然不会更新视图,
  但是因为user会更新视图,所以user2的数据也被动的在视图上被更新了 
  */
  user2.value.name = "陈瑶2"
  user2.value.car.brand = '奔驰2'
}
</script>    

2.【readonly和shallowReadonly 】

2-1.readonly

  • 作用及特点:

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

    const original = reactive({ ... });
    //readOnlyCopy的全部属性变为只读
    const readOnlyCopy = readonly(original);
    
    let user = reactive({
      name: '胡歌',
      age: 20,
      car: {
        brand: '宝马',
        price:2000000
      }
    })
    
    let user2 = readonly(user)
    
    // user2的任何属性都不可以修改,但user数据修改,user2也会同步更新
    
  • 应用场景:

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

2-2.shallowReadonly

  1. 作用:与 readonly 类似,但只作用于对象的浅层属性。

  2. 用法:

    const original = reactive({ ... });
    const shallowReadOnlyCopy = shallowReadonly(original);
    
    let user2 = shallowReadonly({
      name: '胡歌2',
      age: 20,
      car: {
        brand: '宝马2',
        price: 2000000
      }
    })
    
    function changeUser() {
      //user2.name = '陈瑶'    //浅层属性不允许修改
      // user2.car = {xxx}     //浅层属性不允许修改
      user2.car.brand = '奔驰' //深层属性可以修改
    }
    
  3. 特点:

    • 只将对象的顶层属性设置为只读,对象内部的嵌套属性仍然是可变的。

    • 适用于只需保护对象顶层属性的场景。

3.【toRaw 与 markRaw】

3-1.toRaw

作用:用于获取一个响应式对象的原始对象, toRaw 返回的对象不再是响应式的,不会触发视图更新。在需要将响应式对象传递给非 Vue 的库或外部系统时,使用 toRaw 可以确保它们收到的是普通对象。

  • 写法注意:只能用于reactive生成的数据
/* toRaw */
// 响应式对象
let person = reactive({name:'tony',age:18})
// 原始对象
let rawPerson = toRaw(person)    
  • 案例
<template>
  <div>
    <div>{{ user.name }}</div>
    <div>{{ user.age }}</div>
    <div>{{ user.car }}</div>

    <button  @click="change">修改user2</button>
    <button  @click="changeUser">修改user</button>
  </div>
</template>

<script lang='ts' setup>
import { ref,toRaw,reactive} from 'vue';

//响应式对象
let user = reactive({
  name: '胡歌',
  age: 20,
  car: {
    brand: '宝马',
    price:2000000
  }
})
//原始对象
let user2 = toRaw(user)

//修改原始对象的属性
function change() {
  user2.name = '白居易'
  console.log(user)//其实user中的name也被修改了,但是没有更新视图
}
//更新user中的其他属性,引起视图更新,也会更新视图中的name的数据
function changeUser() {
  user.age=23
}
</script>    

3-2.markRaw

  • 作用:标记一个对象,使其永远不会变成响应式的。
  • 写法:
import { markRaw} from 'vue';    
/* markRaw */
let citys = markRaw([
  {id:'asdda01',name:'北京'},
  {id:'asdda02',name:'上海'},
  {id:'asdda03',name:'天津'},
  {id:'asdda04',name:'重庆'}
])
// 根据原始对象citys去创建响应式对象citys2 —— 创建失败,因为citys被markRaw标记了
let citys2 = reactive(citys)

4.【customRef】

  • 作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制。
  • 写法:
    <template>
      <div>
        <h2>{{ name }}</h2>
        <input v-model="name">
      </div>
    </template>
    
    <script lang='ts' setup>
    import { ref,customRef} from 'vue';
    
    //使用ref定义的响应式数据,一改变,视图立马更新
    // let name =ref("胡歌")
    
    //使用customRef定义的响应式数据
    //track(跟踪) ,trigger(触发)
    let initName="胡歌"
    
    let name = customRef((track,trigger) => {
      return {
        //get调用--数据被读取时
        get() {
          track()//先执行track(),告诉vue持续关注数据,一但变化就更新
          return initName
        },
        //set调用--数据被修改时
        set(value) {
          initName = value
          trigger() //数据修改后执行trigger(),通知vue数据变化了
        }
      }
    })
    </script>    
    
  • 实现防抖效果:
    <template>
      <div>
        <h2>{{ name }}</h2>
        <input v-model="name">
      </div>
    </template>
    
    <script lang='ts' setup>
    import {customRef} from 'vue';
    
    //使用customRef定义的响应式数据,实现修改数据,1s后视图上<h2>元素内容才更新
    let initName="胡歌"
    let timer:number
    let name = customRef((track,trigger) => {
      return {
        get() {
          track()
          return initName
        },
        set(value) {
          clearTimeout(timer)
          timer= setTimeout(() => {
            initName = value
            trigger() 
          }, 1000);
        }
      }
    })
    </script>    
    

4.【新组件:Teleport 】

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

<!-- teleport组件 to='放置在某个元素内'  -->
<teleport to='body'>
    <div class="modal" v-show="isShow">
      <h2>我是一个弹窗</h2>
      <p>我是弹窗中的一些内容</p>
      <button @click="isShow = false">关闭弹窗</button>
    </div>
</teleport>        

5.【新组件:Suspense 】

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

子组件

<template>
  <div>{{ num }}</div>
</template>

<script lang='ts' setup>
import { ref } from 'vue';

//模拟3s后获得数据,然后展示
//在setup函数中,使用了await,将组件变了从异步组件
let result= await new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 3000)
})
let num = ref(result)

</script>       

父组件

<template>
  <h2>我是父组件</h2>
  <Suspense>
    <!-- v-slot:default 真正要展示的组件-->
    <template v-slot:default>
      <Child/>
    </template>
    <!-- v-slot:fallback 等待时间中的提示组件-->
    <template v-slot:fallback>
      <h3>加载中.....</h3>
    </template>
  </Suspense>
  
</template>

<script lang='ts' setup>
//传统写法
// import { Suspense, defineAsyncComponent } from 'vue'
// const Child = defineAsyncComponent(() => import('./ChildTwo.vue'))

//语法糖简写
import Child from './ChildTwo.vue'
import { Suspense } from 'vue'

</script>     

6.【全局API转移到应用对象】

  • app.component:全局注册组件
import { createApp } from 'vue'
import App from './App.vue'
import Hello from '@/views/Child/ChildTwo.vue'

const app = createApp(App)
//app.component(组件名,引入的组件)
app.component('Hello',Hello)
app.mount('#app')        
  • app.config:配置全局属性
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
//app.config.globalProperties.xxx==内容
app.config.globalProperties.fruit = "苹果"
declare module 'vue' {
  interface ComponentCustomProperties {
    //ts要判断类型
    fruit:string
   }
}
app.mount('#app')        
  • app.directive:全局指令
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
//app.directive(指令名,(元素,{传入的值})=>{ xxx })        
app.directive('beauty', (element, { value}) => {
  element.innerText += value
  element.style.color='green'
  element.style.background='yellow'
})
app.mount('#app')        
<template>
  <!-- v-指令名=value -->  
  <h2 v-beauty="'苹果'">我最喜欢的水果--</h2>  
</template>        
  • app.mount:挂载

  • app.unmount:卸载

  • app.use :安装插件

十、vue3非兼容性改变

  • 过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from

  • keyCode 作为 v-on 修饰符的支持。

  • v-model 指令在组件上的使用已经被重新设计,替换掉了 v-bind.sync。

  • v-ifv-for 在同一个元素身上使用时的优先级发生了变化。

  • 移除了$on$off$once 实例方法。

  • 移除了过滤器 filter

  • 移除了$children 实例 propert