一、vue3 Vue 3 是 Vue.js 的第三代核心框架,于 2020 年正式发布,是目前 前端开发的主流选择。它以 组合式 API (Composition API)、 Proxy 响应式系统 、极致性能 和 TypeScript 原生支持 为核心,彻底重构了 Vue 2 的架构。二、快速入门 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <title > Vue3 CDN 引入</title > <script src ="https://unpkg.com/vue@3/dist/vue.global.js" > </script > </head > <body > <div id ="app" > {{ message }}</div > <script > const { createApp } = Vue ; createApp ({ setup ( ){ const message = "hello,vue3" return { message } } }).mount ('#app' ); </script > </body > </html >
三、插值表达式 1.作用 可以使用表达式进行插值,渲染到页面中, 表达式:是可以被求值的代码,js引擎会将其计算出一个结果 2.语法
3.规则 只解析文本 ,不能解析 HTML 标签 支持单行表达式,不支持语句、循环、if 数据变更,页面自动刷新 4.代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <!DOCTYPE html > <html > <body > <div id ="app" > <p > 姓名:{{ name }}</p > <p > 年龄+2:{{ age + 2 }}</p > <p > 状态:{{ sex ? '男' : '女' }}</p > <p > 拼接:{{ '编号' + id }}</p > </div > <script src ="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js" > </script > <script > const { createApp } = Vue createApp ({ data ( ){ return { name :'张三' , age :18 , sex :true , id :1001 } } }).mount ('#app' ) </script > </body > </html >
四、Vue指令
指令
功能
简写
用法示例
v-text
渲染纯文本
无
<span v-text="msg"></span>
v-html
解析渲染 HTML
无
<div v-html="htmlStr"></div>
v-bind
绑定元素属性
:
<div :class="cls"></div>
v-on
绑定事件监听
@
<button @click="func"></button>
v-model
表单双向绑定
无
<input v-model="value">
v-if
条件销毁渲染
无
<div v-if="bool"></div>
v-else
条件互斥分支
无
<div v-else></div>
v-else-if
多条件判断
无
<div v-else-if="num>5"></div>
v-show
样式切换显隐
无
<div v-show="bool"></div>
v-for
列表循环遍历
无
<li v-for="item in arr"></li>
v-once
元素仅渲染一次
无
<div v-once>{{txt}}</div>
v-pre
跳过编译解析
无
<span v-pre>{{原样显示}}</span>
v-cloak
解决插值闪烁
无
<div v-cloak>{{msg}}</div>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <title > Vue3 全部指令合集案例</title > <style > [v-cloak] { display : none; } .red { color : red; } .box { padding : 8px ; margin : 5px 0 ; border : 1px #ccc solid; } .bind1 { color : green; } .bind2 { font-size : 30px ; } </style > <script src ="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js" > </script > </head > <body > <div id ="app" > <div class ="box" > v-text:<span v-text ="txt" > </span > </div > <div class ="box" > v-html:<span v-html ="htmlTxt" > </span > </div > <div class ="box" :class ="isRed ? 'red' : ''" > v-bind 动态样式</div > <div class ="box" :class ="{bind1: true,bind2: false}" > v-bind 动态类名样式</div > <div class ="box" :style ="{color: 'blue', backgroundColor: 'yellow'}" > v-bind 动态样式</div > <div class ="box" > v-on:<button @click ="num++" > 点击次数{{num}}</button > </div > <div class ="box" > v-model:<input v-model ="inputVal" > 显示: {{inputVal}} </div > <div class ="box" > <div v-if ="score >= 90" > 优秀</div > <div v-else-if ="score >= 60" > 及格</div > <div v-else > 不及格</div > </div > <div class ="box" > <div v-show ="isShow" > v-show 显示内容</div > <button @click ="isShow=!isShow" > 切换显隐</button > </div > <div class ="box" > v-for: <p v-for ="(item,idx) in list" :key ="idx" > {{idx+1}}.{{item}} </p > </div > <div class ="box" > v-once:<span v-once > {{onceTxt}}</span > </div > <div class ="box" > v-pre:<span v-pre > {{原样不会解析}}</span > </div > <div class ="box" v-cloak > {{cloakTxt}}</div > </div > <script > const { createApp } = Vue createApp ({ data ( ){ return { txt : '普通文本内容' , htmlTxt : '<b style="color:blue;">加粗蓝色文字</b>' , isRed : true , num : 0 , inputVal : '' , score : 78 , isShow : true , list : ['Vue' ,'JS' ,'HTML' ,'CSS' ], onceTxt : '只会渲染一次' , cloakTxt : '解决页面插值闪烁' , } } }).mount ('#app' ) </script > </body > </html >
2.指令修饰符
分类
修饰符
作用
v-model 修饰符
.trim
去除输入首尾空格
.number
自动转换为数字类型
.lazy
失焦后才同步数据
事件修饰符
.stop
阻止事件冒泡
.prevent
阻止标签默认行为
.self
仅自身元素触发事件
.once
事件只执行一次
按键修饰符
.enter
按下回车键触发
.space
按下空格键触发
.esc
按下退出键触发
.delete
按下删除键触发
鼠标修饰符
.left
鼠标左键触发
.right
鼠标右键触发
.middle
鼠标滚轮键触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <title > 指令修饰符</title > <style > .box {margin :8px 0 ;padding :8px ;border :1px #ccc solid;} [v-cloak] {display :none;} </style > <script src ="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js" > </script > </head > <body > <div id ="app" > <div class ="box" > .trim去空格:<input v-model.trim ="val1" > <br > .number转数字:<input v-model.number ="val2" > <br > .lazy失焦更新:<input v-model.lazy ="val3" > <p > 实时值:{{val1}} | {{val2}} | {{val3}}</p > </div > <div class ="box" @click ="parentClick" > 父容器 <button @click.stop ="childClick" > .stop阻止冒泡</button > <a href ="https://www.baidu.com" @click.prevent > .prevent阻止跳转 </a > </div > <div class ="box" > 回车提交:<input @keyup.enter ="enterFn" > <br > 空格触发:<input @keyup.space ="spaceFn" > </div > <div class ="box" > <button @click.left ="leftFn" > 左键点击</button > <button @click.right ="rightFn" > 右键点击</button > </div > </div > <script > const {createApp} = Vue createApp ({ data ( ){ return { val1 :'' , val2 :'' , val3 :'' } }, methods :{ parentClick ( ){ alert ('父级点击' ) }, childClick ( ){ alert ('子级点击,不会触发父级' ) }, enterFn ( ){ alert ('按下回车' ) }, spaceFn ( ){ alert ('按下空格' ) }, leftFn ( ){ alert ('左键单击' ) }, rightFn ( ){ alert ('右键单击' ) } } }).mount ('#app' ) </script > </body > </html >
五、项目构建安装 一般情况使用Vue3做项目都是使用框架!! 需要下载node.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #vue3官方安装 npm create vue@latest 配置项目名称 √ Project name: vue3_test 是否添加TypeScript支持 √ Add TypeScript? Yes 是否添加JSX支持 √ Add JSX Support? No 是否添加路由环境 √ Add Vue Router for Single Page Application development? No 是否添加pinia环境 √ Add Pinia for state management? No 是否添加单元测试 √ Add Vitest for Unit Testing? No 是否添加端到端测试方案 √ Add an End-to-End Testing Solution? » No 是否添加ESLint语法检查 √ Add ESLint for code quality? Yes 是否添加Prettiert代码格式化 √ Add Prettier for code formatting? No #vite安装 这里直接选择这个即可 npm install vite@latest pnpm install vite #推荐使用 pnpm 安装pnpm npm i pnpm -g
六、文件详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 E:\frontend\vue3-ts\ ├── node_modules/ # 依赖包(自动生成,勿修改) ├── public/ # 静态资源(不打包) │ └── favicon.ico ├── src/ # 核心开发目录 │ ├── assets/ # 图片、样式、字体 │ ├── components/ # 公共组件 │ ├── router/ # 路由配置(TS) │ │ └── index.ts │ ├── stores/ # Pinia 状态管理(TS) │ │ └── counter.ts │ ├── views/ # 页面组件 │ │ ├── Home.vue │ │ └── About.vue │ ├── App.vue # 根组件 │ ├── main.ts # 项目入口(TS 版本) │ ├── env.d.ts # TS 类型声明文件 │ └── vite-env.d.ts # Vite 类型声明 ├── .gitignore # Git 忽略文件 ├── index.html # 入口 HTML ├── package.json # 项目配置 ├── tsconfig.json # TypeScript 配置 ├── tsconfig.node.json # TS Node 配置 ├── vite.config.ts # Vite 配置(TS 版本) └── README.md # 项目说明
src/main.ts文件详解: 1 2 3 4 5 import {createVue} from 'vue' import 'style.css' import App from './App.vue' createVue (App ).mount ('#app' )
src/app.vue文件详解: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <!-- 1. 模板:页面结构(HTML ) --> <template > </template > <!-- 2. 脚本:业务逻辑(TS + <script setup > 语法糖) --> <script setup lang ="ts" > </script > <style scoped > </style >
七、基本语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <script lang="ts"> export default { setup() { let name = "小陈" let age = 18 let count = 0 function countAdd() { count++; <!-- 点击按钮后,页面的count不会变,因为vue3默认不是响应式,但数据会修改!!!! --> } return { name, age, count, countAdd } }, //嵌套vue2写法 data() { return { msg: "你好", count2: 0 <!-- 当点击时,页面的count2会变,vue2是响应式 --> } }, methods: { sayHello() { return "你好呀" } } } </script> <template> <div>{{ name }}</div> <div>{{ age }}</div> <div>{{ msg }}</div> <div>{{ sayHello() }}</div> <button @click="countAdd()">{{ count }}</button> <button @click="count2++">{{ count2 }}</button> </template> <!-- 注:vue3语法也可以和vue2的data、methods同时存在,旧语法可以用this读新语法的数据,但新的不可以读旧的!!!!!-->
setup语法糖 1 2 3 4 5 6 7 8 9 10 11 <template> <div>{{ name }}</div> <div>{{ age }}</div> </template> <!-- setup语法糖 省去: export default return{}--> <script setup lang="ts"> let name = "小陈" let age = 19 </script>
八、基本数据类型和对象类型响应式数据 ref、reactive 1. ref基本/对象引入类型 let 变量名 = ref(值) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <template> <div>{{ name }}</div> <div>{{ age }}</div> <button @click="ageAdd()">点击</button> <div>{{ getObject() }}</div> </template> <!-- setup语法糖--> <script setup lang="ts"> import {ref} from 'vue' //导入 基本/对象数据类型 ref let name = ref("小陈") //使用括号包裹 注:在这里面的数据使用ref会自动加value 如:name.value,age.value let age = ref(19) let games = ref([ //ref也支持对象类型 {id:"game1",name:"王者荣耀"}, {id:"game2",name:"和平精英"} ]) let games.value[0] = {id:"game",name:"部落冲突"} //ref可以直接修改整个对象 function ageAdd(){ age.value++; //修改值需要加上 .value!!!! } function getObject(){ return games.value[0].name //获取数组对象类型在 .value[下标].名称 } </script> <!-- 记得导入到app.vue根组件里面!!! --> <script setup lang="ts"> // import HelloWorld from './components/HelloWorld.vue' import SetupTang from './components/SetUpTang.vue' </script> <template> <!-- <HelloWorld /> --> <SetupTang /> </template>
2. recative对象数据类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <div>recative: {{ hobby[0].name }}</div> <button @click="setHobby()">点击修改</button> </template> <!-- setup语法糖--> <script setup lang="ts"> import {reactive} from 'vue' //导入 基本/对象数据类型 ref let hobby = reactive([ {id:"hobby",name:"唱"}, {id:"hobby2",name:"跳"}, ]) //let sex = reactive("男") 无法赋值 除了object类型的值 //let hobby[0] = {id:"11",name:"篮球"} reactive修改整个对象会丢失响应式!!!! function setHobby(){ hobby[0].name = "篮球" //recative可以不使用 .value获取值 } </script>
3.区别 1.ref可以定义基本类型和对象类型,而reactive不可以定义基本数据类型; 2.使用官方自带插件可以自动提示.value!!!!! 3.ref可以修改整个对象,而reactive不可以!!!!! 如: function changCar(){ cat.value = {brand:”宝马”,price:2000000} //reactive不可以这样写!!! } 4.如果使用reactive又想要修改整个对象,使用object.assign(对象名,{brand:”宝马”,price:2000000}) 5.如果对象层次更深,推荐使用reactive 九、对象结构响应式 toRefs、toRef 上面ref和reactive如果他们的对象进行解构,此时解构的对象不会是响应式,那该怎么办???? 使用toRefs或toRef 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template> <div>{{ nameOne }}</div> <div>{{ name }}</div> <div>{{ id }}</div> </template> <script setup lang="ts"> import {ref,toRefs,toRef} from 'vue' let hobby = ref({id:"one",name:"java"}) //进行解构 let nameOne = toRef(hobby.value,"name") //单值对象解构 ToRef(对象名,"值") let {name,id} = toRefs(hobby.value) //对象解构 ToRefs(对象名) </script>
十、computed计算属性(缓存优化) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <div>{{ NewNum }}</div> <div>{{ num }}</div> <div>{{ plusNum }}</div> <button @click="setNum">修改Num</button> </template> <script setup lang="ts"> import {ref,computed} from 'vue' let num = ref(1) //只读无法修改 let NewNum = computed(() => { // return num.value = num.value * 2 这样会报错!!!! return num.value * 2 //只能获取 }) //可读可写 let plusNum = computed({ get(){ return num.value + 2 }, set(val){ num.value = val - 1 } }) function setNum(){ plusNum.value = 4 //当 plusNum修改时触发set } </script>
十一、watch监听器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <script setup lang="ts"> import {ref,reactive,watch} from 'vue' //引入 let num = ref(1) let my = reactive({name: "小陈","age": 19,hobby: {one: "唱",two: "跳"}}) //使用reactive //监听变量 watch(变量名,(新值,旧值) => {}) const stopWatchB = watch(num,(newValue,oldValue) => { console.log("原值:" + oldValue,"新值:" + newValue); if(num.value > 10){ console.log("监听结束"); stopWatchB() } }) //监听单个对象值 watch(() => 对象值名,(新值) => {}) watch( () => my.name, //必须写成函数返回值 (newName) => { console.log("修改值:" + newName); } ) //监听多个对象值 watch([() => 对象值名,() => 对象值名2],() => {}) watch( [() => my.name,() => my.age], (newVlaue) => { console.log("修改值:",newVlaue); }, ) //监听对象 watch(对象名,(新值) => {}) watch( my, (newMy) => { console.log("修改值:",newMy); }, {immediate: true,deep: true} // immediate 页面刷新就监听, deep 开启深度监听 ) function setNum(){ num.value += 1 } function setObjectOne(){ my.name = "小丽" my.age = 113 } function setMy(){ my.name = "小美" my.age = 29 my.hobby.one = "篮球" } </script> <!-- 注意事项: immediate: true 开启页面刷新即监听 deep: true 开启深度监听 reactive自动开启 ref手动开启 当有两个以上的嵌套时,如果ref不开启监听不到!!! -->
watchEffect自动监听器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script setup> import { ref, watchEffect } from 'vue' const num = ref(1) watchEffect(() => { // 里面用到了 num → 自动监听 num console.log('num 变了:', num.value) }) </script> <!-- 一开始立即执行一次 num.value 一变 → 重新执行 无法获取旧值 不用写 immediate: true,默认就执行 -->
十二、标签ref属性和scoped样式 1 <style scoped> </style> scoped表示style写的所有样式仅在当前页面生效!!!
ref(可用于dom和组件上): dom: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template> <div> <p ref="father">ref获取当前标签</p> <button @click="changeColor">点击变红</button> </div> </template> <script setup lang="ts"> import {ref,onMounted} from 'vue' let father = ref() //为什么不直接使用document?? Vue 是异步更新的,你拿不到 DOM // const p = document.querySelector("p") //挂载之后可拿到 onMounted(() => { console.log('获取DOM:', father.value) }) function changeColor() { father.value.style.color = 'red' //修改元素信息 } </script>
父组件调用子组件方法和数据: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <!-- 子组件 --> <template> </template> <script setup lang="ts"> import {ref} from 'vue' let a = ref(0) let b = ref(1) let c = ref(2) function child(){ return "我是子组件" } defineExpose({a,b,c,child}) //1.必须导入出去 defineExpose </script> <!-- 父组件 --> <template> <div> <button @click="show">点我显示</button> <ToRef_s ref="childs" /> <!-- 2.使用子组件--> </div> </template> <script setup lang="ts"> import ToRef_s from './ToRef_s.vue'; import {ref} from 'vue' let childs = ref() function show(){ if(childs.value){ console.log(childs.value.a); //获取子组件数据 console.log(childs.value.b); console.log(childs.value.c); console.log(childs.value.child()); } } </script>
十三、vue3生命周期 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 创建阶段 ↓ setup() // 最早执行,没有 this ↓ 挂载阶段 ↓ onBeforeMount // 挂载前 ↓ onMounted ✅ 最常用:DOM 渲染完,发请求、操作DOM ↓ 更新阶段 ↓ onBeforeUpdate // 数据更新,DOM 还没更 ↓ onUpdated // DOM 更新完成 ↓ 卸载阶段 ↓ onBeforeUnmount // 销毁前 ↓ onUnmounted ✅ 常用:清除定时器、取消监听
演示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <template> <div> <h2>生命周期演示</h2> <p>计数:{{ count }}</p> <button @click="count++">点我更新</button> </div> </template> <script setup lang="ts"> import { ref, onMounted, onUpdated, onUnmounted } from 'vue' const count = ref(0) let timer: number | null = null console.log('========== setup 最先执行 ==========') // 1. 挂载完成:DOM 已渲染 onMounted(() => { console.log('onMounted:组件挂到页面了') console.log('可以操作 DOM、发请求、开定时器') // 开定时器 timer = setInterval(() => { console.log('定时器运行中...') }, 1000) }) // 2. 更新完成:数据变 → DOM 变完 onUpdated(() => { console.log('onUpdated:页面更新完成') }) // 3. 卸载之前:清理工作 onUnmounted(() => { console.log('onUnmounted:组件要销毁了') // 必须清理定时器! if (timer) { clearInterval(timer) console.log('定时器已清理') } }) </script> <!-- 注:在父子组件中,子组件会被先挂载,父后挂载 -->
十四、自定义Hook 把组件里重复的逻辑抽出来,单独放一个 js/ts 文件,这就叫自定义 Hook。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import {ref} from 'vue' export function useNum ( ){ let num = ref (1 ) const addNum = ( ) => num.value ++; return { num, addNum } }
1 2 3 4 5 6 7 8 9 10 11 <template> <div>{{ num }}</div> <button @click="addNum">点击增加</button> </template> <script setup lang="ts"> import { useNum } from '../hook/useNum' //导入 const {num,addNum} = useNum() //解构即可使用 </script>
案例演示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { ref, onMounted, onUnmounted } from 'vue' export function useMouse ( ) { const x = ref (0 ) const y = ref (0 ) const update = (e : MouseEvent ) => { x.value = e.pageX y.value = e.pageY } onMounted (() => { window .addEventListener ('mousemove' , update) }) onUnmounted (() => { window .removeEventListener ('mousemove' , update) }) return { x, y } }
1 2 3 4 5 6 7 8 9 10 <template> <p>鼠标位置: {{ x }},{{ y }}</p> </template> <script setup lang="ts"> import { useMouse } from '../hook/useMouse' const {x,y} = useMouse() </script>
十五、路由 快速入门 下载路由:
创建页面文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!-- src/views/.. 或者 src/pages/.. --> <!-- Home.vue文件 --> <template> <div>这是首页</div> </template> <script setup lang="ts"> </script> <!-- My.vue文件 --> <template> <div>这是我的</div> </template> <script setup lang="ts"> </script>
创建路由文件: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import {createRouter,createWebHistory} from 'route' import My from '../views/My.vue' import Home from '../views/Home.vue' const route = createRouter ({ history :createWebHistory (), routes :[ { path : "/" redirect : { path : '/father/son' , query : { id : 666 } } }, { path : "/home" , component : Home }, { path : "/my" , component : My } ] }) export default route
mian.ts文件引入路由: 1 2 3 4 5 6 7 import {createApp} from 'vue' import route from './route/index' import App from './app.vue' const app = createApp (App )app.use (route) app.mount ("#app" )
app.vue测试路由: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <script setup lang="ts"> import { RouterLink,RouterView } from 'vue-router'; //引入 RouterLink 跳转链接 RouterView 路由视图,当路由跳转显示位置 </script> <template> <div> <p>路由测试</p> <RouterLink to="/home">首页</RouterLink> <!-- to和它搭配!!!! --> <router-link to="/my">我的</router-link> <!-- vue传统写法 --> <div> <p>路由页面</p> <RouterView></RouterView> <!-- 此时你的路由页面在这里显示 <router-view></router-view> 传统写法--> </div> </div> </template>
注意事项: 1.路由组件通常放到pages或view文件夹里面,一般组件放到component文件夹 2.路由组件切换另一个路由组件时,前面的会被卸载掉,需要使用的时候又会重新挂载!!!! 3.路由工作模式: history模式 格式: history:createWebHistory(); 优点:url不带井号,更接近传统网站url,但需要配合服务端处理路径问题,否则有404 hash模式: 格式: history:createWebHashHistory(); 优点:兼容性更好,不需要服务端配合,且对搜索引擎优化差 4.to的两种写法 写法1:<RouterLink to="/news" active-class="active">新闻</RouterLink> to=”/路由路径” 写法2:<RouterLink :to={path:”/news”} active-class=”active”>新闻</RouterLink> :to={path:”/路由路径”} 嵌套路由 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import {createRouter,createWebHistory} from 'vue-router' import My from '../pages/My.vue' import Home from '../pages/Home.vue' import Name from '../pages/Name.vue' const route = createRouter ({ history : createWebHistory (), routes : [ { path : "/home" , component : Home , children :[ {path : "name" ,component : Name } ] }, { path : "/my" , component : My } ] }) export default route
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div> <p class="one">这是首页</p> <p class="two">访问次数: {{ count }}</p> <button @click="addCount">点击</button> <!-- 子路由出口(必须写) 子组件在这里显示 --> <router-view /> <router-link to="/home/name">去 name 子页面</router-link> </div> </template> <script setup lang="ts"> import {ref} from 'vue' let count = ref(1) function addCount() { count.value++; } </script>
路由传参 如果我要获取父组件发来的消息,怎么办?? 使用 useRoute; useRoute里面含有query方法,里面携带参数值!!! 1 2 3 4 5 6 7 8 9 10 11 12 13 <template> <ul> <li>子组件</li> <li>{{ routeData.query.id }}</li> <!--自带 qurery属性 --> <li>{{ routeData.query.data }}</li> </ul> </template> <script setup lang="ts"> import {useRoute} from 'vue-router' let routeData = useRoute() </script>
父组件方式1(普通路径传参): 1 2 3 4 5 6 7 8 9 10 <template> <div> 父组件 <router-link to="/father/son?id=1&data=今天美">跳转</router-link> <!--标准地址--> <router-view></router-view> </div> </template> <script setup lang="ts"> </script>
父组件方式2(模板字符串传参): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template> <div> 父组件 <router-link :to="`/father/son?id=${id}&data=${data}`">跳转</router-link> <!-- 使用 :to 解析js语句--> <router-view></router-view> </div> </template> <script setup lang="ts"> import {ref} from 'vue' let id = ref(1) let data = ref("天气良好") </script>
父组件传参方式3(对象方式传递): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <div> 父组件 <router-link :to="{ <!-- path 路径, query 参数 --> path:'/father/son', query:{ id, data } }" >跳转</router-link> <router-view></router-view> </div> </template> <script setup lang="ts"> import {ref} from 'vue' let id = ref(1) let data = ref("天气良好") </script>
父组件传参方式4(params占位符传递): 路由: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import {createRouter,createWebHistory} from 'vue-router' import My from '../pages/My.vue' import Home from '../pages/Home.vue' import Name from '../pages/Name.vue' import father from '../pages/father.vue' import son from '../pages/son.vue' const route = createRouter ({ history : createWebHistory (), routes : [ { path :"/father" , component : father, children :[ { path : "son/:id/:data?" , component : son, name : "son" } ] } ] }) export default route
父组件: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template> <div> 父组件 <router-link :to="{ name:'son', <!-- 这个name 和路由的一一对应 --> params: {id,data} <!-- 使用params传递 子组件使用 params.字段访问--> }">跳转</router-link> <router-view></router-view> </div> </template> <script setup lang="ts"> import {ref} from 'vue' let id = ref(1) let data = ref("天气良好") </script>
props配置传参: 路由: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import {createRouter,createWebHistory} from 'vue-router' import My from '../pages/My.vue' import Home from '../pages/Home.vue' import Name from '../pages/Name.vue' import father from '../pages/father.vue' import son from '../pages/son.vue' const route = createRouter ({ history : createWebHistory (), routes : [ { path :"/father" , component : father, children :[ { path : "son" , component : son, props : route => route.query } ] } ] }) export default route
父组件(path + query): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template> <div> 父组件 <router-link :to="{ path:'/father/son', query:{ id, data } }" >跳转</router-link> <router-view></router-view> </div> </template> <script setup lang="ts"> import {ref} from 'vue' let id = ref(1) let data = ref("天气良好") </script>
子组件(defineProps): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template> <ul> <li>子组件</li> <!-- <li>{{ routeData.params.id }}</li> <li>{{ routeData.params.data }}</li> --> <li>{{ id }}</li> <li>{{ data }}</li> </ul> </template> <script setup lang="ts"> defineProps(["id","data"]) //获取参数 </script>
编程式导航(useRouter) 在开发中,不可以只用<RouterLink>标签进行路由跳转,需要使用函数式导航!! 如果我想要点击按钮也可以查看新闻,该怎么办??? 引入 useRouter 1. useRouter().push跳转方式: 跳转方式1(无参数跳转): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script setup lang="ts"> import { RouterLink,RouterView,useRouter } from 'vue-router'; const route = useRouter() function pushOne(){ route.push("/home") //无参数跳转 route.push("路径名") } </script> <template> <div> <p>路由测试</p> <button @click="pushOne">无参数跳转</button> <br> <div> <p>路由页面</p> <!-- <RouterView></RouterView> --> <router-view></router-view> </div> </div> </template>
跳转方式2(带query参数跳转): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <script setup lang="ts"> import { RouterLink,RouterView,useRouter } from 'vue-router'; const route = useRouter() function pushOne(){ route.push({ path: "/my", query: { name: "小陈" } }) } </script> <template> <div> <p>路由测试</p> <button @click="pushOne">带query参数跳转</button> <br> <div> <p>路由页面</p> <!-- <RouterView></RouterView> --> <router-view></router-view> </div> </div> </template> <!-- 接收的页面使用 useRoute 接取即可!! -->
跳转方式3(带params参数跳转): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <script setup lang="ts"> import { RouterLink,RouterView,useRouter } from 'vue-router'; const route = useRouter() function pushOne(){ route.push({ path: "/my", params: {name: "小陈"} }).catch(err => {console.log("跳转出错",err)}) //设定异常处理 } }) </script> <template> <div> <p>路由测试</p> <button @click="pushOne">带params参数跳转</button> <br> <div> <p>路由页面</p> <!-- <RouterView></RouterView> --> <router-view></router-view> </div> </div> </template> <!-- 动态路径需要在route路由文件修改 -->
2. useRoute()r.back()返回 / forword()前进: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <script setup lang="ts"> import { RouterLink,RouterView,useRouter } from 'vue-router'; const route = useRouter() function pushOne(){ route.push({ path: "/my", query: { name: "小陈" } }) } function backOne(){ route.back() //返回上一页 } </script> <template> <div> <p>路由测试</p> <button @click="pushOne">带query参数跳转</button> <br> <button @click="backOne">返回上一页</button> <br> <div> <p>路由页面</p> <!-- <RouterView></RouterView> --> <router-view></router-view> </div> </div> </template>
3. useRouter().replace()跳转并不留下历史记录 用法和 push一模一样这里不重复演示!!!! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <script setup lang="ts"> import { RouterLink,RouterView,useRouter } from 'vue-router'; const route = useRouter() function backOne(){ route.back() } function replaceOne(){ // route.push("/home") //无参数跳转 route.push({ path: "/my", query: { name: "小陈" } }) } </script> <template> <div> <p>路由测试</p> <button @click="replaceOne">跳转并删除历史记录</button> <br> <div> <p>路由页面</p> <!-- <RouterView></RouterView> --> <router-view></router-view> </div> </div> </template>
4. useRouter().go() 前进和后退 1 2 3 router.go (-1 ) router.go (-2 ) router.go (1 )
5.useRouter().addRoute() 动态添加路由 1 2 3 4 5 6 function addRoutes ( ){ route.addRoute ({ path : "/my" , component : Son2 }) }
6.useRoute().hasRoute() 判断路由存在 1 2 3 4 5 6 7 if (!router.hasRoute ('Son' )) { router.addRoute ({ name : 'Son' , path : '/father/son' , component : Son }) }
7.useRouter.currentRoute() 1 2 3 4 5 6 7 8 9 10 11 12 import { useRouter } from 'vue-router' const router = useRouter ()const route = router.currentRoute .value console .log (route.path )console .log (route.query )console .log (route.params )console .log (route.fullPath )
路由守卫 全局路由守卫: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import {createRouter,createWebHistory} from 'vue-router' import My from '../pages/My.vue' import Home from '../pages/Home.vue' import Name from '../pages/Name.vue' import father from '../pages/father.vue' import son from '../pages/son.vue' const route = createRouter ({ history : createWebHistory (), routes : [ { path : "/home" , component : Home , children :[ {path : "name" ,component : Name } ] }, { path : "/my" , component : My } ] }) route.beforeEach ((to,from ,next ) => { const token = localStorage .getItem ('token' ) if (to.path != "/my" ){ return next () } if (token){ next () }else { alert ("没有登录!!" ) next ("/home" ) } }) router.afterEach ((to ) => { document .title = to.meta .title || '后台管理系统' }) export default route
独享路由守卫: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import {createRouter,createWebHistory} from 'vue-router' import My from '../pages/My.vue' import Home from '../pages/Home.vue' import Name from '../pages/Name.vue' import father from '../pages/father.vue' import son from '../pages/son.vue' const route = createRouter ({ history : createWebHistory (), routes : [ { path : "/home" , component : Home , meta : { title : "首页" }, children :[ {path : "name" ,component : Name } ], beforeEnter : (to,from ,next ) => { next () } } }) export default route
组件内守卫(onBeforeRouteLeave): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <template> <div> <h1>表单页面</h1> <input type="text" v-model="formData" placeholder="请输入内容" @input="handleInput" /> <button @click="save">保存</button> </div> </template> <script setup lang="ts"> import { ref } from 'vue' import { onBeforeRouteLeave } from 'vue-router' const formData = ref('') const isChanged = ref(false) // 输入后标记为已修改 const handleInput = () => { isChanged.value = true } // 保存后重置状态 const save = () => { // 模拟保存 isChanged.value = false alert('保存成功') } // 离开页面时判断是否未保存 onBeforeRouteLeave((to, from) => { if (isChanged.value) { return '您有未保存的内容,确定要离开吗?' } }) </script>
十六、pinia pinia是什么? 它也充当的是一个存储数据的作用,存储在pinia的数据允许我们在各个组件中使用。 简单来说就是一个存储数据的地方,存放在Vuex中的数据在各个组件中都能访问到,它是Vue生态中重要的组成部分。 1.安装与配置: 1 2 npm i pinia #如果是pnpm 使用 pnpm i pinia npm i pinia-plugin-persistedstate #数据持久化,刷新页面不丢失 #如果是pnpm 使用pnpm
main.ts文件: 1 2 3 4 5 6 7 8 9 10 11 12 import { createApp } from 'vue' import {createPinia} from 'pinia' import persist from 'pinia-plugin-persistedstate' import './style.css' import App from './App.vue' const app = createApp (App )const pinia = createPinia () app.use (pinia.use (persist)) app.mount ("#app" )
创建文件(src/stores/..) 选项式(类似vue2): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import { defineStore } from "pinia" ;import "pinia-plugin-persistedstate" ; const useStore = defineStore ('user' ,{ state : () => ({ name : "小陈" , age : 20 , token : '' }), persist : true , getters : { NewAge : (state ) => state.age * 2 }, actions : { updateName (name :string ) { this .name = name }, login (token :string ){ this .token = token } } }) export default useStore
组合式(类型vue3 推荐): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import { defineStore } from "pinia" ;import "pinia-plugin-persistedstate" ;import {ref,computed} from 'vue' const useStore = defineStore ('user' ,() => { let name = ref ("小陈" ) let age = ref (20 ) let token = ref ('' ) let NewAge = computed (() => age.value * 2 ) function updateName (Newname :string ) { name.value = Newname } function login (NewToken :string ){ token.value = NewToken } function persists ( ){ token.value = "10001" } return { name, age, token, NewAge , updateName, login, persists } },{ persist : true }) export default useStore
2.存储和读取数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <button @click="per">修改token</button> </template> <script setup lang="ts"> import useStore from '../stores' //1.引入 const store = useStore() //获取数据 console.log(store.age); console.log(store.$state.name); //也可以使用 $state 获取 console.log(store.NewAge); //修改数据 3种方式 store.name = "小王" //直接修改 store.updateName("小丽") //使用函数,推荐!! store.$patch({ //使用 $patch函数批量修改 name: "校长", age: 56 }) console.log("修改后的名称:" + store.name); store.login("10000") console.log(store.token); //演示开启持久化区别 不开启: 点击按钮修改,此时刷新页面修改失效 开启没问题 function per(){ store.persists() console.log(store.token); } </script>
3.解构pinia数据(storeToRefs) 如果我想将他们进行解构,但数据不会是响应式的,怎么办?? 使用storeToRefs 1 2 3 4 5 6 7 8 9 10 11 <script> import { useCountStore } from '@/store/counts' ; import {storeToRefs} from 'pinia' ; let countStore = useCountStore () let {sum,school,address} = storeToRefs (countStore); </script>
4.pinia监听器($subscribe) 1 2 3 4 5 6 store.$subscribe((mutation, state ) => { console .log ('最新 token:' , state.token ) })
十七、自定义事件 defineEmits() 你自己起个名字,比如 @getMsg、@update → 这就叫自定义事件 代码流程: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <!-- 创建一个组件 --> <template> </template> <script setup lang="ts"> const emit = defineEmits(["data"]) //使用defineEmits(['data']) 导出自定义事件 emit("data",123) //存入数据 emit("自定义事件名",数据) </script> <!-- 使用 --> <template> <div> <Son @data="SonMsg"></Son> <!-- 这里使用自定义事件名 --> <p> {{childMsg}} </p> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import Son from './son.vue' let childMag = ref() let SonMsg = (val:any) => { //val就是数据 console.log(val); childMag.value = val } </script>
表单案例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <template> <div> <form action=""> 姓名:<input type="text" v-model="form.username"> 密码: <input type="password" v-model="form.password"> <button type="button" @click="send">提交</button> </form> </div> </template> <script setup lang="ts"> import {ref} from 'vue' let form = ref({ username: '', password: '' }) const emit = defineEmits(["data"]) function send(){ emit("data",form.value) } </script> <!-- 使用 --> <template> <div> <p>父盒子</p> <Son @data="SonMsg"></Son> <div v-if="childMag"> <p>姓名: {{ childMag.username }}</p> <p>密码: {{ childMag.password }}</p> </div> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import Son from './son.vue' let childMag = ref() let SonMsg = (val:any) => { console.log(val); childMag.value = val } </script>
十八、组件通讯 1.父子通讯 父传子(defineProps): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <!-- 父组件 --> <template> <ToRef_s :msg="message" :age :count="999"/> <!-- 细节: 必须是 :变量名 格式, 变量名一致可省略,也可直接赋值!!!!--> </template> <script setup lang="ts"> import ToRef_s from './ToRef_s.vue'; import {ref} from 'vue' let message = ref("父亲的语句") let age = ref(50) </script> <!-- 子组件 --> <template> <div>{{ msg }}</div> <div>{{ age }}</div> <div>{{ count }}</div> </template> <script setup lang="ts"> import {ref} from 'vue' // const data = defineProps({ //普通语法 // msg:String, // age: Number, // count: Number // }) const props = defineProps<{ //ts语法 msg?: String age?: Number count?: Number }>() </script>
子传父(自定义事件名 ): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <!-- 子组件 --> <template> <div> <button @click="send">发送给父</button> <!-- 1.设置一个点击事件 --> </div> </template> <script setup lang="ts"> const emit = defineEmits(["childMsg"]) //3.导出自定义数据名 defineEmits() function send(){ emit('childMsg',"我是子组件") // 2.使用 emit("自定义事件名",数据) } </script> <!-- 父组件 --> <template> <div> <Son @childMsg="SonMsg"></Son> <!-- 5.在子组件使用自定义事件名,获取数据 --> <p>{{ childMag }}</p> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import Son from './son.vue' // 4.获取子组件 let childMag = ref() let SonMsg = (val:string) => { // 6.使用函数,获取数据 val就是数据!!! console.log(val); childMag.value = val } </script>
2.祖孙通讯(了解) 方式1($attr): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <!-- 爷爷组件 --> <template> <div> <h2>爷爷</h2> <Father name="张三" age="18" msg="来自爷爷" /> <!-- 数据传递给父亲 --> </div> </template> <script setup> import Father from './Father.vue' </script> <!-- 父亲组件 --> <template> <div> <h3>爸爸(中间人)</h3> <Son v-bind="$attrs" /> <!-- 继续传递,如果父亲在此使用 defineProps() 读取部分数据, 剩下的会继续传递,使用的不会!!! --> </div> </template> <script setup> import Son from './Son.vue' </script> <!-- 孙子组件 --> <template> <div> <h4>孙子</h4> <p>姓名:{{ name }}</p> <p>年龄:{{ age }}</p> <p>消息:{{ msg }}</p> </div> </template> <script setup> defineProps(['name', 'age', 'msg']) //直接使用 defineProps() 读取即可 </script>
方式2(provide /inject): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <!-- 爷爷组件 --> <template> <div class="grandpa"> <h3>我是爷爷</h3> <Son/> </div> </template> <script setup lang="ts"> import { provide } from 'vue' import Son from './son.vue' // 给后代提供数据 provide("名称",数据) provide('gift', '爷爷给的红包') </script> <!-- 父亲组件 什么操作都不要--> <template> <div class="father"> <h3>我是爸爸(中间人)</h3> <Child/> </div> </template> <script setup lang="ts"> import Child from './grandChild.vue' </script> <!-- 孙子组件 --> <template> <div class="son"> <h3>我是孙子</h3> <p>拿到:{{ gift }}</p> </div> </template> <script setup lang="ts"> import { inject } from 'vue' // 直接拿爷爷 provide 的东西 const gift = inject('gift') </script>
3.父组件修改子组件数据 ref和defineExpose 在第九,这里不演示 4.获取 子、父实例(了解) 父获得子($ref): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <!-- 子组件 --> <script setup> const sayHi = () => { alert('你好,我是子组件!') } // 必须暴露,外面才能访问 defineExpose({ sayHi }) </script> <!-- 父组件 --> <template> <div> <Son ref="mySon" /> <button @click="get($refs)"></button> <!-- 使用 $refs --> </div> </template> <script setup> import Son from './Son.vue' import { ref } from 'vue' // 对应模板里的 ref="mySon" const mySon = ref(null) function get(x){ x.value.sayHi() } </script>
子获得父($parent) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <!-- 子组件 --> <template> <div class="child2"> <h3>子组件2</h3> <button @click="minusHouse($parent)">干掉父亲房产</button> //1.创建函数,参数是: $parent </div> </template> <script setup lang="ts" name="Child2"> function minusHouse(value:any){ value.house -= 1 //2.进行操作属性 } </script> <!-- 父组件 --> <script setup lang="ts" name="Father"> import Child1 from './Child1.vue' import Child2 from './Child2.vue' import { ref,reactive } from "vue"; let house = ref(4) let c1 = ref() let c2 = ref() defineExpose({house}) //3.将父属性进行公开!!!!! </script>
5. 全局组件通信 mitt(了解) Mitt 就是 Vue3 里最简洁的「全局事件总线」 ,用来解决 任意组件间通信 (父子、祖孙、兄弟、跨页面),比 $parent、$attrs 干净、通用得多。安装与配置: 1 npm i mitt #如果是 pnpm 使用 pnpm i mitt
写一个util文件: 1 2 3 4 5 6 7 import mitt from 'mitt' const emitter = mitt ()export default emitter
常用API:
方法
作用
用法示例
说明
on(事件名, 回调)
监听事件
bus.on('msg', (data) => {})
等待别人发送,一触发就执行
emit(事件名, 数据)
触发事件
bus.emit('msg', 'hello')
发送消息,可带参数
off(事件名, 回调)
取消监听
bus.off('msg', fn)
组件销毁时必须用,防内存泄漏
on('*', 回调)
监听所有事件
bus.on('*', (type, data) => {})
监听任意事件触发
all
所有事件集合
bus.all
很少用,清空事件用
代码演示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <!-- 发送者 --> <template> <button @click="datas">点我测试</button> </template> <script setup lang="ts"> import mitter from '../util/mitter' function datas() { mitter.emit('grand', '我是传递的数据') } </script> <!-- 接收者 --> <template> <div>接收组件</div> </template> <script setup lang="ts"> import mitter from '../util/mitter' import { onMounted, onUnmounted } from 'vue' onMounted(() => { mitter.on('grand', (msg) => { //获取 alert("收到数据:" + msg) }) }) onUnmounted(() => { mitter.off('grand') //销毁 }) </script> <!-- 注: app.vue要同时使用这两个组件 -->
十九、插槽 在组件中,如果我想在里面写入标签,如: <Gategory> <span>111<span> </Gateory>,但span不会生效,怎么办?? 使用 <slot></slot> 默认插槽 让父组件可以往子组件里塞 HTML 内容 。默认插槽 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!-- 子组件 --> <template> <button>子按钮</button> <!-- 插入的标签放到这里面 只能有一个默认插槽!!--> <slot></slot> </template> <script setup lang="ts"> </script> <!-- 父组件 --> <template> <son> <h1>插入内容</h1> <!-- 直接在子组件插入 --> </son> </template> <script setup lang="ts"> import son from './son.vue' </script>
具名插槽 给插槽写入name属性就叫具名插槽 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!-- 子组件 --> <template> <button>子按钮</button> <!-- 插入的标签放到这里面--> <slot></slot> <footer> <slot name="floor"></slot> <!-- 具名插槽 可以有多个--> <slot name="two">hello</slot> <!-- 如果父组件没有使用这个插槽,默认内容显示 hello </footer> </template> <script setup lang="ts"> </script> <!-- 父组件 --> <template> <son> <h1>插入内容</h1> <!-- 直接在子组件插入 --> <template #floor> <!-- 使用 template 标签 # + 具名名称 <template #floor />--> <p>结语:好好敲!!!</p> </template> </son> </template> <script setup lang="ts"> import son from './son.vue' </script>
作用域插槽 子组件把数据传给父组件插槽用。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <!-- 子组件 --> <template> <button>子按钮</button> <!-- 插入的标签放到这里面--> <slot></slot> <main> <slot name="main" :data="{name: '小陈',age : 20}"></slot> <!-- 具名 + 作用域(data) --> </main> <footer> <slot name="floor"></slot> <!-- 具名插槽 可以有多个--> <slot name="two">hello</slot> <!-- 如果父组件没有使用这个插槽,默认内容显示 hello </footer> </template> <script setup lang="ts"> </script> <!-- 父组件 --> <template> <son> <h1>插入内容</h1> <!-- 直接在子组件插入 --> <template #main="{ data }"> <!-- {} 表示解构的数据 --> 传递数据:{{ data.name }},{{ data.age }} </template> <template #floor> <!-- 使用 template 标签 # + 具名名称 <template #floor />--> <p>结语:好好敲!!!</p> </template> </son> </template> <script setup lang="ts"> import son from './son.vue' </script>
二十、全局API 1. app.createApp() 作用 :创建 Vue 实例(入口)
1 2 3 4 5 import { createApp } from 'vue' import App from './App.vue' const app = createApp (App )app.mount ('#app' )
2. app.component() 作用 :注册全局组件 (任何页面都能用)
1 2 3 import Card from './components/Card.vue' app.component ('Card' , Card )
3. app.use() 作用 :安装插件(最常用)
1 2 3 4 import Pinia from 'pinia' import router from './router' app.use (Pinia ) app.use (router)
4. app.provide() 作用 :全局注入值(所有组件都能 inject 拿到)
1 app.provide ('globalMsg' , '我是全局数据' )
二十一、其他常用API 1.shallowRef和shallowReactove 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 shallowRef: 只监听 .value 的变化,内部属性不监听 <script setup> import { shallowRef } from 'vue' const obj = shallowRef({ name: '小明', info: { age: 18 } }) // 不会更新视图 ❌(内部属性不监听) const change1 = () => { obj.value.name = '小红' } // 会更新视图 ✅(因为改了 .value) const change2 = () => { obj.value = { name: '小红' } } </script> shallowReactove: 只监听对象第一层,深层不监听 <script setup> import { shallowReactive } from 'vue' const obj = shallowReactive({ name: '小明', info: { age: 18 } }) // 会更新 ✅(第一层) const change1 = () => { obj.name = '小红' } // 不会更新 ❌(深层) const change2 = () => { obj.info.age = 20 } </script>
2.readonly和shallReadonly 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 readonly: 深度只读,所有层级都不能改 <script setup> import { reactive, readonly } from 'vue' const obj = reactive({ name: 'aaa', info: { age: 18 } }) const readOnlyObj = readonly(obj) // 全部报错 ❌ readOnlyObj.name = 'bbb' readOnlyObj.info.age = 20 </script> shallReadonly: 浅只读,只有第一层不能改,深层可以改 <script setup> import { reactive, shallowReadonly } from 'vue' const obj = reactive({ name: 'aaa', info: { age: 18 } }) const sReadonly = shallowReadonly(obj) // 第一层:报错 ❌ sReadonly.name = 'bbb' // 深层:可以改 ✅ sReadonly.info.age = 20 </script>
3.toRow和markRaw 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 toRow: 从 reactive/readonly 代理中,取出原本的普通对象(脱离响应式)。 修改原始对象 不触发视图更新 只对 reactive/readonly 有效,对 ref 对象要 .value <script setup> import { reactive, toRaw } from 'vue' const state = reactive({ name: '小明', age: 18 }) // 获取原始对象 const rawState = toRaw(state) // 修改响应式 → 视图更新 ✅ const changeReactive = () => { state.age++ } // 修改原始 → 不更新 ❌ const changeRaw = () => { rawState.age++ } console.log(state === rawState) // false(代理 vs 原始) console.log(toRaw(state) === rawState) // true </script> markRaw: 给对象打标记 → 永远不会被 reactive 变成响应式。 <script setup> import { reactive, markRaw } from 'vue' // 标记为“永远原始” const config = markRaw({ theme: 'dark', api: 'https://xxx' }) // 放进 reactive 也不会变响应式 const state = reactive({ user: { name: '小红' }, // 响应式 ✅ config // 永远非响应式 ❌ }) // 修改 config → 不触发更新 const changeConfig = () => { state.config.theme = 'light' } // 修改 user → 正常更新 ✅ const changeUser = () => { state.user.name = '小兰' } </script>
4.customRef 自己封装一个自定义 ref ,可以手动控制依赖收集 和触发更新 ,常用于做防抖、节流、懒加载 等。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <script import { customRef } from 'vue' function myRef(value) { return customRef((track, trigger) => { return { get() { track() // 收集依赖 return value }, set(newVal) { value = newVal trigger() // 触发更新 } } }) } </script>
防抖案例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <script setup> import { customRef } from 'vue' // 防抖 ref function useDebounceRef(value, delay = 300) { let timer = null return customRef((track, trigger) => { return { get() { track() return value }, set(newVal) { clearTimeout(timer) timer = setTimeout(() => { value = newVal trigger() }, delay) } } }) } // 使用 const keyword = useDebounceRef('', 500) </script> <template> <input v-model="keyword" /> <p>{{ keyword }}</p> </template>
5.Teleport传送标签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <div> <button @click="show = true">打开</button> <Teleport to="body"> <div class="modal"> 弹窗内容 <button @click="show = false">关闭</button> </div> </Teleport> </div> </template> <script setup> import { ref, onMounted } from 'vue' const show = ref(false) const isMounted = ref(false) // 等组件挂载完成再渲染 Teleport onMounted(() => { isMounted.value = true }) </script> <style> .modal { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 9999; color: white; } </style>
6.Suspense加载标签 等待异步组件加载,期间显示 loading 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <!-- AsyncPage.vue --> <script setup> // 里面有 await 就会变成异步组件 await new Promise(resolve => setTimeout(resolve, 1500)) </script> <template> <div>我是异步加载的页面</div> </template> <script setup> // 直接引入 import AsyncPage from './AsyncPage.vue' </script> <template> <Suspense> <!-- 真正要显示的内容 --> <template #default> <AsyncPage /> <!-- 异步内容 --> </template> <!-- 加载中显示的内容 --> <template #fallback> <div>加载中...</div> </template> </Suspense> </template>
7.defineAsyncComponent 异步组件懒加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script setup> import { defineAsyncComponent } from 'vue' // 定义异步组件 const Dialog = defineAsyncComponent(() => import('./components/Dialog.vue') ) // 也可以加加载中、错误处理 const Dialog2 = defineAsyncComponent({ loader: () => import('./components/Dialog.vue'), loadingComponent: () => <div>加载中...</div>, errorComponent: () => <div>加载失败</div>, delay: 200, timeout: 5000 }) </script> <template> <!-- 用到时才会加载 --> <Dialog v-if="show" /> </template>
二十二、ElementPlus组件库 ElementPlus组件库,里面有非常多的组件可以供我们使用!!!!!!! 安装与配置 1 npm i element-plus #如果是 pnpm pnpm i element-plus
mian.ts 1 2 3 4 5 6 7 8 9 10 11 12 import { createApp } from 'vue' import App from './App.vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' const app = createApp (App )app.use (ElementPlus ) app.mount ('#app' )
简单使用规则 组件名全部以 el- 开头 事件和原生一样 @click 绑定数据用 v-model 样式自动带,不用自己写 代码演示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <template> <div> <h2>Element Plus 测试</h2> <!-- 按钮 --> <el-button type="primary">主要按钮</el-button> <el-button type="success">成功按钮</el-button> <el-button type="danger">危险按钮</el-button> <!-- 输入框 --> <el-input v-model="inputValue" placeholder="请输入" /> <!-- 弹窗 --> <el-dialog v-model="dialogVisible" title="标题"> <span>这是一段信息</span> </el-dialog> </div> </template> <script setup> import { ref } from 'vue' const inputValue = ref('') const dialogVisible = ref(false) </script>
常用组件: 1.基础组件
组件名
标签
常用属性
Button 按钮
<el-button>
type、size、plain、round、circle、disabled、loading
Link 链接
<el-link>
type、disabled、underline、href
Container 布局
<el-container>
direction
Header 头部
<el-header>
height
Aside 侧边栏
<el-aside>
width
Main 内容区
<el-main>
—
Footer 底部
<el-footer>
height
Row 行
<el-row>
gutter、justify、align
Col 列
<el-col>
span、offset、pull、push、xs/sm/md/lg/xl
Space 间距
<el-space>
size、direction、wrap、alignment
2.表单组件
组件名
标签
常用属性
Form 表单
<el-form>
model、rules、inline、label-width、status-icon
FormItem 表单项
<el-form-item>
prop、label、required、rules
Input 输入框
<el-input>
v-model、type、placeholder、disabled、maxlength、clearable
InputNumber 数字框
<el-input-number>
v-model、min、max、step、disabled、controls
Select 选择器
<el-select>
v-model、placeholder、disabled、clearable、multiple
Option 选项
<el-option>
label、value、disabled
Checkbox 多选框
<el-checkbox>
v-model、label、disabled、border
Radio 单选框
<el-radio>
v-model、label、disabled、border
Switch 开关
<el-switch>
v-model、disabled、active-text、inactive-text
DatePicker 日期
<el-date-picker>
v-model、type、placeholder、disabled、format
Upload 上传
<el-upload>
action、multiple、limit、accept、file-list
Rate 评分
<el-rate>
v-model、max、disabled、allow-half、show-score
3.数据展示
组件名
标签
常用属性
Table 表格
<el-table>
data、border、stripe、size、height
TableColumn 列
<el-table-column>
prop、label、width、fixed、sortable
Pagination 分页
<el-pagination>
total、page-size、current-page、layout
Tag 标签
<el-tag>
type、size、closable、disable-transitions
Card 卡片
<el-card>
header、shadow
Collapse 折叠面板
<el-collapse>
v-model、accordion
Tree 树形控件
<el-tree>
data、props、node-key、default-expand-all
Image 图片
<el-image>
src、alt、fit、lazy、preview-src-list
Empty 空状态
<el-empty>
description、image-size
4.导航
组件名
标签
常用属性
Menu 菜单
<el-menu>
mode、default-active、background-text-color
SubMenu 子菜单
<el-sub-menu>
index、disabled
MenuItem 菜单项
<el-menu-item>
index、disabled
Tabs 标签页
<el-tabs>
v-model、type、closable、tab-position
Breadcrumb 面包屑
<el-breadcrumb>
separator
Dropdown 下拉
<el-dropdown>
trigger
Steps 步骤条
<el-steps>
active、space、direction
5.反馈
组件名
标签
常用属性
Dialog 弹窗
<el-dialog>
v-model、title、width、close-on-click-modal
Drawer 抽屉
<el-drawer>
v-model、title、direction、size
Tooltip 提示
<el-tooltip>
content、placement、effect
Popover 弹出框
<el-popover>
placement、trigger
Alert 警告
<el-alert>
title、type、description、closable
Loading 加载
v-loading
lock、background、spinner
6.常用 JS 调用(非标签)
方法
调用方式
常用配置
Message 消息提示
ElMessage
message、type、duration、showClose
MessageBox 确认框
ElMessageBox.confirm
title、message、type
Notification 通知
ElNotification
title、message、type、duration
二十三、axios(扩展) 安装
使用: 跨域问题: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig ({ plugins: [vue() ], server: { proxy: { '/api' : { target: 'http://localhost:8080' , changeOrigin: true , rewrite: (path) => path.replace(/^\/api/, '' ) } } } }) @Controller @CrossOrigin public class UserController {}
get用法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 async function getList ( ) { const res = await axios.get ('/api/list' , { params : { id : 1 , name : '小明' }, headers : { token : 'xxxxxxxxxxxx' , 'Content-Type' : 'application/json' }, timeout : 5000 , responseType : 'json' , withCredentials : true , }) console .log (res.data ) }
post用法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 async function addData ( ) { const postData = { name : "张三" , age : 18 } const res = await axios.post ("/api/add" , postData, { headers : { token : "xxxxxxxxxx" , "Content-Type" : "application/json" }, timeout : 10000 , withCredentials : true }) console .log (res.data ) }
delete: 1 2 3 4 5 6 7 8 async function deleteData ( ) { const res = await axios.delete ('/api/delete' , { params : { id : 123 }, headers : { token : 'xxx' }, timeout : 10000 , withCredentials : true }) }
put: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 async function addData ( ) { const postData = { name : "张三" , age : 18 } const res = await axios.post ("/api/add" , postData, { headers : { token : "xxxxxxxxxx" , "Content-Type" : "application/json" }, timeout : 10000 , withCredentials : true }) console .log (res.data ) }
工具封装: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import axios from 'axios' const service = axios.create ({ baseURL : 'http://localhost:8080' , timeout : 10000 }) service.interceptors .request .use ( (config ) => { const token = localStorage .getItem ('token' ) if (token) { config.headers .token = token } return config }, (error ) => { return Promise .reject (error) } ) service.interceptors .response .use ( (response ) => { return response.data }, (error ) => { console .error ('请求错误:' , error) return Promise .reject (error) } ) const request = { get (url, params = {} ) { return service.get (url, { params }) }, post (url, data = {} ) { return service.post (url, data) }, put (url, data = {} ) { return service.put (url, data) }, delete (url, params = {} ) { return service.delete (url, { params }) } } export default request