前端-Vue3

news2025/4/14 0:55:31

1. Vue3简介

  • 2020年9月18日,Vue.js发布版3.0版本,代号:One Piece(n

  • 经历了:4800+次提交、40+个RFC、600+次PR、300+贡献者

  • 官方发版地址:Release v3.0.0 One Piece · vuejs/core

  • 截止2023年10月,最新的公开版本为:3.3.4

1.1. 【性能的提升】

  • 打包大小减少41%

  • 初次渲染快55%, 更新渲染快133%

  • 内存减少54%

1.2.【 源码的升级】

  • 使用Proxy代替defineProperty实现响应式。

  • 重写虚拟DOM的实现和Tree-Shaking

1.3. 【拥抱TypeScript】

  • Vue3可以更好的支持TypeScript

1.4. 【新的特性】

  1. Composition API(组合API):

    • setup

    • refreactive

    • computedwatch

      ......

  2. 新的内置组件:

    • Fragment

    • Teleport

    • Suspense

      ......

  3. 其他改变:

    • 新的生命周期钩子

    • data 选项应始终被声明为一个函数

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

      ......

2. 创建Vue3工程

2.1. 【基于 vue-cli 创建】

点击查看官方文档

备注:目前vue-cli已处于维护模式,官方推荐基于 Vite 创建项目。

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
​
## 安装或者升级你的@vue/cli 
npm install -g @vue/cli
​
## 执行创建命令
vue create vue_test
​
##  随后选择3.x
##  Choose a version of Vue.js that you want to start the project with (Use arrow keys)
##  > 3.x
##    2.x
​
## 启动
cd vue_test
npm run serve

2.2. 【基于 vite 创建】(推荐)

vite 是新一代前端构建工具,官网地址:https://vitejs.cn,vite的优势如下:

  • 轻量快速的热重载(HMR),能实现极速的服务启动。

  • TypeScriptJSXCSS 等支持开箱即用。

  • 真正的按需编译,不再等待整个应用编译完成。

  • 具体操作如下(点击查看官方文档)

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

自己动手写一个app.vue

<template>
  <div class="app">
    <h1>你好啊!</h1>
  </div>
</template>

<script lang="ts">
  export default {
    name:'App' //组件名
  }
</script>

<style>
  .app {
    background-color: #ddd;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
  }
</style>

总结:

  • Vite 项目中,index.html 是项目的入口文件,在项目最外层。

  • 加载index.html后,Vite 解析 <script type="module" src="xxx"> 指向的JavaScript

  • Vue3中是通过 createApp 函数创建一个应用实例。

2.3. 【一个简单的效果】基于Vue2选项式编程

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

我们自己创建一个vue来呈现自己的效果

在src包的component创建Person.vue

<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导入这个vue

<template>
  <div class="app">
    <h1>你好啊!</h1>
  </div>
  <Person/> #使用刚刚我们创建的vue
</template>

<script lang="ts">

  import Person from './components/Person.vue'; //import
  export default {
    name:'App', //组件名
    components:{Person} //注册组件
  }
</script>

<style>
  .app {
    background-color: #ddd;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
  }
</style>

效果

3. Vue3核心语法

3.1. 【OptionsAPI 与 CompositionAPI】

  • Vue2API设计是Options(配置)选项式编程风格的。

  • Vue3API设计是Composition(组合)组合式风格的。

Options API 的弊端

Options类型的 API,数据、方法、计算属性等,是分散在:datamethodscomputed中的,若想新增或者修改一个需求,就需要分别修改:datamethodscomputed,不便于维护和复用。

Composition API 的优势

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

3.2. 【拉开序幕的 setup】

setup 概述

setupVue3中一个新的配置项,值是一个函数,它是 Composition API “表演的舞台,组件中所用到的:数据、方法、计算属性、监视......等等,均配置在setup中。

特点如下:

  • setup函数返回的对象中的内容,可直接在模板中使用。

  • setup中访问thisundefined。所以不能用this

  • setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的。

<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:'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 的返回值

  • 若返回一个对象:则对象中的:属性、方法等,在模板中均可以直接使用(重点关注)。

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

setup(){
  return ()=> '你好啊!'
}

setup 与 Options API 的关系

  • Vue2 的配置(datamethos......)中可以访问到 setup中的属性、方法。

  • 但在setup不能访问到Vue2的配置(datamethos......)。

  • 如果与Vue2冲突,则setup优先。

setup 语法糖

setup函数有一个语法糖,就是用来简化代码的。这个语法糖,可以让我们把setup独立出去,这样我们就不用手动写return将数据交到前端。代码如下:

<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
  
  // 数据(注意:此时的name、age、tel都不是响应式数据)
  let name = '张三'
  let age = 18
  let tel = '13888888888'
​
  // 方法
  function changName(){
    name = '李四'//注意:此时这么修改name页面是不变化的
  }
  function changAge(){
    console.log(age)
    age += 1 //注意:此时这么修改age页面是不变化的
  }
  function showTel(){
    alert(tel)
  }
</script>

扩展:上述代码,还需要编写一个不写setupscript标签,去指定组件名字,比较麻烦,我们可以借助vite中的插件简化

第一步:npm i vite-plugin-vue-setup-extend -D

第二步:vite.config.ts

import { defineConfig } from 'vite'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
​
export default defineConfig({
  plugins: [ VueSetupExtend() ]
})
  1. 第三步:将原来文件里面的setup的script改成<script setup lang="ts" name="Person">

3.3. 【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" name="Person">
  import {ref} from 'vue' //第一步要引入ref
  // 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.4. 【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
}
</script>

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

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

  • ref接收的是对象类型,内部其实也是调用了reactive函数。ref里面的value就是生成的proxy代理对象。所以我们用ref实现对象类型的响应式数据,要去访问对象的value,然后才是具体成员变量。

