当前位置: 首页 > news >正文

前端-Vue3

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'; //importexport 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 = 18let 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 = 18let 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.tsimport { 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对象时候需要.valuename.value = '李四'console.log(name.value)
​// 注意:name不是响应式的,name.value是响应式的,所以如下代码并不会引起页面的更新。// name = ref('zhang-san')}function changeAge(){// JS中操作ref对象时候需要.valueage.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 = str1lastName.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、heightwatch([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: trueprops(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>

相关文章:

前端-Vue3

1. Vue3简介 2020年9月18日&#xff0c;Vue.js发布版3.0版本&#xff0c;代号&#xff1a;One Piece&#xff08;n 经历了&#xff1a;4800次提交、40个RFC、600次PR、300贡献者 官方发版地址&#xff1a;Release v3.0.0 One Piece vuejs/core 截止2023年10月&#xff0c;最…...

【毕设通关】——文献查阅

目录 &#x1f552; 1. 常见文献库&#x1f552; 2. 快速寻找文献&#x1f552; 3. 引用网页资料 &#x1f552; 1. 常见文献库 中文文献&#xff1a; CNKI中国知网万方数据维普资讯超星期刊 英文文献&#xff1a; 谷歌学术Sci-hubOALib&#xff08;Open Access Library&am…...

贪心算法-跳跃游戏

55.跳跃游戏 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。输入&#xf…...

程序化广告行业(75/89):行业发展与PC端和移动端投放差异

程序化广告行业&#xff08;75/89&#xff09;&#xff1a;行业发展与PC端和移动端投放差异 在互联网广告领域&#xff0c;程序化广告正发挥着越来越重要的作用。今天&#xff0c;咱们就来一起深入了解一下程序化广告行业&#xff0c;希望能和大家一起学习进步&#xff0c;共同…...

Windows 下搭建 Git 本地服务器并进行开发

目录 1. 安装 Git for Windows 2. 创建本地 Git 仓库&#xff08;裸仓库&#xff09; 3. 设置本地开发环境 (1) 在开发机器上克隆仓库 (2) 提交代码 4. 远程访问本地 Git 服务器 方法 1&#xff1a;使用 SSH 访问 (1) 配置 SSH 服务器 (2) 客户端连接 方法 2&#xf…...

C++中stack函数的用法示例

C++中stack函数的用法示例 std::stack 是C++标准模板库(STL)中的一个容器适配器,它提供了后进先出(LIFO)的数据结构。以下是stack的一些常用函数及其用法示例: 1. 基本操作 #include <iostream> #include <stack>int main() {// 创建一个整数栈std::stack<…...

网络安全1

一、网络安全的定义与重要性 定义 网络安全&#xff08;信息技术安全&#xff09;&#xff1a;保护计算机系统和网络免受电子攻击的技术和过程&#xff0c;包括保护个人信息和企业数据不被盗窃、破坏或非法访问。涵盖范围&#xff1a;网络设备、数据传输、系统运行安全。 重要…...

腾讯会议for flatpak

今天想切换opensuse&#xff0c;无意间查询了腾讯会议等软件的下载方式&#xff0c;发现腾讯会议是deb包安装的。但是过程中&#xff0c;我发现flatpak仓库中居然有腾讯会议&#xff0c;然后尝试着在wayland下的debian12中进行了安装。 发现居然可以正常开启视频等操作。 下载…...

【C++初学】C++核心编程技术详解(二):类与继承

函数提高 3.1 函数默认参数 函数的形参可以有默认值&#xff0c;调用时可以省略这些参数。 示例代码&#xff1a; int func(int a, int b 10, int c 10) {return a b c; }int main() {cout << "ret " << func(20, 20) << endl;cout <<…...

紧急任务插入,如何不影响原计划

面对紧急任务插入&#xff0c;为确保不影响原计划&#xff0c;需要做到&#xff1a;重新明确任务优先级、合理调整资源配置、灵活管理时间和任务、建立紧急任务处理流程、有效沟通与反馈。其中&#xff0c;重新明确任务优先级最为关键。这可以帮助项目团队快速确定哪些任务需立…...

