Vue3-Quick-Start
你只要会基础的HTML,JS,CSS,那么就可以上⼿Vue了。如果你会Java,那么上⼿Vue⾮常轻松。如果你会Vue2,那么上⼿Vue3会更加舒服。
Vue3简介
- 官⽹地址:
https://vuejs.org/ - 中⽂官⽹:
https://cn.vuejs.org/ - Vue是什么?易学易⽤,性能出⾊,适⽤场景丰富的 Web 前端框架。
- Vue2已经于2023年12⽉31⽇停⽌维护。建议升级到Vue.js 3.0版本。打包更⼩,内存更少,渲染更快。 好消息是,vue3向下兼容vue2的语法
- Vue3于2020年9⽉18⽇发布,代号: One Piece 海贼王。 久 经磨砺
- Vue3新特性:组合式API(重点),更好的⽀持TypeScript(熟悉),状态存储框架Pinia(重点),新组件(了 解)。。。。。详⻅官⽹
⼀、整体认识Vue3项⽬
1、创建Vue3⼯程
前置:安装NodeJS。NodeJS版本18.0以上。 使⽤官⽅脚⼿架创建Vue⼯程[推荐]。
# 使⽤官⽅脚⼿架
npm create vue@latest
# 按照脚⼿架要求选择是否启⽤相关组件
Vue.js - The Progressive JavaScript Framework
✔ 请输⼊项⽬名称: … myVue3
✔ 请输⼊包名称: … myvue3
✔ 是否使⽤ TypeScript 语法? … 否 / 是 # 选是
✔ 是否启⽤ JSX ⽀持? … 否 / 是
✔ 是否引⼊ Vue Router 进⾏单⻚⾯应⽤开发? … 否 / 是
✔ 是否引⼊ Pinia ⽤于状态管理? … 否 / 是
✔ 是否引⼊ Vitest ⽤于单元测试? … 否 / 是
✔ 是否要引⼊⼀款端到端(End to End)测试⼯具? › 不需要
✔ 是否引⼊ ESLint ⽤于代码质量检测? … 否 / 是 # 选是
✔ 是否引⼊ Prettier ⽤于代码格式化? … 否 / 是
✔ Add Vue DevTools extension for debugging? (experimental) … 否 / 是
# 启动项⽬
npm install
npm run dev
# VITE v5.1.6 ready in 315 ms
# ➜ Local: http://localhost:5173/
# ➜ Network: use --host to expose
# ➜ press h + enter to show help
1 、所有功能组件都可以后续⼿动添加。 关于TypeScript,在Vue中的TypeScript可以认为是在JS的基础上,增加⾯向对象的能⼒。可以定义接⼝、 类、抽象类等。 2、npm install过程中会去node仓库下载很多依赖库,放到项⽬本地node_modules⽬录。建议将npm源设定为淘宝提供的国内镜像,可以下载快⼀点。
npm config get registry https://registry.npmmirror.com
补充:vue2时提供了另外⼀个脚⼿架vue-cli,也可以⽤来创建vue3项⽬。但是vue-cli已经处于停⽌维护状态。
# 安装或者升级脚⼿架
npm install -g @vue/cli
# 查看脚⼿架版本,确保版本在4.5.0以上
vue --version
# 创建应⽤
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
另外,官⽅还有其他⼀些集成vue的⽅法,⾃⾏参考。
2、主要⼯程结构
官⽅建议开发IDE: vscode。提供了辅助开发插件 vue-official。 在这之前有个插件叫volar,现在已经停⽤ 主要代码结构如下图
.vscode
public // 静态资源文件夹,构建时文件会原封不动复制到dist根目录
favicon.ico // 网站页签图标文件
src // 项目源代码核心目录,所有业务代码、组件、逻辑均存放于此
assets // 项目静态资源目录,Vite会对其中资源做编译(如图片hash命名)
components // 全局/通用组件目录,存放可复用的Vue组件
router // 路由配置目录,存放Vue Router的路由规则、路由守卫等相关代码
stores // Pinia状态管理目录,存放全局状态的store模块
views // 页面级组件目录,存放对应路由的页面组件
App.vue // 根组件,项目所有页面的父容器,可配置全局布局、路由出口等
main.ts // 项目入口文件,创建Vue实例、挂载根组件、配置全局插件等
.gitignore // Git版本控制忽略文件配置
env.d.ts // TypeScript类型声明文件,解决TS类型提示问题
index.html // 项目入口HTML文件,包含Vue挂载的根节点
package.json // 项目依赖配置文件
README.md // 项目说明文档
tsconfig.app.json // TypeScript针对Vue应用代码的编译配置,仅作用于src目录下的业务代码
tsconfig.json // TypeScript根配置文件,全局通用的TS编译规则
tsconfig.node.json // TypeScript针对Vite/node端代码的编译配置
vite.config.ts // Vite构建工具的配置文件
- 典型的Vue项⽬,都是在
index.html这⼀个单⻚⾯⾥形成各种交互,这也就是所谓的SPA(Single Page Application) - Vue3的核⼼是通过createApp函数创建⼀个应⽤实例,在这个实例中构建各种应⽤。(
main.ts中) - 每个vue⽂件就是⼀个⻚⾯上的组件,组件可以嵌套使⽤。
- vue中的组件分为
<template>⻚⾯模板,<script>脚本和<style>样式三个部分。Vue2中要求<template>下必须有⼀个唯⼀的根元素,Vue3中则没有了这个限制。
⼆、数据双向绑定
双向绑定是Vue最为核⼼的功能。简单理解就是<template>中的⻚⾯数据和<script>中的脚本数据进⾏绑定,其中任何⼀个数据发⽣了变化,另⼀个数据也随之发⽣变化。
1、vue2语法的双向绑定
<template>
<div>
姓名:<input v-model="userName" /> {{ userName }} <br />
薪⽔:<input type="number" v-model="salary" /> {{ salary }} <br />
<button @click="addSalary">薪⽔加1000</button>
</div>
</template>
<script lang="ts">
export default {
// 数据
data() {
return {
userName:"王⼀",
salary:15000
}
}
// ⽅法
methods:{
addSalary(){
this.salary += 1000
}
}
}
</script>
<style scoped>
</style>
数据双向绑定可以说是整个Vue的核⼼。例如,我们可以⽤数据双向绑定实现⼀些更为复杂的表单。
<template>
<div>
姓名:<input v-model="userName" /> {{ userName }} <br />
薪⽔:<input type="number" v-model="salary" /> {{ salary }} <br />
<button v-on:click="addSalary"> 提交 </button>
<button @click="changeUserInfo"> 查看个⼈信息 </button>
</div>
<hr />
<div class="userInfo" v-if="showUserInfo">
<h2>个⼈信息</h2>
年龄:<input type="number" v-model="userInfo.age" /> <br />
性别:<input type="radio" value="1" v-model="userInfo.sex"> 男 </input>
<input type="radio" value="2" v-model="userInfo.sex"> ⼥ </input> <br />
岗位:<select v-model="userInfo.department">
<option value="dev">开发</option>
<option value="test">测试</option>
<option value="maintain">运维</option>
</select> <br />
技术: <span v-for="skill in userInfo.skills" :key="skill">{{ skill }} </span> <br />
学习新技术: <input v-model="newSkill" /> <br />
<button @click="learnSkill">学习</button> <br />
<p> 个⼈信息汇总:{{ userInfo }} </p>
</div>
</template>
<script lang="ts">
export default{
data() {
return{
userName:'roy',
salary:15000,
userInfo:{
age:0,
sex:1,
skills:['java','vue','python'],
department:''
},
newSkill:'',
showUserInfo:false
}
},
methods: {
addSalary(){
this.salary +=1000
},
learnSkill() {
if(this.newSkill)
this.userInfo.skills.push(this.newSkill)
},
changeUserInfo(){
this.showUserInfo= !this.showUserInfo
}
}
}
</script>
<style scoped>
.userInfo{
background-color: bisque;
width: 80%;
}
.userInfo span{
background-color: yellow;
margin-left: 10px;
border: 1px;
border-radius: 5px;
}
</style>
这样的表单,如果要⽤纯JS实现,就会相当困难。但是,⽤双向绑定就简单很多。
2、OptionsAPI和CompositionAPI
Vue2中常⽤的这种编写⽅式称为OptionsAPI,配置式。其实现⽅式是⽤⼀个统⼀的配置对象来实现全部代码逻辑。在这个对象中,通过data、methods、computed等配置选项来控制逻辑。
OptionsAPI是Vue2时的标准API编写⽅式。Vue3向下兼容了Vue2的API。因此,Vue2的⽼项⽬,在Vue3中基本可以⽆缝迁移。 实际上,OptionsAPI是在CompositionAPI的基础上实现的。关于Vue的基础概念和知识,在这两种API之间是通⽤的 。另外,官⽅建议,如果采⽤Vue构建完整的SPA应⽤,那么更建议使⽤CompositionAPI。
但是,OptionsAPI所有逻辑都混在⼀起,不便于维护和复⽤。 Vue3另外通过了⼀种更⽅便的API,CompositionAPI,混合式API。
上⾯同样的示例,⽤Composition API的写法如下:
<template>
<div>
姓名:<input v-model="userName" /> {{ userName }} <br />
薪⽔:<input type="number" v-model="salary" /> {{ salary }} <br />
<button @click="addSalary">薪⽔加1000</button>
</div>
</template>
<script lang="ts">
export default {
setup() {
// 现在声明的变量还不具备双向绑定
let userName="王⼀"
let salary=15000
function addSalary() {
salary += 1000
console.log("salary = " + salary)
}
// 模板要⽤哪些,就返回哪些
return {userName,salary,addSalary}
}
}
</script>
<style scoped>
</style>
1、setup是Vue3中的⼀个⽣命周期函数,他会在组件加载时执⾏。后⾯会细讲⽣命 周期。
2、setup可以返回对象或者函数。如果是⼀个对象,则对象中的属性、⽅法等,可以在模板中直接使⽤(常⽤)。如果返回⼀个函数,则通过函数的返回值直接渲染⻚⾯,不经过模板。例如
setup() {return ()=>"直接渲染"}3、setup是⼀个普通的函数,不能使⽤this。 OptionsAPI中可以通过this访问脚本本身的数据 同时 setup中不处理this,意味着setup编写可以更灵活,不需要依赖当前⻚⾯上下⽂
4、此时声明的userName, salary等变量不具备双向绑定。Vue3对双向绑定做了重新设计,后⾯会详细分享。
5、setup有⼀种简写的⽅式
<script setup lang="ts">。这样就不需要写函数了,标签内部直接写函数体。在标签内部声明的对象,函数等,都会直接return出去。
项⽬中常⽤
<script setup lang="ts">
// 现在声明的变量还不具备双向绑定
let userName="王⼀"
let salary=15000
function addSalary(){
salary += 1000
console.log("salary = " + salary)
}
</script>
在CompositionAPI中,由于setup是⼀个不同的函数,不需要处理this。这也意味着setup函数编写可以更加灵活,不需要依赖当前⻚⾯上下⽂。例如:将示例中的脚本单独写到⼀个ts⽂件中。
// MySalary.ts
import { onMounted, ref } from "vue"
export default function() {
// 现在声明的变量还不具备双向绑定。添加ref函数才能具备响应式
const userName=ref("王⼀")
const salary=ref(15000)
function addSalary(){
salary.value += 1000
console.log("salary = " + salary.value)
}
onMounted( ()=> {
console.log("加载了外部脚本")
});
return {userName,salary,addSalary}
}
然后,在App.vue中就可以直接引⽤脚本
<template>
<div>
姓名:<input v-model="userName" /> {{ userName }} <br />
薪⽔:<input type="number" v-model="salary" /> {{ salary }} <br />
<button @click="addSalary">薪⽔加1000</button>
</div>
</template>
<script setup lang="ts">
import MySalary from './components/MySalary';
let {userName,salary,addSalary} = MySalary()
</script>
<style scoped>
</style>
如果App.vue的逻辑越来越复杂,通过这种⽅式,就更易于将相关的属性和⽅法整理到⼀起,从⽽实现⼀个特定 的业务功能。
1、ref函数让变量具备了双向绑定功能。后⾯详细分析。
2、复杂⻚⾯可以⽤这种⽅式。⼀般情况下,显然是将MySalary的模板和脚本封装到⼀起,这就是⾃定义组件了。
3、Vue3中的数据双向绑定
3.1 ref定义基础类型响应式数据
- 语法: let userName=ref(初始值)。
- 返回值:⼀个RefImpl的实例对象,值被包裹在对象的value属性中。
- 注意点:
- 脚本中要⽤ref对象的value属性访问值,例如
userName.value。但是模板中可以直接⽤。 - ref对象本身不是响应式的,value属性是响应式的。例如js中修改值,要通过
userName.value="xxx",⽽不能userName="xxx"。 vue-official插件中可以选择⾃动添加value属性。(需要⼿动勾选)
- 脚本中要⽤ref对象的value属性访问值,例如
<template>
<div>
姓名:<input v-model="userName" /> {{userName }} <br />
薪⽔:<input type="number" v-model="salary" /> {{ salary }} <br />
<button @click="changeName">名字后⾯加1</button>
<button @click="addSalary">薪⽔加1000</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
// 基础类型⽤ref声明响应式
let userName=ref("王⼀")
let salary=ref(15000)
function changeName(){
userName.value += "⼀"
// userName不是响应式的,userName.value才是响应式的。重新定义userName就⽆法双向绑定
// userName = ref("王⼀⼀")
}
function addSalary(){
// 脚本中操作数据要加.value
salary.value += 1000
// 观察salary对象结构
console.log("salary = " , salary)
}
</script>
3.2 reactive定义对象型响应式数据
- 语法:
let salaryInfo = reactive({userName:"王⼀",salary:15000}) - 返回值:⼀个Proxy实例对象,具有双向绑定能⼒。
<template>
<div>
姓名:<input v-model="salaryInfo.userName" /> {{ salaryInfo.userName }} <br />
薪⽔:<input type="number" v-model="salaryInfo.salary" /> {{ salaryInfo.salary }} <br />
<button @click="changeName">名字后⾯加1</button>
<button @click="addSalary">薪⽔加1000</button>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
// 对象类型⽤reactive声明响应式
let salaryInfo = reactive({userName:"王⼀",salary:15000})
function changeName(){
salaryInfo.userName+="⼀"
}
function addSalary(){
salaryInfo.salary+=1000
// 观察SalaryInfo对象
console.log("salaryInfo",salaryInfo)
}
</script>
<style scoped>
</style>
3.3 ref对⽐reactive
这两者都是⽤来声明响应式数据的。但是也有⼀些需要注意的地⽅。
- ref也可以⽤来声明对象型响应式数据。其内部也是使⽤reactive实现。例如下⾯的写法效果是⼀样的
<script setup lang="ts">
import { ref } from 'vue';
// 对象类型⽤reactive声明响应式
let salaryInfo = ref({userName:"王⼀",salary:15000})
function changeName(){
salaryInfo.value.userName+="⼀"
}
function addSalary(){
salaryInfo.value.salary+=1000
//观察SalaryInfo对象
console.log("salaryInfo",salaryInfo)
}
</script>
其 中salaryInfo.value其实拿到的就是⼀个Reactive对象。
- 基础类型响应式数据,只能⽤ref声明。对象型响应式数据,ref,reactive都可以。通常⼤对象推荐使⽤reactive。
- 对象型响应数据,如果将各个属性拆解出来,是不具备响应式的。如果需要响应式属性,可以使⽤toRefs或者toRef函数进⾏转换。例如
<template>
<div>
姓名:<input v-model="name" /> {{name }} <br />
薪⽔:<input type="number" v-model="money" /> {{ money }} <br />
<button @click="changeName">名字后⾯加1</button>
<button @click="addSalary">薪⽔加1000</button>
</div>
</template>
<script setup lang="ts">
import { reactive, toRef, toRefs } from 'vue';
// 对象类型⽤reactive声明响应式
let salaryInfo = reactive({userName:"王⼀",salary:15000})
// 拆解出来的属性,是基础数据,不具备响应式
// let name = salaryInfo.userName
// let money = salaryInfo.salary
// toRef将对象的属性转为⼀个响应式数据
let name = toRef(salaryInfo,'userName')
let money = toRef(salaryInfo,'salary')
// 将对象的所有属性⼀起转换成响应式数据
// let {userName,salary} = toRefs(salaryInfo)
function changeName(){
name.value +="⼀"
console.log("name", name)
}
function addSalary(){
money.value +=1000
//观察SalaryInfo对象
console.log("money",money)
}
</script>
<style scoped>
</style>
3.4 标签的ref属性
在 <template> 中定义模板时,可以通过ref属性将当前DOM元素绑定给响应式变量。
<template>
姓名:<input ref="name" abc="aaaaa"/> {{name }} <br />
<button @click="showRes">分析输⼊框</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
let name = ref()
function showRes(){
console.log(name) //RefImpl ref对象
console.log(name.value) //<input> dom元素
console.log(name.value.value) //输⼊框的值
console.log(name.value.getAttribute("abc")) //⾃定义属性的值
}
</script>
<style scoped>
</style>
如果只是针对普通元素,还体现不出Ref的作⽤。如果配合⾃定义组件,则更 能体现Ref属性的作⽤。例如,针对薪⽔信息,可以⾃⼰写⼀个简单组件,把多个输⼊框整合到⼀起。
<!-- ⾃定义的薪⽔信息输⼊组件 -->
<template>
姓名:<input v-model="userName"><br />
薪⽔:<input type="number" v-model="salary">
</template>
<script lang="ts">
// 组件名默认是⽂件名。如果不希望⽤⽂件名,也可以⾃定义
export default {
name:"SalaryInfo"
}
</script>
<script setup lang="ts">
import { ref } from 'vue';
// 响应式数据默认值
let userName = ref("unknown")
let salary = ref(1000)
// 对外暴露属性。只有暴露出去,组件外部才能访问
defineExpose({userName,salary})
</script>
<style>
</style>
然后,在App.vue中,就可以通过ref属性获取薪⽔输⼊框的值。
<!-- App.vue -->
<template>
<MySalaryInfo ref="salaryInfo"/>
<button @click="showRes">查看薪⽔信息</button>
</template>
<script setup lang="ts">
// 引⼊⼦组件
import MySalaryInfo from '@/components/MySalaryInfo.vue';
import { ref } from 'vue';
// 获取绑定对象
let salaryInfo = ref()
function showRes(){
console.log(salaryInfo) //RefImpl ref对象
console.log(salaryInfo.value) //Proxy ⼦组件的响应式数据
console.log(salaryInfo.value.userName) //输⼊框的值
console.log(salaryInfo.value.salary)
}
</script>
<style scoped>
</style>
3.5⾃定义组件的props属性
上⾯的示例相当于是⼦组件将属性暴露给⽗组件。那如果想要⽗组件给⼦组件赋值呢?这就可以⽤到组件的props属性。
<!-- App.vue -->
<template>
<MySalaryInfo :salaryInfo="salaryInfo"/> <br />
<button @click="setSalary">修改薪⽔信息</button>
</template>
<script setup lang="ts">
//引⼊⼦组件
import MySalaryInfo from '@/components/MySalaryInfo.vue';
import { reactive } from 'vue';
let salaryInfo = reactive({
userName:"王⼀",salary:15000
})
function setSalary(){
salaryInfo.salary+=1000
console.log(salaryInfo)
}
</script>
<style scoped>
</style>
此时,在MySalaryInfo组件内,就可以通过defineProps函数,获取属性。
<!-- MySalaryInfo.vue -->
<template>
{{ salaryInfo }} <br />
<!-- ⽗组件传进来的值,不建议直接⽤,eslint会报红提示 -->
姓名:<input v-model="salaryInfo.userName"><br />
薪⽔:<input type="number" v-model="salaryInfo.salary">
</template>
<script lang="ts">
// 组件名默认是⽂件名。如果不希望⽤⽂件名,也可以⾃定义
export default {
name:"SalaryInfo"
}
</script>
<script setup lang="ts">
import type { SalaryInfo } from '@/types/SalaryInfo';
// 直接接收,不限定类型
// defineProps(["salaryInfo"])
// 接收参数,限定类型
defineProps<{salaryInfo:SalaryInfo}>()
</script>
<style>
</style>
三、VUE3⽣命周期
每个 Vue 组件实例在创建时都需要经历⼀系列的初始化步骤,⽐如设置好数据侦听,编译模板,挂载实例到DOM,以及在数据改变时更新 DOM。在此过程中,它也会运⾏被称为⽣命周期钩⼦的函数,让开发者有机会在特定阶段运⾏⾃⼰的代码。
⽣命周期有四个阶段:创建,挂载,更新,销毁。每个阶段有⼀前⼀后两个函数
OptionsAPI的⽣命周期函数:
- 创建阶段: beforeCreate、 created
- 挂载阶段: beforeMount、 mounted
- 更新阶段: beforeUpdate、 updated
- 销毁阶段: beforeDestroy、 destroyed
CompositionAPI的⽣命周期函数:
- 创建阶段: setup
- 挂载阶段: onBeforeMount、 onMounted
- 更新阶段: onBeforeUpdate、 onUpdated
- 卸载阶段: onBeforeUnmount、 onUnmounted
示例
<template>
<div>
薪⽔:<input type="number" v-model="salary" /> <br />
<button @click="addsum">薪⽔+1000</button>
</div>
</template>
<!-- vue3写法 -->
<script lang="ts" setup>
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
// 数据
let salary = ref(0)
// ⽅法
function addsum() {
salary.value += 1000
}
console.log('setup')
// ⽣命周期钩⼦
onBeforeMount(()=>{
console.log('挂载之前')
})
onMounted(()=>{
console.log('挂载完毕')
})
onBeforeUpdate(()=>{
console.log('更新之前')
})
onUpdated(()=>{
console.log('更新完毕')
})
onBeforeUnmount(()=>{
console.log('卸载之前')
})
onUnmounted(()=>{
console.log('卸载完毕')
})
</script>
四、Vue-Router组件路由机制
Vue项⽬虽然只有index.html⼀个⻚⾯,但是可以通过多路由机制实现多⻚⾯跳转的效果。访问不同链接,展示不同的⻚⾯内容,形成多⻚⾯的效果。
Vue官⽅提供了Vue-Router组件实现路由管理,官⽹地址:https://router.vuejs.org/zh/ 。该组件可以在创建Vue项⽬时选择引⼊。如果创建时没有安装,也可以⼿动安装。
npm install vue-router@4
vue3要求使⽤router组件最新版本。⽬前最新版本是4
1、基础使⽤
⾸先要在ts脚本中配置router组件。
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createRouter,createWebHistory } from "vue-router";
import HomePage from "@/pages/Home.vue"
import AboutPage from "@/pages/About.vue"
import NewsPage from "@/pages/News.vue"
// 配置路由规则
const routes = [
{ path: '/',redirect: '/home'}, //默认跳转都⾸⻚
{ path: '/home', component: HomePage },
{ path: '/about', component: AboutPage, name:'about' }, //命名路由
{ path: '/news', component: NewsPage },
]
// 创建路由器
const router = createRouter({
history: createWebHistory(),//路由器⼯作模式
routes,
})
// 项⽬中,通常将两个配置项放到单独的ts⽂件中
const app = createApp(App)
// 加载路由器
app.use(router)
app.mount('#app')
然后,在Vue模板中,配置跳转链接(<router-link>标签)以及跳转⻚⾯(<router-view>标签)。
<!-- App.vue -->
<template>
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使⽤ router-link 组件进⾏导航 -->
<!-- 通过传递 `to` 来指定链接 -->
<!-- `<router-link>` 将呈现⼀个带有正确 `href` 属性的 `<a>` 标签 -->
<router-link to="/home">⾸⻚</router-link> <!-- 直接跳转 -->
<router-link :to="{ path:'/about'}">关于</router-link> <!-- 路径跳转 -->
<router-link replace :to="{ name:'news'}">新闻</router-link> <!-- 命名跳转 -->
</p>
<div class="content">
<!-- 路由出⼝ -->
<!-- 路由匹配到的组件将渲染在这⾥ -->
<router-view></router-view>
</div>
</div>
</template>
<!-- vue3写法 -->
<script lang="ts" setup >
</script>
<style>
a {
margin: 10px;
}
.content{
background: yellowgreen;
widows: 10%;
height: 400px;
border: 1cap;
border-radius: 10px;
}
</style>
启动后,点击⻚⾯上⽅的菜单,下⽅内容⻚就会显示相对应的内容。同时注意观察上⽅路径变化。
2、路由⼯作模式
在router配置中的history项为路由⼯作模式。Vue提供了两种⼯作模式:
- history模式
访问路径:URL不带
#,斜杠链接,接近传统⽹站。缺点:容易产⽣404错误。
const router = createRouter({
history:createWebHistory(), //history模式
/******/
})
- hash模式
访问路径:URL带有
#。缺点:对SEO不太友好。⽐较适合内部系统。
const router = createRouter({
history:createWebHashHistory(), //hash模式
/******/
})
3、replace属性
<route-link> 标签可以添加replace属性。有两种可选配置: push 和 replace
push追加浏览器历史记录(默认值)。追加历史记录后,可以使⽤浏览器的返回按钮,跳回历史⻚replace替换浏览器历史记录。替换历史记录后,浏览器的返回按钮不可⽤。
4、嵌套路由
<route-view> 标签嵌⼊的⻚⾯中⽀持进⼀步嵌套⼦菜单。例如,新闻⻚希望进⼀步嵌套新闻路由。新闻⻚有多条新闻,希望在新闻⻚展示多条新闻的标题。点击标题,可以查看对应新闻的详情。
⾸先,定义三个新闻对应的详情⻚。每个详情⻚包含简单的内容
<!-- NewsDetail1.vue -->
<template>
<p>新闻ID: 1</p>
<p>新闻标题: 1 </p>
<p>新闻内容: 1 </p>
</template>
<script lang="ts" setup>
</script>
<style>
</style>
并配置到路由规则中
import News1 from "@/pages/NewsDetail1.vue"
import News2 from "@/pages/NewsDetail2.vue"
const routes = [
{ path: '/',redirect: '/home'}, //默认跳转都⾸⻚
{ path: '/home', component: HomePage },
{ path: '/about', component: AboutPage, name:'about' }, //命名路由
{
path: '/news',
component: NewsPage,
name:'news',
children:[ //⼦路由
{
name: "xinwen1",
path: "1",
component: News1
},
{
name: "xinwen2",
path: "2",
component: News2
}
]
},
]
然后,在新闻详情⻚增加嵌套路由
<template>
<div class="news">
<!-- 导航区 -->
<ul>
<li><RouterLink to="/news/1">新闻1</RouterLink></li>
<li><RouterLink to="/news/2">新闻2</RouterLink></li>
</ul>
<!-- 展示区 -->
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>
<script setup lang="ts">
</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: 90%;
height: 90%;
border: 1px solid;
margin-top: 20px;
border-radius: 10px;
}
</style>
这样就实现了新闻⻚内的嵌套路由。点击新闻标题,会跳到对应的新闻详情⻚。
5、路由传参
上⾯的示例显然太呆板,现实的场景当然是希望查出⼀个完整的新闻列表,然后每个新闻⻚都是展示新闻列表中的内容,⽽不是每个组件内固定的内容。这也就需要进⾏路由传参,也就是NewsDetail中的内容是从新闻列表中传递进来的。
Vue3中提供了两种传参⽅式,query传参和param传参。
1、query传参
News.vue 传参
<!-- 字符串传参 -->
<router-link to="/news/1?id=1&title=新闻1&content=asdfasdf" />
<!-- 对象传参 -->
<RouterLink :to="{
path:'/news/1',
query:{
id:'1',
title:'新闻1',
content:'asdfasdf'
}
}"> 新闻1 </RouterLink>
NewsDetail.vue接收参数
import {useRoute} from 'vue-router'
import {toRefs} from 'vue'
const route = useRoute()
// 打印query参数
console.log(route.query)
// 配置双向绑定数据
let {query} = toRefs(route)
2、params传参
params传参⽅式表示所有参数都拼接到URL上。
⾸先需要在route配置中预设占位符
{
path: '/news',
component: NewsPage,
name:'news',
children:[ //⼦路由
{
name: "xinwen2",
// Param传参,URL预设占位符,?表示参数可选
path: "2/:id/:title/:content",
component: News2
}
]
},
然后,传参时,在RouteLink中直接传到预设的URL,或者⽤name属性指定⽬标。
<!-- params传参 -->
<RouterLink to="/news/2/1/新闻2/qowuieoiu">param路径传参</RouterLink>
<!-- params传参 -->
<RouterLink :to="{
name:'xinwen2',
params:{
id:2,
title :'新闻2',
content :'qowiueoiqu'
}
}">param对象传参 </RouterLink>
接下来NewsDetail2.vue中通过路由的params属性接收参数
import {useRoute} from 'vue-router'
import {toRefs} from 'vue'
const route = useRoute()
// 打印params参数
console.log(route.params)
// 配置双向绑定数据
let {params} = toRefs(route)
五、Pinia集中式状态存储
1、理解状态
- 在任意Vue⻚⾯之间共享的存储数据。简单理解:在当前Vue项⽬中使⽤的MySQL数据库。例如登录信息,只要完成了登录,所有Vue⻚⾯都能读取到当前登录⽤户。
- Vue2中提供的集中状态存储框架是Vuex,Vue3中新提供了Pinia。如果你使⽤的还是Vue2,那么主要下,Vuex和Pinia不能⼀起使⽤。
2、创建store
Pinia可以在创建应⽤时选择引⼊。如果创建时没有引⼊,那就需要⼿动引⼊⼀下。
npm install pinia
Pinia的使⽤⽅式和Route组件基本相似。需要在启动的ts⽂件中使⽤use函数引⼊。
// main.ts
import {createPinia} from 'pinia'
// 加载pinia
const pinia = createPinia()
app.use(pinia)
接下来使⽤pinia需要创建Store。⼀个Store可以理解为MySQL中的⼀个库,保存⼀部分数据。Pinia的Store中有
三个概念: state, getter , action。这三个概念也可以类⽐于熟悉的MVC。
- state相当于是数据;
- getter相当于是服务,⽤来获取并返回数据;
- action相当于是Controller,组织业务逻辑。
创建定义store的⽂件 store/user.ts
import { defineStore } from 'pinia'
export const userStore = defineStore('userStore',{
//action封装修改state的业务动作
actions: {
changeUsername(value:string) {
if(value && value.length < 10) {
this.username = value
}
}
},
// getters读取state的计算值
getters: {
getUsername():string {
return this.username.toUpperCase()
}
},
// state定义要保存的数据结构
state() {
return {
// 给定默认值
username:'--'
}
}
})
store中最为核⼼的就是state,⽽在定义state时,可以利⽤TypeScript的类型定制功能,对复杂数据结构进⾏规范。
例如
const useStore = defineStore('storeId', {
state: () => {
return {
// ⽤于初始化空列表
userList: [] as UserInfo[],
// ⽤于尚未加载的数据
user: null as UserInfo | null,
}
}
},
)
interface UserInfo {
name: string
age: number
}
如果你熟悉Java后端开发,有没有觉得接⼝很熟悉?那么接下来,类、抽象类这些呢?也可以尝试尝试。
3、使⽤store操作数据
App.vue中修改stroe的数据
<script lang="ts" setup >
// 获取store
import { userStore } from "@/store/user";
const user = userStore()
// 修改store中的值
// 1、直接修改某⼀个state
user.username='roy'
// 2、修改完整的state
user.$patch({
username:'roy2'
})
// 3、通过action进⾏修改 推荐⽅式
user.changeUsername('roy')
// 获取store中的数据
console.log(user.username)
// 通过getter获取state数据 推荐⽅式
console.log(user.getUsername)
</script>
pinia的使⽤⼏乎没有⻔槛,相⽐vuex要简单很多,所以官⽅对Pinia的定义是符合直觉的状态管理库。因此,在使⽤pinia时,更应该是注意使⽤规范。
4、storeToRefs声明响应式数据
如果需要将store中的数据声明成响应式数据,供Vue的模板使⽤,可以使⽤pinia提供的storeToRefs函数。他和Vue提供的toRefs函数的区别在于,stroeToRefs只将store中的数据转换成响应式数据。⽽toRefs会将store对象中很多隐藏的⽅法和属性⻚转换出来。
<template>
<div id="app">
<h1>Hello {{ userInfo.username.value }}</h1>
</div>
</template>
<!-- vue3写法 -->
<script lang="ts" setup >
// 获取store
import { userStore } from "@/store/user";
import { storeToRefs } from "pinia";
import { toRefs } from "vue";
const user = userStore()
// storeToRefs转换后只有username和getUsername
let userInfo = storeToRefs(user)
console.log(userInfo)
// toRefs转换后包含了很多隐藏⽅法和属性,⽐如$patch
let userInfo2 = toRefs(user)
console.log(userInfo2)
</script>
<style>
</style>
5、store的混合式写法
store也有⼀种混合式的写法,将各种组件混合到⼀起。
import { defineStore } from 'pinia'
import { reactive } from 'vue'
export const userStore = defineStore('userStore',()=>{
// 相当于是state
const userInfo = reactive({username:"---"})
// 相当于action
function changeUsername(value:string){
if(value && value.length < 10) {
userInfo.username = value
}
}
// 相当于getters
function getUsername():string{
return userInfo.username.toUpperCase()
}
// 不⽤区分什么类型,返回出去的就可以⽤
return {userInfo,changeUsername,getUsername}
})
在App.vue中,也可以像使⽤普通对象⼀样,使⽤store中的⽅法和对象。
<template>
<div id="app">
<!-- 注意对象拆包过程 -->
<h1>Hello {{ res.userInfo.value.username }}</h1>
</div>
</template>
<!-- vue3写法 -->
<script lang="ts" setup >
// 获取store
import { userStore } from "@/store/user2";
import { storeToRefs } from "pinia";
const user = userStore()
// 修改store中的值
// 通过action进⾏修改 推荐⽅式
user.changeUsername('roy')
// 获取store中的数据
console.log(user.userInfo)
// 通过getter获取state数据 推荐⽅式
console.log(user.getUsername())
// 混合式store转成Ref后,只有数据的ref
let res = storeToRefs(user)
console.log(res)
</script>
<style>
</style>