<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.6. 【ref 对比 reactive】

宏观角度看:

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

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

  • 区别:

  1. ref创建的变量必须使用.value(可以使用volar插件自动添加.value)。

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

  • 使用原则:

  1. 若需要一个基本类型的响应式数据,必须使用ref

  2. 若需要一个响应式对象,层级不深,refreactive都可以。

  3. 若需要一个响应式对象,且层级较深,推荐使用reactive

3.7. 【toRefs 与 toRef】

  • 作用:将一个响应式对象中的每一个属性,转换为ref对象。就是说,我可以将reactive包括的对象里面的每一组变量都变成ref对象。

  • 如果没有toRefs,那么我们直接let {age, name} = Person,那么实际上是执行了let age = Person.age, let name = Person.name,此时的age和name不是响应式的。

  • 备注: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>

效果

3.8. 【computed】

作用:根据已有数据计算出新数据(和Vue2中的computed作用一致)。

我们输入firstName和LastName就可以通过conputed得到fullName

<template>
  <div class="person">
    姓:<input type="text" v-model="firstName"> <br>
    名:<input type="text" v-model="lastName"> <br>
    <button @click="changeFullName">将全名改为li-si</button> <br>
    全名:<span>{{fullName}}</span> <br>
    全名:<span>{{fullName}}</span> <br>
    全名:<span>{{fullName}}</span> <br>
    全名:<span>{{fullName}}</span> <br>
    全名:<span>{{fullName}}</span> <br>
    全名:<span>{{fullName}}</span> <br>
  </div>
</template>

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

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

  // fullName是一个计算属性,且是只读的
  /* let fullName = computed(()=>{
    console.log(1)
    return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
  }) */

  // fullName是一个计算属性,可读可写
  let fullName = computed({
    // 当fullName被读取时,get调用
    get(){
      return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
    },
    // 当fullName被修改时,set调用,且会收到修改的值
    set(val){
      const [str1,str2] = val.split('-')
      firstName.value = str1
      lastName.value = str2
    }
  })

  function changeFullName(){
    fullName.value = 'li-si'
  }
</script>

<style scoped>
  .person {
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
  }
  button {
    margin: 0 5px;
  }
  li {
    font-size: 20px;
  }
</style>

3.9.【watch】

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

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

  1. ref定义的数据。

  2. reactive定义的数据。

  3. 函数返回一个值(getter函数)。

  4. 一个包含上述内容的数组。

我们在Vue3中使用watch的时候,通常会遇到以下几种情况:

情况一

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

注意watch的参数和函数的书写,他的返回值是停止监视函数。

<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定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视,就是watch配置第三个参数,这个参数就是用来配置配置选项的

注意:

  • 若修改的是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定义的【对象类型】数据,且默认开启了深度监视。

<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. 若该属性值不是【对象类型】,需要写成函数形式。官方说是getter函数,实际上就写一个函数return要监听的字段

  2. 若该属性值是依然是【对象类型】,可直接编,也可写成函数,建议写成函数。

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

<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)
  }) */
​
  // 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
  watch(()=>person.car,(newValue,oldValue)=>{
    console.log('person.car变化了',newValue,oldValue)
  },{deep:true})
</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>

3.10. 【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>

3.11. 【标签的 ref 属性】

作用:用于注册模板引用。我们获取模块可以用id,class,标签本身等等,这种就是模块的引用,ref和他们的功能相当

  • 用在普通DOM标签上,获取的是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>

用在组件标签上:

<!-- 父组件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>

3.12. 【props】

// 定义一个接口,限制每个Person对象的格式
export interface PersonInter {
id:string,
name:string,
 age:number
}
​
// 定义一个自定义类型Persons
export type Persons = Array<PersonInter>
App.vue中代码:

<template>
	<Person :list="persons"/>
</template>

<script lang="ts" setup name="App">
import Person from './components/Person.vue'
import {reactive} from 'vue'
 import {type Persons} from './types'

 let persons = reactive<Persons>([
  {id:'e98219e12',name:'张三',age:18},
   {id:'e98219e13',name:'李四',age:19},
    {id:'e98219e14',name:'王五',age:20}
  ])
</script>
Person.vue中代码:

<template>
<div class="person">
<ul>
  <li v-for="item in list" :key="item.id">
     {{item.name}}--{{item.age}}
   </li>
 </ul>
</div>
</template>
​
<script lang="ts" setup name="Person">
import {defineProps} from 'vue'
import {type PersonInter} from '@/types'
​
// 第一种写法:仅接收
// const props = defineProps(['list'])
​
// 第二种写法:接收+限制类型
// defineProps<{list:Persons}>()
​
// 第三种写法:接收+限制类型+指定默认值+限制必要性
let props = withDefaults(defineProps<{list?:Persons}>(),{
  list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]
})
console.log(props)
</script>