蓝桥杯赛前题

开始每个人能量为3 答题了&#xff0c;答题者1 扣分最后算 #include<bits/stdc.h> using namespace std;const int N1e510; int a[N]; int main(){int n,k,q;cin>>n>>k>>q;for(int i1;i<n;i){a[i]k; }for(int i1;i<q;i){int x;cin>>x;a[…...

ETL的核心概念与价值

在数字化转型的浪潮中&#xff0c;数据已成为企业决策的基石。然而&#xff0c;面对分散、异构且质量参差不齐的数据源&#xff0c;如何高效整合并挖掘其价值&#xff1f;这一问题的答案&#xff0c;指向了一项关键的技术——ETL&#xff08;Extract-Transform-Load&#xff09…...

神经动力学系统与计算及AI拓展

大脑&#xff0c;一个蕴藏在我们颅骨之内的宇宙&#xff0c;以活动脉动&#xff0c;如同由电信号和化学信号编织而成的交响乐&#xff0c;精巧地协调着思想、情感和行为。但是&#xff0c;这种复杂的神经元舞蹈是如何产生我们丰富多彩的精神生活的呢&#xff1f;这正是神经动力…...

力扣DAY46-50 | 热100 | 二叉树:展开为链表、pre+inorder构建、路径总和、最近公共祖先、最大路径和

前言 中等 、困难 √&#xff0c;越来越有手感了&#xff0c;二叉树done&#xff01; 二叉树展开为链表 我的题解 前序遍历树&#xff0c;当遇到左子树为空时&#xff0c;栈里pop节点&#xff0c;取右子树接到左子树位置&#xff0c;同时断开该右子树与父节点的连接&#x…...

备赛蓝桥杯-Python-考前突击

额&#xff0c;&#xff0c;离蓝桥杯开赛还有十个小时&#xff0c;最近因为考研复习节奏的问题&#xff0c;把蓝桥杯的优先级后置了&#xff0c;突然才想起来还有一个蓝桥杯呢。。 到目前为止python基本语法熟练了&#xff0c;再补充一些常用函数供明天考前再背背&#xff0c;算…...

蓝桥杯 Web 方向入门指南:从基础到实战

一、蓝桥杯 Web 方向简介 蓝桥杯是国内最具影响力的编程竞赛之一&#xff0c;Web 方向主要考察前端开发和后端服务能力。比赛形式为 4 小时限时编程&#xff0c;题型包括页面布局、数据交互、API 开发等。根据最新大纲&#xff0c;大学组需掌握 HTML5、CSS3、JavaScript、Vue.…...

生命篇---心肺复苏、AED除颤仪使用、海姆立克急救法、常见情况急救简介

生命篇—心肺复苏、AED除颤仪使用、海姆立克急救法、常见情况急救简介 文章目录 生命篇---心肺复苏、AED除颤仪使用、海姆立克急救法、常见情况急救简介一、前言二、急救1、心肺复苏&#xff08;CPR&#xff09;&#xff08;1&#xff09;适用情况&#xff08;2&#xff09;操作…...

C++Cherno 学习笔记day19 [76]-[80] std::optional、variant、any

b站Cherno的课[76]-[80] 一、如何处理OPTIONAL数据 std::optional二、单一变量存放多类型的数据 std::variant三、如何存储任意类型的数据 std::any四、如何让C运行得更快五、如何让C字符串更快 一、如何处理OPTIONAL数据 std::optional std::optional C17 数据是否存在是可选…...

【Python Requests 库详解】

目录 简介一、安装与导入安装导入 二、发送 HTTP 请求1. GET 请求基本请求URL 参数 2. POST 请求表单数据提交JSON 数据提交文件上传 3. 其他方法PUT 请求示例DELETE 请求示例 三、处理响应1. 响应内容解析文本内容处理二进制内容处理JSON 数据处理 2. 响应状态与头信息状态码检…...

二维偏序-蓝桥20102,没写完