3.13. 【生命周期】

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

规律:

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

Vue2的生命周期

创建阶段:beforeCreatecreated

挂载阶段:beforeMountmounted

更新阶段:beforeUpdateupdated

销毁阶段:beforeDestroydestroyed

Vue3的生命周期

创建阶段:setup

挂载阶段:onBeforeMountonMounted

更新阶段:onBeforeUpdateonUpdated

卸载阶段:onBeforeUnmountonUnmounted

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

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

4. 路由

对路由的理解

Vue 3 中的路由(Vue Router)主要用途是实现单页面应用(SPA)的页面切换功能,同时提升用户体验和开发效率。以下是它的一些主要作用:

1. 实现页面切换而无需重新加载页面

  • 在传统的多页面应用(MPA)中,每次点击链接都会重新加载整个页面,这会导致页面闪烁、加载时间变长,并且用户体验较差。

  • Vue Router 通过在前端拦截链接跳转,动态切换页面内容,而无需重新加载整个页面。这样可以显著提升页面的响应速度和用户体验。

2. 支持动态路由和嵌套路由

  • 动态路由:允许在路径中传递参数。例如,/user/:id 可以通过路径动态传递用户 ID,方便实现用户详情页等功能。

  • 嵌套路由:可以定义组件内部的子路由,实现更复杂的页面结构。例如,一个用户页面可以包含多个子页面(如用户资料、用户订单等),通过嵌套路由可以方便地管理这些子页面。

3. 支持多种路由模式

  • History 模式:使用 HTML5 的 History API,URL 更友好(如 /about),适合需要更美观 URL 的场景。

  • Hash 模式:通过 URL 的哈希值(#)来实现路由切换(如 /#/about)。这种模式兼容性更好,适合一些老旧浏览器或搜索引擎优化要求不高的场景。

  • Abstract 模式:主要用于非浏览器环境(如 Node.js),适合一些特殊场景。

总的来说,Vue Router 是 Vue.js 中实现单页面应用的核心工具,它不仅提供了强大的路由功能,还提升了用户体验和开发效率。

一个路由切换的实现

先决条件,我们要去安装vue-router,要准备好!!!

最终实现的效果

我们有一个导航区,有一个展示区,还有一个标题头。导航区有三个组件,分别点击有高亮效果并实现切换。

子组件的定义

就是普通定义组件一样,这里面不涉及路由的知识

关于组件
<template>
  <div class="about">
    <h2>大家好,欢迎来到尚硅谷直播间</h2>
  </div>
</template>

<script setup lang="ts" name="About">

</script>

<style scoped>
.about {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  color: rgb(85, 84, 84);
  font-size: 18px;
}
</style>

首页组件
<template>
  <div class="home">
    <img src="http://www.atguigu.com/images/index_new/logo.png" alt="">
  </div>
</template>

<script setup lang="ts" name="Home">

</script>

<style scoped>
  .home {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;
  }
</style>

新闻组件
<template>
  <div class="news">
    <ul>
      <li><a href="#">新闻001</a></li>
      <li><a href="#">新闻002</a></li>
      <li><a href="#">新闻003</a></li>
      <li><a href="#">新闻004</a></li>
    </ul>
  </div>
</template>

<script setup lang="ts" name="News">
  
</script>

<style scoped>
/* 新闻 */
.news {
  padding: 0 20px;
  display: flex;
  justify-content: space-between;
  height: 100%;
}
.news ul {
  margin-top: 30px;
  list-style: none;
  padding-left: 10px;
}
.news li>a {
  font-size: 18px;
  line-height: 40px;
  text-decoration: none;
  color: #64967E;
  text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {
  width: 70%;
  height: 90%;
  border: 1px solid;
  margin-top: 20px;
  border-radius: 10px;
}
</style>

App.vue

<RouterLink><RouterView>
  • <RouterLink>

    • 用于创建导航链接,类似于HTML中的<a>标签,但专门用于Vue Router。

    • 在代码中,<RouterLink>用于定义导航菜单,用户可以通过点击这些链接在不同的路由之间切换。

    • to属性指定了链接的目标路由路径,例如to="/home"表示导航到/home路径。

    • active-class属性用于自定义激活状态的链接的CSS类名。在这个例子中,当链接被激活时,会应用xiaozhupeiqi类。

  • <RouterView>

    • 用于显示当前路由对应的组件内容,也就是给对应组件显示占个位,告诉vue组件在这里显示

    • 在代码中,<RouterView>位于<div class="main-content">中,用于展示当前路由匹配的组件。

<template>
    <div class="app">
      <h2 class="title">Vue路由测试</h2>
      <!-- 导航区 -->
      <div class="navigate">
        <RouterLink to="/home" active-class="xiaozhupeiqi">首页</RouterLink>
        <RouterLink to="/news" active-class="xiaozhupeiqi">新闻</RouterLink>
        <RouterLink to="/about" active-class="xiaozhupeiqi">关于</RouterLink>
      </div>
      <!-- 展示区 -->
      <div class="main-content">
        <RouterView></RouterView>
      </div>
    </div>
  </template>
  
  <script lang="ts" setup name="App">
    import {RouterView,RouterLink} from 'vue-router'
  
  </script>
  
  <style>
      /* App */
    .title {
      text-align: center;
      word-spacing: 5px;
      margin: 30px 0;
      height: 70px;
      line-height: 70px;
      background-image: linear-gradient(45deg, gray, white);
      border-radius: 10px;
      box-shadow: 0 0 2px;
      font-size: 30px;
    }
    .navigate {
      display: flex;
      justify-content: space-around;
      margin: 0 100px;
    }
    .navigate a {
      display: block;
      text-align: center;
      width: 90px;
      height: 40px;
      line-height: 40px;
      border-radius: 10px;
      background-color: gray;
      text-decoration: none;
      color: white;
      font-size: 18px;
      letter-spacing: 5px;
    }
    .navigate a.xiaozhupeiqi {
      background-color: #64967E;
      color: #ffc268;
      font-weight: 900;
      text-shadow: 0 0 1px black;
      font-family: 微软雅黑;
    }
    .main-content {
      margin: 0 auto;
      margin-top: 30px;
      border-radius: 10px;
      width: 90%;
      height: 400px;
      border: 1px solid;
    }
  </style>
  

index.ts

我们在src包下创建一个router包,然后下面创建一个index.ts文件。在这里我们配置路由的信息

直接看下面的代码,看看是怎么创建路由的

包括创建路由实例,设置路由模式,配置路由规则,最后将路由暴露出去

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

// 第一步:引入createRouter
import {createRouter,createWebHistory} from 'vue-router'
// 引入一个一个可能要呈现组件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'

// 第二步:创建路由器
const router = createRouter({
  history:createWebHistory(), //路由器的工作模式(稍后讲解)
  routes:[ //一个一个的路由规则
    {
      path:'/home',
      component:Home
    },
    {
      path:'/news',
      component:News
    },
    {
      path:'/about',
      component:About
    },
  ]
})

// 暴露出去router
export default router

main.ts

这里我们引入并使用路由

// 引入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')

这样,一个路由切换的实例就做好了

路由器的工作模式

1. 哈希模式(Hash Mode)

特点

  • URL 中包含一个 # 符号,例如:http://example.com/#/home

  • 路由的变化不会导致页面重新加载,因为浏览器不会将 # 后面的内容发送到服务器。

  • 适合需要兼容旧浏览器的应用,因为这种模式不需要服务器支持。

实现

默认情况下,Vue Router 使用哈希模式。可以通过以下代码显式指定:

import { createRouter, createWebHashHistory } from 'vue-router';
​
const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    { path: '/home', component: Home },
    { path: '/news', component: News },
    { path: '/about', component: About },
  ],
});