代码&#xff1a; 暴力&#xff1a;只过了35%,双重for循环o(n^2)1e10; #include <iostream> #include <bits/stdc.h> using namespace std; const int N1e910; typedef long long LL; typedef pair<int,int> PII;int main() {// 请在此输入您的代码//相当于…...

ASEG的鉴定

等位基因特异性表达(Allele-Specific Expression, ASE)基因的鉴定是研究杂种优势和基因表达调控的重要手段。以下是鉴定ASE基因的详细流程和方法: ### **1. 实验设计与样本准备** - **选择材料**:选择杂交种及其亲本作为研究材料。例如,玉米中的B73和Mo17及其杂交组合B73…...

从零开始:在 GrapesJS Style Manager 中新增 row-gap 和 column-gap

在前端开发中&#xff0c;页面样式的灵活性和可扩展性至关重要。GrapesJS 作为一个强大的网页构建工具&#xff0c;其内置的 Style Manager 提供了常见的 CSS 样式的可视化设置&#xff0c;极大地方便了开发者和设计师。然而&#xff0c;随着项目需求的不断变化&#xff0c;有些…...

解决双系统ubuntu24.04开机出现花屏等情况

1. 问题描述&#xff1a; 刚刚装上的双系统ubuntu24.04开机就出现花屏情况 2. 解决方案 安装显卡驱动 查看显卡型号 查看显卡硬件支持的驱动类型 ubuntu-drivers devices如果输入没有反映&#xff0c;请更新软件源&#xff0c;或者换官方软件源 sudo add-apt-repository…...

基于SpringBoot的智慧社区管理系统(源码+数据库)

499基于SpringBoot的智慧社区管理系统&#xff0c;系统包含三种角色&#xff1a;管理员、用户主要功能如下。 【用户功能】 1. 首页&#xff1a;查看系统。 2. 超市商品&#xff1a;浏览超市中各类商品信息。 3. 动物信息&#xff1a;了解社区内的动物种类和相关信息。 4. 车位…...

通俗理解CLIP模型如何实现图搜图乃至文搜图

一、图搜图与文搜图 图搜图和文搜图的场景相信大家并不少见&#xff0c;比如度娘的搜索框就可以直接上传图片找到相似的图片&#xff0c;还有某宝某团都有这种上传图片匹配到相似商品或者商品页的推荐的功能。那比如我想搜一张“正在跳舞的狗”的图片&#xff0c;是不是就能搜…...

谷歌最近放出大招——推出全新“Agent Development Kit(简称ADK)

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