优点

  • 无需服务器配置:因为 URL 中的 # 郜部分不会发送到服务器,所以不需要服务器支持。

  • 兼容性好:几乎所有浏览器都支持哈希模式。

缺点

  • URL 不够美观:URL 中包含 # 符号,可能会影响用户体验。

  • 搜索引擎优化(SEO):虽然现代搜索引擎可以处理哈希模式的 URL,但 HTML5 历史模式的 URL 更友好。

2. HTML5 历史模式(History Mode)

特点

  • URL 看起来更像传统的 Web 应用,例如:http://example.com/home

  • 路由的变化不会导致页面重新加载,但需要服务器支持。

  • 适合现代的单页应用(SPA),因为 URL 更友好,用户体验更好。

实现

可以通过以下代码指定使用 HTML5 历史模式:

import { createRouter, createWebHistory } from 'vue-router';
​
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/home', component: Home },
    { path: '/news', component: News },
    { path: '/about', component: About },
  ],
});

优点

  • URL 更美观:没有 # 符号,URL 更符合传统 Web 应用的风格。

  • 搜索引擎优化(SEO):更友好的 URL 结构有助于搜索引擎优化。

  • 用户体验更好:用户更习惯没有 # 的 URL。

缺点

  • 需要服务器支持:服务器需要配置,以便正确处理路由变化。如果服务器没有正确配置,可能会导致 404 错误。

  • 配置复杂:需要额外的服务器配置,增加了开发和部署的复杂性。

to的两种写法

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

<!-- 第二种:to的对象写法 -->
<router-link active-class="active" :to="{path:'/home'}">Home</router-link>

命名路由

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

给路由规则命名:

routes:[
  {
    name:'zhuye',
    path:'/home',
    component:Home
  },
  {
    name:'xinwen',
    path:'/news',
    component:News,
  },
  {
    name:'guanyu',
    path:'/about',
    component:About
  }
]

跳转路由:

<!--简化前:需要写完整的路径(to的字符串写法) -->
<router-link to="/news/detail">跳转</router-link>

<!--简化后:直接通过名字跳转(to的对象写法配合name属性) -->
<router-link :to="{name:'guanyu'}">跳转</router-link>

嵌套路由

预期实现

我们想实现这样的功能:当点击新闻页面的时候,左侧是新闻,右侧是内容。简单的来说,我们想新闻下面再嵌套一个vue组件,加一个路由

定义Detail组件

<template>
  <ul class="news-list">
    <li>编号:xxx</li>
    <li>标题:xxx</li>
    <li>内容:xxx</li>
  </ul>
</template>

<script setup lang="ts" name="About">

</script>

<style scoped>
  .news-list {
    list-style: none;
    padding-left: 20px;
  }

  .news-list>li {
    line-height: 30px;
  }
</style>

重新编写news.vue

原本全是展示区的news现在有一半是导航区一半是展示区。记得RouterView和RouterLink

<template>
  <div class="news">
    <!-- 导航区 -->
    <ul>
      <li v-for="news in newsList" :key="news.id">
        <RouterLink to="/news/detail">{{news.title}}</RouterLink>
      </li>
    </ul>
    <!-- 展示区 -->
    <div class="news-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

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

  const newsList = reactive([
    {id:'asfdtrfay01',title:'很好的抗癌食物',content:'西蓝花'},
    {id:'asfdtrfay02',title:'如何一夜暴富',content:'学IT'},
    {id:'asfdtrfay03',title:'震惊,万万没想到',content:'明天是周一'},
    {id:'asfdtrfay04',title:'好消息!好消息!',content:'快过年了'}
  ])

</script>

<style scoped>
/* 新闻 */
.news {
  padding: 0 20px;
  display: flex;
  justify-content: space-between;
  height: 100%;
}
.news ul {
  margin-top: 30px;
  list-style: none;
  padding-left: 10px;
}
.news li>a {
  font-size: 18px;
  line-height: 40px;
  text-decoration: none;
  color: #64967E;
  text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {
  width: 70%;
  height: 90%;
  border: 1px solid;
  margin-top: 20px;
  border-radius: 10px;
}
</style>

router包下的index.js

引入Detail,并定义在news下面,注意语法,记得子组件前面不要    "   /   "

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

// 第一步:引入createRouter
import {createRouter,createWebHistory} from 'vue-router'
// 引入一个一个可能要呈现组件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
import Detail from '@/components/Detail.vue'

// 第二步:创建路由器
const router = createRouter({
  history:createWebHistory(), //路由器的工作模式(稍后讲解)
  routes:[ //一个一个的路由规则
    {
      name:'home',
      path:'/home',
      component:Home
    },
    {
      path:'/news',
      component:News,
      children:[
        {
          path:'detail',
          component:Detail
        }
      ]
    },
    {
      path:'/about',
      component:About
    },
  ]
})

// 暴露出去router
export default router

路由传参

query参数

我们在上面的案例的基础上加码,我们要实现,点击新闻都每一个新闻,右边的编号那些都会显示对应的信息

改装news

注意传参的写法,query是参数,路径也是参数

<template>
  <div class="news">
    <!-- 导航区 -->
    <ul>
      <li v-for="news in newsList" :key="news.id">
        <!-- 第一种写法 -->
        <!-- <RouterLink :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`">{{news.title}}</RouterLink> -->

        <!-- 第二种写法 -->
        <RouterLink 
          :to="{
            path:'/news/detail',
            query:{
              id:news.id,
              title:news.title,
              content:news.content
            }
          }"
        >
          {{news.title}}
        </RouterLink>

      </li>
    </ul>
    <!-- 展示区 -->
    <div class="news-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

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

  const newsList = reactive([
    {id:'asfdtrfay01',title:'很好的抗癌食物',content:'西蓝花'},
    {id:'asfdtrfay02',title:'如何一夜暴富',content:'学IT'},
    {id:'asfdtrfay03',title:'震惊,万万没想到',content:'明天是周一'},
    {id:'asfdtrfay04',title:'好消息!好消息!',content:'快过年了'}
  ])

</script>

<style scoped>
/* 新闻 */
.news {
  padding: 0 20px;
  display: flex;
  justify-content: space-between;
  height: 100%;
}
.news ul {
  margin-top: 30px;
  /* list-style: none; */
  padding-left: 10px;
}
.news li::marker {
  color: #64967E;
}
.news li>a {
  font-size: 18px;
  line-height: 40px;
  text-decoration: none;
  color: #64967E;
  text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {
  width: 70%;
  height: 90%;
  border: 1px solid;
  margin-top: 20px;
  border-radius: 10px;
}
</style>

改装detail

接受父组件传来的参数,记得不要直接赋值,要先转换成动态的

<template>
  <ul class="news-list">
    <li>编号:{{query.id}}</li>
    <li>标题:{{query.title}}</li>
    <li>内容:{{query.content}}</li>
  </ul>
</template>

<script setup lang="ts" name="About">
  import {useRoute} from 'vue-router'
  import {toRefs} from 'vue'



  let route = useRoute()
  
  let {query} = toRefs(route)
  console.log(query)

</script>

<style scoped>
  .news-list {
    list-style: none;
    padding-left: 20px;
  }

  .news-list>li {
    line-height: 30px;
  }
</style>

params参数

我们还原到query的前面,这次我们换一个参数来传参,同样实现query的效果

修改news

传递params参数时,若使用to的对象写法,必须使用name配置项,不能用path!!!要用在路由中路径对应起的名字

<template>
  <div class="news">
    <!-- 导航区 -->
    <ul>
      <li v-for="news in newsList" :key="news.id">
        
        <!-- 第二种写法 -->
        <RouterLink 
          :to="{
            name:'xijie',
            params:{
              id: news.id,
              title: news.title,
              content: news.content
            }
          }"
        >
          {{news.title}}
        </RouterLink>

      </li>
    </ul>
    <!-- 展示区 -->
    <div class="news-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

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

  const newsList = reactive([
    {id:'asfdtrfay01',title:'很好的抗癌食物',content:'西蓝花'},
    {id:'asfdtrfay02',title:'如何一夜暴富',content:'学IT'},
    {id:'asfdtrfay03',title:'震惊,万万没想到',content:'明天是周一'},
    {id:'asfdtrfay04',title:'好消息!好消息!',content:'快过年了'}
  ])

</script>

<style scoped>
/* 新闻 */
.news {
  padding: 0 20px;
  display: flex;
  justify-content: space-between;
  height: 100%;
}
.news ul {
  margin-top: 30px;
  /* list-style: none; */
  padding-left: 10px;
}
.news li::marker {
  color: #64967E;
}
.news li>a {
  font-size: 18px;
  line-height: 40px;
  text-decoration: none;
  color: #64967E;
  text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {
  width: 70%;
  height: 90%;
  border: 1px solid;
  margin-top: 20px;
  border-radius: 10px;
}
</style>

修改index.ts

传递params参数时,需要提前在规则中占位。记得起名字,也就是配置name属性

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

// 第一步:引入createRouter
import {createRouter,createWebHistory} from 'vue-router'
// 引入一个一个可能要呈现组件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
import Detail from '@/components/Detail.vue'

// 第二步:创建路由器
const router = createRouter({
  history:createWebHistory(), //路由器的工作模式(稍后讲解)
  routes:[ //一个一个的路由规则
    {
      name:'home',
      path:'/home',
      component:Home
    },
    {
      path:'/news',
      component:News,
      children:[
        {
          name:'xijie',
          path:'detail/:id/:title/:content',
          component:Detail
        }
      ]
    },
    {
      path:'/about',
      component:About
    },
  ]
})

// 暴露出去router
export default router

修改detail

注意在news中已经将参数放到route里面了

<template>
  <ul class="news-list">
    <li>编号:{{ route.params.id }}</li>
    <li>标题:{{ route.params.title }}</li>
    <li>内容:{{ route.params.content }}</li>
  </ul>
</template>

<script setup lang="ts" name="About">
  import {useRoute} from 'vue-router'
  import {toRefs} from 'vue'



  let route = useRoute();
  console.log(route.params);
  


</script>

<style scoped>
  .news-list {
    list-style: none;
    padding-left: 20px;
  }

  .news-list>li {
    line-height: 30px;
  }
</style>

路由的props配置

再次代码恢复到query的时候,我还是想实现query的需求。这是第三种方法。传参的方法。

既然props是路由的配置,那么就是在路由上做文章的。

注意props有三种写法,这里演示两种

写法1

修改index.ts

第一种就是直接让props为true,那么他就是默认的传法,传的是params。

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

// 第一步:引入createRouter
import { createRouter, createWebHistory } from 'vue-router'
// 引入一个一个可能要呈现组件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
import Detail from '@/components/Detail.vue'

// 第二步:创建路由器
const router = createRouter({
  history: createWebHistory(), //路由器的工作模式(稍后讲解)
  routes: [ //一个一个的路由规则
    {
      name: 'home',
      path: '/home',
      component: Home
    },
    {
      path: '/news',
      component: News,
      children: [
        {
          name: 'xijie',
          path: 'detail/:id/:title/:content',
          component: Detail,

          props: true

          // props(route){
          //   return route.query
          // }
        }
      ]
    },
    {
      path: '/about',
      component: About
    },
  ]
})

// 暴露出去router
export default router

detail

<template>
  <ul class="news-list">
    <li>编号:{{ id }}</li>
    <li>标题:{{ title }}</li>
    <li>内容:{{ content }}</li>
  </ul>
</template>

<script setup lang="ts" name="About">

  defineProps(['id','title','content'])
  


</script>

<style scoped>
  .news-list {
    list-style: none;
    padding-left: 20px;
  }

  .news-list>li {
    line-height: 30px;
  }
</style>

news

将路由传参设置为params

<template>
  <div class="news">
    <!-- 导航区 -->
    <ul>
      <li v-for="news in newsList" :key="news.id">
        
        <!-- 第二种写法 -->
        <RouterLink 
          :to="{
            name:'xijie',
            params:{
              id: news.id,
              title: news.title,
              content: news.content
            }
          }"
        >
          {{news.title}}
        </RouterLink>

      </li>
    </ul>
    <!-- 展示区 -->
    <div class="news-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

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

  const newsList = reactive([
    {id:'asfdtrfay01',title:'很好的抗癌食物',content:'西蓝花'},
    {id:'asfdtrfay02',title:'如何一夜暴富',content:'学IT'},
    {id:'asfdtrfay03',title:'震惊,万万没想到',content:'明天是周一'},
    {id:'asfdtrfay04',title:'好消息!好消息!',content:'快过年了'}
  ])

</script>

<style scoped>
/* 新闻 */
.news {
  padding: 0 20px;
  display: flex;
  justify-content: space-between;
  height: 100%;
}
.news ul {
  margin-top: 30px;
  /* list-style: none; */
  padding-left: 10px;
}
.news li::marker {
  color: #64967E;
}
.news li>a {
  font-size: 18px;
  line-height: 40px;
  text-decoration: none;
  color: #64967E;
  text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {
  width: 70%;
  height: 90%;
  border: 1px solid;
  margin-top: 20px;
  border-radius: 10px;
}
</style>

写法2

修改index.js

第二种就是将props写成函数,函数的形参就是route,那么我们就可以返回route里面的东西,包括params和query

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

// 第一步:引入createRouter
import { createRouter, createWebHistory } from 'vue-router'
// 引入一个一个可能要呈现组件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
import Detail from '@/components/Detail.vue'

// 第二步:创建路由器
const router = createRouter({
  history: createWebHistory(), //路由器的工作模式(稍后讲解)
  routes: [ //一个一个的路由规则
    {
      name: 'home',
      path: '/home',
      component: Home
    },
    {
      path: '/news',
      component: News,
      children: [
        {
          name: 'xijie',
          path: 'detail',
          component: Detail,

          // props: true

          props(route){
            return route.query
          }
        }
      ]
    },
    {
      path: '/about',
      component: About
    },
  ]
})

// 暴露出去router
export default router

修改news

将路由传参设置为query

<template>
  <div class="news">
    <!-- 导航区 -->
    <ul>
      <li v-for="news in newsList" :key="news.id">
        
        <!-- 第二种写法 -->
        <RouterLink 
          :to="{
            name:'xijie',
            query:{
              id: news.id,
              title: news.title,
              content: news.content
            }
          }"
        >
          {{news.title}}
        </RouterLink>

      </li>
    </ul>
    <!-- 展示区 -->
    <div class="news-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

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

  const newsList = reactive([
    {id:'asfdtrfay01',title:'很好的抗癌食物',content:'西蓝花'},
    {id:'asfdtrfay02',title:'如何一夜暴富',content:'学IT'},
    {id:'asfdtrfay03',title:'震惊,万万没想到',content:'明天是周一'},
    {id:'asfdtrfay04',title:'好消息!好消息!',content:'快过年了'}
  ])

</script>

<style scoped>
/* 新闻 */
.news {
  padding: 0 20px;
  display: flex;
  justify-content: space-between;
  height: 100%;
}
.news ul {
  margin-top: 30px;
  /* list-style: none; */
  padding-left: 10px;
}
.news li::marker {
  color: #64967E;
}
.news li>a {
  font-size: 18px;
  line-height: 40px;
  text-decoration: none;
  color: #64967E;
  text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {
  width: 70%;
  height: 90%;
  border: 1px solid;
  margin-top: 20px;
  border-radius: 10px;
}
</style>

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

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

相关文章

Facebook账号类型一览

对于跨境出海从业者来说&#xff0c;Facebook是必不可少的内容营销和广告投放平台。针对Facebook的营销策略和发挥空间都很丰富&#xff0c;因此了解Facebook账号的类型、特点、适用场景和相关工具还是很有用的。 一、账号类型及特点 1.小黑号 无主页、无好友、无历史操作&am…

Java学习手册:Java基本语法与数据类型

Java语言以其简洁明了的语法和强大的数据类型系统而闻名。掌握Java的基本语法和数据类型是成为一名合格Java开发者的第一步。本文将深入探讨Java的基本语法结构和数据类型&#xff0c;帮助读者打下坚实的基础。 Java的基本语法 Java语言的语法设计简洁而强大&#xff0c;强调…

通过扣子平台将数据写入飞书多维表格

目录 1.1 创建飞书开放平台应用 1.2 创建飞书多维表格 1.3 创建扣子平台插件 1.1 创建飞书开放平台应用 1.1.1 打开地址&#xff1a;飞书开放平台&#xff0c;点击创建应用 注&#xff1a;商店应用需要申请ISV资质&#xff0c;填写企业主体信息&#xff0c;个人的话&#x…

C++-Mongoose(2)-https-server-openssl

OpenSSL生成HTTPS自签名证书 - 简书 1.Openssl windowsubuntu下载http://www.openssl.vip/download1.VS2019编译OpenSSL 2.VS2019编译第一个OpenSSL项目 1.ubuntu编译OpenSSL 3.0 2.编写第一个OpenSSL 1.windows下编译OpenSSL 安装vs2019 perl nasm安装activePerl…

服务器DNS失效

服务器异常 xx.t.RequestException: java.net.UnknownHostException: test.ac.xxxx.cn现象分析 本地测试正常&#xff0c;说明域名本身无问题。服务器 DNS 解析异常&#xff0c;导致 UnknownHostException。**服务器可正常解析 ****baidu.com**&#xff0c;说明网络正常&#…

用excel做九乘九乘法表

公式&#xff1a; IF($A2>B 1 , 1, 1,A2 & “" & B$1 & “” & $A2B$1,”")

企业数据安全如何保障?深度解析AIGC系统源码本地化部署

—从数据加密到权限管控&#xff0c;构建企业级AI安全防线 企业AIGC面临的5大数据安全风险 1. 数据出境违规 典型场景&#xff1a; 使用ChatGPT处理客户信息 → 数据经美国服务器中转 → 违反《个人信息保护法》第38条某金融公司因通过Midjourney生成宣传图&#xff0c;导致产…

企业指标设计方法指南

该文档聚焦企业指标设计方法,适用于企业中负责战略规划、业务运营、数据分析、指标管理等相关工作的人员,如企业高管、部门经理、数据分析师等。 主要内容围绕指标设计展开:首先指出指标设计面临的困境,包括权责不清、口径不统一、缺乏标准规范、报表体系混乱、指标…

CSS学习02 动态列数表格开发,解决多组数据布局与边框重合问题

概要 在前端开发中&#xff0c;表格常用于展示结构化数据。当数据组的字段数量不统一时&#xff08;如有的行包含 3 组数据&#xff0c;有的行包含 2 组或 1 组&#xff09;&#xff0c;传统固定列数的表格会出现结构错位、边框重合等问题。本文通过 HTML/CSS 规范方法&#x…

大唐杯省赛安排来了!还有7天,该如何准备?

(一&#xff09;赛道一:工程实践赛 1、理论赛阶段由参赛队伍使用两台电脑分别登录学唐平台作答&#xff0c;仿真实践赛阶段为参赛队伍共用一台电脑&#xff0c;以竞赛小组方式共同作答&#xff08;按照报名顺序&#xff0c;用第1选手账号登录仿真平台&#xff09;。最终统计理…

iframe学习与应用场景指南

一、iframe核心原理与学习路径 1. 嵌套网站的本质原理 技术特性&#xff1a; • 浏览器为每个iframe创建独立的window对象和DOM环境 • 资源独立加载&#xff1a;子页面拥有自己的CSS/JS/Cookie作用域 • 跨域限制&#xff1a;同源策略下无法直接访问DOM&#xff08;需CORS或…

WebGL数学手记:矩阵基础

一、矩阵的定义 矩阵&#xff0c;数学术语。在数学中&#xff0c;矩阵&#xff08;Matrix&#xff09;是一个按照长方阵列排列的复数或实数集合。 1.英文发音&#xff08;Matrix&#xff09; Matrix的发音类似于中文的[美吹克斯]&#xff0c;知道它的发音。方便后期看教程时…

IO流——字符输入输出流:FileReader FileWriter

一、文件字符输入流&#xff1a;FileReader 作用&#xff1a;以内存为基准&#xff0c;可以把文件中的数据以字符的形式读入到内存中去 public class Test5 {public static void main(String[] args) {try (Reader fr new FileReader("E:\\IDEA\\JavaCodeAll\\file-io-t…

Graphpad Prism for Mac医学绘图

Graphpad Prism for Mac医学绘图 一、介绍 GraphPad Prism for Mac是一款功能强大、易于使用的科学和统计分析软件&#xff0c;适用于各种类型的数据处理和可视化需求。无论您是进行基础研究、临床试验还是学术写作&#xff0c;GraphPad Prism for Mac都能为您短时间内做出最…

使用人工智能大模型腾讯元宝,如何免费快速做高质量的新闻稿?

今天我们学习使用人工智能大模型腾讯元宝&#xff0c;如何免费快速做高质量的新闻稿&#xff1f; 手把手学习视频地址&#xff1a;https://edu.csdn.net/learn/40402/666431 第一步在腾讯元宝对话框中输入如何协助老师做新闻稿&#xff0c;通过提问&#xff0c;我们了解了老师…

破解root密码

一、背景&#xff1a; 必须是服务器的管理者&#xff0c;涉及重启服务器 二、破解过程&#xff1a; 1)重启系统,进入 救援模式 开机过程中&#xff0c;按e进入救援模式 在linux开头的该行&#xff0c;将此行的ro修改为rw 然后空格输入 rd.break 按 ctrl x 启动&#xff0c;…

嵌入式---烧录器

一、核心定义与本质功能 烧录器&#xff08;Programmer&#xff09;是一种将用户编写的程序代码&#xff08;如.hex/.bin文件&#xff09;写入单片机内部存储器&#xff08;Flash/EEPROM/ROM&#xff09;的专用工具&#xff0c;核心功能包括&#xff1a; 程序烧写&#xff1a…

swift菜鸟教程14(闭包)

一个朴实无华的目录 今日学习内容&#xff1a;1.Swift 闭包1.1闭包定义1.2闭包实例1.3闭包表达式1.3.1sorted 方法&#xff1a;据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。1.3.2参数名称缩写&#xff1a;直接通过$0,$1,$2来顺序调用闭包的参数。1.3.3运算符函…

新手宝塔部署thinkphp一步到位

目录 一、下载对应配置 二、加载数据库 三、添加FTP​ 四、上传项目到宝塔​ 五、添加站点​ 六、配置伪静态 七、其他配置 开启监控 八、常见错误 一、打开宝塔页面&#xff0c;下载对应配置。 二、加载数据库 从本地导入数据库文件 三、添加FTP 四、上传项目到宝塔…

基于STM32 的实时FFT处理(Matlab+MDK5)

目录 一、 任务介绍二、基本原理三、软件仿真3.1 软件仿真基本原理3.2 软件仿真序列的软件模拟 四、模拟测试五、 附加题六、 源码 一、 任务介绍 1、在硬件平台上实现 FFT 算法&#xff1b; 2、模拟数据&#xff0c;通过 FFT 算法进行谱分析&#xff1b; 3、测定 PWM 输出方波…