std::string` 类

以下是对 std::string 类中 修改操作 和 字符串操作 的示例代码&#xff0c;帮助你更好地理解这些函数的使用&#xff1a; 5. 修改操作 (1) operator 用于追加字符串、C 风格字符串或字符。 #include <iostream> #include <string>int main() {std::string str …...

Sping Cloud配置和注册中心

1.Nacos实现原理了解吗&#xff1f; Nacos是注册中心&#xff0c;主要是帮助我们管理服务列表。Nacos的实现原理大概可以从下面三个方面来讲&#xff1a; 服务注册与发现&#xff1a;当一个服务实例启动时&#xff0c;它会向Nacos Server发送注册请求&#xff0c;将自己的信息…...

Java基础 - 泛型(常见用法)

文章目录 泛型类泛型方法泛型类派生子类示例 1&#xff1a;子类固定父类泛型类型&#xff08;StringBox 继承自 Box<String>&#xff09;示例 2&#xff1a;子类保留父类泛型类型&#xff08;AdvancedBox<T> 继承自 Box<T>)示例 3&#xff1a;添加子类自己的…...

待排序元素规模较小时,宜选取哪种排序算法效率最高

当待排序元素规模较小时&#xff0c;通常选择以下几种简单高效的排序算法&#xff0c;因为它们在小规模数据下具有更低的常数开销和良好的局部性表现&#xff1a; ✅ 插入排序&#xff08;Insertion Sort&#xff09; 推荐理由&#xff1a;在数据量小、数据部分有序的情况下&a…...

SAQ评级是什么,SAQ评级的意义?对企业发展好处

SAQ评级&#xff08;Supplier Audit Questionnaire&#xff0c;供应商审计问卷评级&#xff09;是供应链管理中常见的一种评估机制&#xff0c;主要用于对供应商的质量、合规性、风险管理能力等进行标准化审核和分级。它常见于汽车、电子、医药等对供应链要求严格的行业&#x…...

嵌入式---电机分类

一、按电流类型分类&#xff08;最基础分类&#xff09; 1. 直流电机&#xff08;DC Motor&#xff09; 工作原理&#xff1a;通过换向器&#xff08;有刷&#xff09;或电子换向&#xff08;无刷&#xff09;将直流电源转换为交变磁场&#xff0c;驱动转子旋转。 核心特点&a…...

定制一款国密浏览器(5):修改浏览器名称

在上一章中,我介绍了如何修改浏览器的 logo,这一章介绍修改浏览器的名称。 浏览器的名称是 AI 帮我想的,英文名 Mojo Browser,中文名墨舟浏览器。 第一步先修改 desktop 文件,desktop 文件支持多语言化,遵循 Freedesktop.org 制定的 Desktop Entry Specification 规范。…...

水库大坝安全监测系统

水库大坝安全监测系统是现代水利管理中至关重要的组成部分&#xff0c;是一种用于实时监控和评估大坝结构安全状况的技术系统&#xff0c;通过多种技术手段实现对水库大坝的全方位、全天候监测&#xff0c;旨在确保大坝的稳定性和安全性&#xff0c;预防潜在风险。 系统构成 …...

文件IO5(JPEG图像原理与应用)

JPEG图像原理与应用 ⦁ 基本概念 JPEG&#xff08;Joint Photographic Experts Group&#xff09;指的是联合图像专家组&#xff0c;是国际标准化组织ISO制订并于1992年发布的一种面向连续色调静止图像的压缩编码标准&#xff0c;所以也被称为JPEG标准。 同样&#xff0c;JP…...

开源Cursor替代品——Void

我原先写过很多关于Cursor文章。 今天分享一个开源的 Cursor 替代方案。 如果你也热爱开源项目,那么这篇文章正是为你准备的! 寻找开源替代的原因 我一直在思考:市面上这类产品层出不穷,但真正的核心技术似乎都依赖于大语言模型(LLM)。于是萌生了一个想法——何不自己…...

DAOS系统架构-组件

如上图所示&#xff0c;一个完整的DAOS系统是由管理节点组件、客户端节点组件、服务端节点组件以及网络通信组件四个部分组成。管理节点组件通过管理网络通道&#xff08;蓝色&#xff09;对DAOS服务管理和监控。客户端节点组件通过数据网络通道&#xff08;红色&#xff09;与…...

论文精读:MSCA-Net:多尺度上下文聚合网络在红外小目标检测中的突破

论文地址:https://arxiv.org/pdf/2503.17193 目录 一、论文结构解析 二、三大创新点详解 1. 多尺度增强检测注意力(MSEDA) 2. 位置卷积块注意力模块(PCBAM) 3. 通道聚合模块(CAB) 三、实验结果对比 定量分析(表I关键数据) 定性对比 四、应用场景展望 五、总…...

LangGraph 使用指南

安装配置 安装依赖 LangGraph 可以通过 pip 安装&#xff1a; pip install langgraph如果您想使用最新的功能和改进&#xff0c;可以从源代码安装&#xff1a; pip install githttps://github.com/langchain-ai/langgraph.git环境配置 LangGraph 与 LangChain 共享许多配置…...

C++指针(四)万字图文详解!

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 相关文章&#xff1a;C指针&#xff08;一&#xff09;、C指针&#xff08;二&#xff09;、C指针&#xff08;三&#xff09; 本篇博客是介绍函数指针、函数指针数组、回调函数、指针函数的。 点赞破六…...

【AI提示词】长期主义助手提供规划支持

提示说明 长期主义是一种关注长期利益和持续学习的思维模式&#xff0c;帮助个人和组织在快速变化的环境中保持耐心和系统性思考。 提示词 # Role: Long-termist Assistant## Profile - language: 中文 - description: 长期主义是一种关注长期利益和持续学习的思维模式&…...

JavaScript逆向工程:如何判断对称加密与非对称加密

在现代Web应用安全分析中&#xff0c;加密算法的识别是JavaScript逆向工程的关键环节。本文将详细介绍如何在逆向工程中判断JavaScript代码使用的是对称加密还是非对称加密。 一、加密算法基础概念 1. 对称加密 (Symmetric Encryption) 特点&#xff1a;加密和解密使用相同的…...

制造企业如何通过实现数据统一?

随着生产规模的扩大、供应链的复杂化以及市场需求的快速变化&#xff0c;企业内部的数据管理问题日益凸显。数据孤岛、数据冗余、数据不一致等问题不仅降低了运营效率&#xff0c;还可能导致决策失误。那么&#xff0c;制造企业如何才能打破这些壁垒&#xff0c;实现数据的统一…...

ICRA-2025 | 视觉预测助力机器人自主导航!NavigateDiff:视觉引导的零样本导航助理

论文&#xff1a;Yiran Qin 1 , 2 ^{1,2} 1,2, Ao Sun 2 ^{2} 2, Yuze Hong 2 ^{2} 2, Benyou Wang 2 ^{2} 2, Ruimao Zhang 1 ^{1} 1单位&#xff1a; 1 ^{1} 1中山大学&#xff0c; 2 ^{2} 2香港中文大学深圳校区论文标题&#xff1a;NavigateDiff: Visual Predictors are Ze…...

Java 与 LibreOffice 集成开发指南

1. 引言 Java 与 LibreOffice 的集成开发为开发者提供了一种强大的工具&#xff0c;用于实现文档自动化处理、文档转换、内容编辑等任务。通过 Java 调用 LibreOffice 提供的 API&#xff0c;可以实现跨平台的文档处理功能&#xff0c;满足各种业务需求。本文将详细介绍如何使…...

如果单表数据量大,只能考虑分库分表吗

程序员最怕啥?不是需求改八遍,也不是半夜报警电话,而是数据库突然卡成PPT!尤其是当单表数据冲到几千万行,查询慢得像老牛拉车,这时候团队第一反应往往是:“赶紧分库分表!” 但兄弟,分库分表可不是什么温柔小姐姐,它更像是个浑身带刺的仙人掌——你以为抱上就能解决问…...

3.3.1 spdlog异步日志

文章目录 3.3.1 spdlog异步日志1. spdlog1. 日志作用2 .同步日志和异步日志区别 2. spdlog是什么下载命令&#xff1a;2. spdlog为什么高效3. spdlog特征5. spdlog输出控制6. 处理流程7. 文件io8.问题 2. 如何创建logger3. 如何创建sink4. 如何自定义格式化5. 如何创建异步日志…...

【Web功能测试】注册与登录功能测试用例设计深度解析

1 注册功能测试用例设计 1.1 明确需求与测试范围 必填字段&#xff1a;手机号、验证码、密码 手机号规则&#xff1a;11位手机号 密码规则&#xff1a;6-16位&#xff0c;需包含大小写字母必须同时存在&#xff0c;数字或符号存在其一 协议勾选 提交后跳转登录页或显示成功…...

netstat 查看端口占用、杀死进程

文章目录 查看端口占用强制杀死 报错信息使用 lsof 查看端口占用 netstat -tanlp强制杀死 在安全的情况下&#xff0c;用下面命令杀死端口占用的进程 sudo kill $(sudo lsof -t -i:3128)报错信息 部署程序时&#xff0c;会出现这样的报错&#xff1a; Error response from…...

Windows 11 PowerShell重定向文本文件的编码问题

目录 问题的由来 编码导致的问题 解决办法 VSCode进行转换 记事本进行转换 直接指定输出的文本编码 总结 问题的由来 在我的嵌入式系统的课程中有一个裸机开发的实验&#xff0c;其中需要把图片等文件转换为C语言数组保存在程序中。课程中&#xff0c;我推荐了CodePlea的…...