Vue教程 Vue基础 1. 创建Vue3(Vue3 )工程 vite (推荐)
具体操作(官方文档 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ## 1.创建命令 npm create vue@latest ## 2.具体配置 ## 配置项目名称 project name:vue3_test ## 是否添加ts(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 ## 是否添加Prettier代码格式 Add Prettier for code formatting? No
2. 介绍Vue3文件 .vscode:存放Vue官方推荐的插件
public:页签图标
src:源代码文件
main.ts:
1 2 3 4 import { createApp } from 'vue' import App from './App.vue' createApp (App ).mount ('#app' )
App.vue:根组件
components:组件
.gitignore:git忽略文件
env.d.ts:让ts认识.txt、.jpg等文件
inedx.html:入口文件(通过引用src里的main.ts来运行src里面内容)
README.md:工程介绍文件
package类文件:依赖声明文件
tsconfig类文件:ts配置文件
vite.config.ts:整个项目配置文件
Tip:使用npm i一键补全依赖
package.json可以查看命令(如启动命令npm run dev)
3. .Vue文件 大概模版
1 2 3 4 5 6 7 8 9 10 11 <template> <!-- html 结构 --> </template> <script> //JS或TS代码 交互 </script> <style> /* 样式 */ </style>
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template> <div class="app"> <h1>hello</h1> </div> </template> <script> export default{ name: 'App' //组件名 } </script> <style> .app{ background-color: #ddd; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; } </style>
4. 语法 4.1 基础 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 <template> <div class="person"> <h2>姓名:{{ name }}</h2> //变量展示方式 <h2>年龄:{{ age }}</h2> <button @click="changeName">修改姓名</button> //触发函数写法 <button @click="changeAge">修改年龄</button> <button @click="showTel">查看联系方式</button> </div> </template> <script> export default { name: 'Person', //组件名称 data() { //数据 return { name: '张三', age: 18, tel: '123456789' } }, methods: { //函数方法实现 showTel() { alert(this.tel) }, changeName() { this.name = 'zhangsan' }, changeAge() { this.age += 1 } } } </script> //scoped属性可以让样式只在当前组件生效 <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button{ margin: 0 5px; } } </style>
4.2 Vue3核心语法 4.2.1 OptionsAPI与CompositionAPI
vue2的API设计是Options(配置)风格
vue3的API设计是Composition(组合)风格
4.2.1.1 OptionalAPI的弊端 Options类型的API,数据、方法、计算属性等,是分散在:data、methods、computed中的,若想增或者修改一个需求,就需要分别修改:data、methods、computed,不便于维护和复用
4.2.1.2 CompositionAPI的优势 可以用函数的方法,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起
4.2.2 setup setup是Vue3中的一个新的配置项,值是一个函数,它是Composition API表演的舞台,组件中所用到的:数据、方法、计算属性、监视等,均配置在setup中。
特点:
setup函数返回的对象中的内容,可直接在模板中使用
setup中访问this是undefined
setup函数会在beforeCreate之前调用,它是领先所有钩子先执行
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 <template> <div class="person"> <h2>姓名:{{ name }}</h2> <h2>年龄:{{ age }}</h2> <button @click="changeName">修改姓名</button> <button @click="changeAge">修改年龄</button> <button @click="showTel">查看联系方式</button> </div> </template> <script> export default { name: 'Person', setup() { // console.log(this) //此时this是undefined //数据 let name = '张三' //此时name不是响应式数据 let age = 18 //此时age不是响应式数据 let tel = '1234567890' //此时tel不是响应式数据 //方法 function changeName() { name = '李四' //这样修改name页面不会发生变化,但name的值已经改变 } function changeAge() { age += 1 } function showTel() { alert(tel) } return {name,age,changeName,changeAge,showTel} //要返回出去才能生效或者可以取个别名如name使用a:name // return () => 'hh' setup也可以直接返回渲染函数(此为箭头函数) } } </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button{ margin: 0 5px; } } </style>
data、methods和setup可以同时存在,data可以读取setup数据要使用this.,setup无法读取data ++
setup语法糖
1 2 3 <scripts setup> let a = 123 //自动返回 </scripts>
4.2.3 ref基本类型的响应式数据和reactive对象类型的响应式数据 4.2.3.1 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 43 44 45 46 47 48 <template> <div class="person"> <h2>姓名:{{ name }}</h2> <!-- 自动转换为name.value --> <h2>年龄:{{ age }}</h2> <button @click="changeName">修改姓名</button> <button @click="changeAge">修改年龄</button> <button @click="showTel">查看联系方式</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { ref } from 'vue' //添加ref包 //数据 let name = ref('张三') //为需要响应式改变的数据套上ref let age = ref(18) let tel = '1234567890' //此时tel不是响应式数据 //方法 function changeName() { name.value = '李四' } function changeAge() { age.value += 1 } function showTel() { alert(tel) } </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.3.2 reactive 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> <div class="person"> <h2>一辆{{ car.brand }},价值{{ car.price }}</h2> <button @click="changePrice">修改汽车价格</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { reactive } from 'vue' //引入reactive //数据 let car = reactive({ //reactive创建响应式对象 brand: 'BMW', price: 100 }) //方法 function changePrice() { car.price += 10 } </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.3.3 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 <template> <div class="person"> <h2>一辆{{ car.brand }},价值{{ car.price }}</h2> <button @click="changePrice">修改汽车价格</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { ref } from 'vue' //ref //数据 let car = ref({ //ref创建响应式对象 brand: 'BMW', price: 100 }) //方法 function changePrice() { car.value.price += 10 //修改需要加.value数据 } </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.3.4 ref和reactive的区别 宏观角度:
ref用来定义:基本数据类型、对象数据类型
reactive用来定义:对象类型数据
区别:
ref创建的变量必须使用.value(使用插件vue-official可以配置自动加value)
reactive重新分配一个对象,会失去响应式(可使用Object.assign去整体替换) Object.assign(car,{brand:’123’,price:’12’}) 直接用car={brand:’123’,price:’12’}响应式会失效,也不能加reactive包裹。
使用原则:
若需要一个基本类型的响应式数据,必须使用ref
若需要一个响应式对象,层级不深,ref、reactive均可
若需要一个响应式对象,且层级深,推荐reactive
4.2.4 toRefs和toRef 让其结构出来的依旧拥有响应式数据且与原数据绑定
toRefs可以解构全部的
toRef可以解构指定的
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 <template> <div class="person"> <h2>姓名:{{ person.name }}</h2> <h2>年龄:{{ person.age }}</h2> <button @click="changeName">修改姓名</button> <button @click="changeAge">修改年龄</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { reactive,toRefs,toRef } from 'vue'; let person = reactive({ name: '张三', age: 18 }) let {name,age} = toRefs(person) let ages = toRef(person,'age') function changeName() { person.name += '~' } function changeAge() { person.age += 1 } </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.5 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <template> <div class="person"> 姓:<input type="text" v-model="firstName"> <br> 名:<input type="text" v-model="lastName"> <br> 姓名:<span>{{ fullName }}</span> <br> <button @click="changeFullName">修改全名</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { ref,computed } from 'vue'; let firstName = ref('ma'); let lastName = ref('yun'); //这么定义的fullName是一个计算属性,且只能读取,不能修改 // let fullName = computed(()=>{ // return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value // }) //可读可写的fullName let fullName = computed({ get() { return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value }, set(newValue) { let [first,last] = newValue.split('-'); firstName.value = first; lastName.value = last; } }) function changeFullName() { fullName.value = 'Ma-Yun'; } </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.6 watch监视
作用:监视数据的变化
特点:vue3中的watch只能监视四种数据
ref定义的数据
reactive定义的数据
函数返回一个值(getter函数)
一个包含上述内容的数组
watch第一个参数是:被监视的数据
watch第二个参数是:监视的回调
watch的第三个参数是:配置对象(deep,immediate等等)
4.2.6.1情况一 监视ref定义的基本类型数据:直接写数据名即可,监视的是其value值
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 <template> <div class="person"> <h1>监视情况一:</h1> <h2>当前求和为:{{ sum }}</h2> <button @click="addSum">+1</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { ref,watch } from 'vue' //数据 let sum = ref(0) //方法 function addSum() { sum.value++ } //监视情况一 const stopWatch = watch(sum, (newValue, oldValue) => { console.log(newValue, oldValue) if (newValue > 10) { stopWatch() } }) </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.6.2情况二 监视ref定义的对象类型数据:直接写数据名,监视的是对象的地址值,若想监视对象的内部数据,要手动开启深度监视
修改ref对象属性,new和old都是新值,因为是同一个对象
若修改整个ref定义的对象,new是新值,old是旧值,因为不是同一个对象
immediate为true开始立即监视
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 <template> <div class="person"> <!-- <h1>监视情况一:</h1> <h2>当前求和为:{{ sum }}</h2> <button @click="addSum">+1</button> --> <h1>监视情况二:</h1> <h2>姓名:{{ person.name }}</h2> <h2>年龄:{{ person.age }}</h2> <button @click="changeName">修改姓名</button> <button @click="changeAge">修改年龄</button> <button @click="changePerson">修改整个</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { ref,watch } from 'vue' // //数据 // let sum = ref(0) // //方法 // function addSum() { // sum.value++ // } // //监视情况一 // const stopWatch = watch(sum, (newValue, oldValue) => { // console.log(newValue, oldValue) // if (newValue > 10) { // stopWatch() // } // }) //数据 let person = ref({ name: '张三', age: 18 }) //方法 function changeName() { person.value.name += '~' } function changeAge() { person.value.age += 1 } function changePerson() { person.value = { name: '李四', age: 20 } } //监视情况二 监视对象的地址值 watch(person, (newValue, oldValue) => { console.log(newValue, oldValue) }) //监视情况二 监视对象内部属性变化 watch(person, (newValue, oldValue) => { console.log(newValue, oldValue) }, { deep: true ,immediate:true}) </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.6.3情况三 监视reactive定义的对象类型数据,且默认开启深度监视
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 99 100 101 102 103 104 105 106 107 108 <template> <div class="person"> <!-- <h1>监视情况一:</h1> <h2>当前求和为:{{ sum }}</h2> <button @click="addSum">+1</button> --> <!-- <h1>监视情况二:</h1> <h2>姓名:{{ person.name }}</h2> <h2>年龄:{{ person.age }}</h2> <button @click="changeName">修改姓名</button> <button @click="changeAge">修改年龄</button> <button @click="changePerson">修改整个</button> --> <h1>监视情况三:</h1> <h2>姓名:{{ person.name }}</h2> <h2>年龄:{{ person.age }}</h2> <button @click="changeName">修改姓名</button> <button @click="changeAge">修改年龄</button> <button @click="changePerson">修改整个</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { reactive,watch } from 'vue'; //import { ref,watch } from 'vue' // //数据 // let sum = ref(0) // //方法 // function addSum() { // sum.value++ // } // //监视情况一 // const stopWatch = watch(sum, (newValue, oldValue) => { // console.log(newValue, oldValue) // if (newValue > 10) { // stopWatch() // } // }) // //数据 // let person = ref({ // name: '张三', // age: 18 // }) // //方法 // function changeName() { // person.value.name += '~' // } // function changeAge() { // person.value.age += 1 // } // function changePerson() { // person.value = { // name: '李四', // age: 20 // } // } // //监视情况二 监视对象的地址值 // watch(person, (newValue, oldValue) => { // console.log(newValue, oldValue) // }) // //监视情况二 监视对象内部属性变化 // watch(person, (newValue, oldValue) => { // console.log(newValue, oldValue) // }, { deep: true ,immediate:true}) //数据 let person = reactive({ name: '张三', age: 18 }) //方法 function changeName() { person.name += '~' } function changeAge() { person.age += 1 } function changePerson() { Object.assign(person, { name: '李四', age: 20 }) } //监视情况三 watch(person, (newValue, oldValue) => { console.log(newValue, oldValue) }) </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.6.4情况四 监视ref或reactive定义的对象数据的某个属性,注意:
若该属性不是对象类型,需要写成函数形式
若该函数值是依然是对象类型,可直接编,也可以写成函数,不过建议写成函数
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 <template> <div class="person"> <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="changeCar1">修改车1</button> <button @click="changeCar2">修改车2</button> <button @click="changeCar">修改车</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { reactive,watch } from 'vue'; //数据 let person = reactive({ name: '张三', age: 18, car:{ c1: '宝马', c2: '奔驰' } }) //方法 function changeName() { person.name += '~' } function changeAge() { person.age += 1 } function changeCar1() { person.car.c1 = '奥迪' } function changeCar2() { person.car.c2 = '大众' } function changeCar(){ person.car = {c1: '保时捷', c2: '雅迪'} } //监视 watch(() => person.name, (newValue, oldValue) => { console.log('name', newValue, oldValue) }) //监视属性变化 watch(person.car, (newValue, oldValue) => { console.log('car', newValue, oldValue) }) //监视整个变化,加上deep:true即可全部监视 watch(()=>person.car, (newValue, oldValue) => { console.log('car', newValue, oldValue) }, {deep: true}) </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.6.5情况五 监视上述的多个数据
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 <template> <div class="person"> <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="changeCar1">修改车1</button> <button @click="changeCar2">修改车2</button> <button @click="changeCar">修改车</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { reactive,watch } from 'vue'; //数据 let person = reactive({ name: '张三', age: 18, car:{ c1: '宝马', c2: '奔驰' } }) //方法 function changeName() { person.name += '~' } function changeAge() { person.age += 1 } function changeCar1() { person.car.c1 = '奥迪' } function changeCar2() { person.car.c2 = '大众' } function changeCar(){ person.car = {c1: '保时捷', c2: '雅迪'} } // //监视 // watch(() => person.name, (newValue, oldValue) => { // console.log('name', newValue, oldValue) // }) // //监视属性变化 // watch(person.car, (newValue, oldValue) => { // console.log('car', newValue, oldValue) // }) // //监视整个变化,加上deep:true即可全部监视 // watch(()=>person.car, (newValue, oldValue) => { // console.log('car', newValue, oldValue) // }, {deep: true}) //监视多个数据 watch([() => person.name, () => person.car.c1], (newValue, oldValue) => { console.log('name', newValue, oldValue) }, {deep: true}) </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.7 watchEffect 立即运行一个函数,同时响应式跟踪其依赖,并在依赖更改时重新执行该函数
watch对比watchEffect
都能监听响应式数据的变化,不同的是监听数据变化的方式不同
watch:要明确指出监视的数据
watchEffect:不用明确指出监视的数据(函数用到哪些属性,那就监视哪些数据)
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 <template> <div class="person"> <h2>需求:当水温达到60度,或水位达到80cm时,给服务器发送请求</h2> <h2>当前水温为:{{ tmp }}℃</h2> <h2>当前水位为:{{ height }}cm</h2> <button @click="changeSum">水温+10</button> <button @click="changeHeight">水位+10</button> </div> </template> <script> export default { name: 'Person', } </script> <script setup> import { ref,watch, watchEffect } from 'vue' //数据 let tmp = ref(0) let height = ref(0) //方法 function changeSum() { tmp.value += 10 } function changeHeight() { height.value += 10 } //监视 // watch([tmp, height], (val) => { // let [newTmp, newHeight] = val // if (newTmp >= 60 || newHeight >= 80) { // console.log('发送请求') // } // }) //watchEffect watchEffect(() => { // ||导致后面失效 if (tmp.value >= 60 || height.value >= 80) { console.log('发送请求') } }) </script> <style> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.8 标签的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 28 29 30 31 32 33 34 35 36 37 <template> <div class="person"> <h1>中国</h1> <h2 ref="title2">武汉</h2> <button @click="showLog">输出h2元素</button> </div> </template> <script setup name="Person"> import { ref,defineExpose } from 'vue' //创建一个title2,用于存储ref标记的内容 let title2 = ref() const showLog = () => { console.log(title2.value) } //在组件使用ref,要使其暴露才能看见具体数据 let a = ref(1) let b = ref(2) defineExpose({ a, b }) </script> <style scoped> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.9 TS 4.2.9.1 基础 ts
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> <div class="person"> </div> </template> <script lang="ts" setup name="Person"> //使用ts必须使用lang="ts" import { type PersonInter,type Persons } from '@/types' //引用方法 let person:PersonInter = { //接口规范 id: 'asjdijasidja1', name: 'John Doe', age: 30 } let personlist:Persons = [ { id: 'asjdijasidja1', name: 'John Doe', age: 30 }, { id: 'asjdijasidja2', name: 'John Doe', age: 30 } ] </script> <style scoped> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
ts规定一个规范接口(types/index.ts)
1 2 3 4 5 6 7 8 9 export interface PersonInter { id : string , name : string , age : number } export type Persons = PersonInter []
4.2.9.2 props src/components/Person.vue
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 class="person"> <h2>{{ a }}</h2> <ul> <li v-for="item in list" :key="item.id">{{ item.name }}-{{ item.age }}</li> <!-- item为表示,list为需要循环的数据,需要用key为每个数据绑定自己的id加上:才能读取自己的id否则读取都为相同的id --> </ul> </div> </template> <script lang="ts" setup name="Person"> import { type Persons } from '@/types'; //只接收 //defineProps(['a', 'list']) //限制类型的接收 // defineProps<{list:Persons,a:string}>() //限制接收+限制必要性+指定默认值,?是限制必要性,withDefaults是指定默认值 withDefaults(defineProps<{list?:Persons,a?:string}>(),{ a:'默认值', list:()=>[{id:'1',name:'张三',age:18}] }) // 接收并保存 // let x = defineProps('a') // console.log(x) </script> <style scoped> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
src/App.vue
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 <template> <!-- a为字符串1+1 b为表达式2 c为字符串x d为表达式8 ref不需要: --> <!-- <h2 a="1+1" :b="1+1" c="x" :d="x" ref="qwe"></h2> --> <Person a="好好" :list="personList"/> </template> <script lang="ts" setup name="App"> import Person from '@/components/Person.vue' //引用组件 import { reactive } from 'vue'; import { type Persons } from '@/types' let x = 8 let personList = reactive<Persons>([ { id: '1', name: '张三', age: 18 }, { id: '2', name: '李四', age: 19 }, { id: '3', name: '王五', age: 20 } ]) </script>
4.2.10 生命周期 4.2.10.1 组件的生命周期 在不同的时间调用特定的函数
创建-挂载-更新-销毁
4.2.10.2 vue2的生命周期 4个阶段8个钩子hooks
创建(创建前beforeCreate,创建完毕created)
挂载(挂载前beforeMount,挂载完毕mounted)
更新(更新前beforeUpdate,更新完毕updated)
销毁(销毁前beforeDestroy,销毁完毕destroy)
src/App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <div> <Person v-if="ifShow"/> </div> </template> <script> import Person from "./components/Person.vue" export default { name: 'App', data() { return { ifShow: true } }, components: { Person } } </script>
src/components/Person.vue
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 <template> <div class="person"> <h2>当前求和为:{{sum}}</h2> <button @click="increment">+</button> </div> </template> <script> export default { // eslint-disable-next-line name: 'Person', data() { return { sum: 0 } }, methods: { increment() { this.sum++ } }, //创建前的钩子 beforeCreate() { console.log('beforeCreate()') }, //创建后的钩子 created() { console.log('created()') }, //挂载前的钩子 beforeMount() { console.log('beforeMount()') }, //挂载后的钩子 mounted() { console.log('mounted()') }, //更新前的钩子 beforeUpdate() { console.log('beforeUpdate()') }, //更新后的钩子 updated() { console.log('updated()') }, //销毁前的钩子 beforeDestroy() { console.log('beforeDestroy()') }, //销毁后的钩子 destroyed() { console.log('destroyed()') } } </script> <style scoped> .person { background-color: skyblue; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px; } </style>
4.2.10.3 vue3的生命周期 创建-挂载-更新-卸载
创建 setup(执行一次setup里的语句)
挂载 onBeforeMount onMounted
更新 onBeforeUpdate onUpdated
卸载 onBeforeUnmount onUnmounted
常用的钩子:挂载完毕、更新完毕、卸载之前
src/App.vue
1 2 3 4 5 6 7 8 9 10 <template> <Person v-if="isShow"/> </template> <script lang="ts" setup name="App"> import Person from '@/components/Person.vue' //引用组件 import { ref } from 'vue' let isShow = ref(true) </script>
src/components/Person.vue
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 <template> <div class="person"> <h2>当前求和为:{{ sum }}</h2> <button @click="add">+1</button> </div> </template> <script lang="ts" setup name="Person"> import { ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted } from 'vue' let sum = ref(0) const add = () => sum.value++ //创建 console.log('创建') //挂载 onBeforeMount(() => { console.log('挂载') }) //挂载完毕 onMounted(() => { console.log('挂载完毕') }) //更新 onBeforeUpdate(() => { console.log('更新') }) //更新完毕 onUpdated(() => { console.log('更新完毕') }) //卸载 onBeforeUnmount(() => { console.log('卸载') }) //卸载完毕 onUnmounted(() => { console.log('卸载完毕') }) </script> <style scoped> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } } </style>
4.2.10.4 自定义hooks src/components/Person.vue
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 <template> <div class="person"> <h2>当前求和为:{{ sum }}</h2> <button @click="add">+</button> <hr> <img v-for="(dog,index) in dogList" :src="dog" :key="index"> <button @click="getDog">再来一只</button> </div> </template> <script lang="ts" setup name="Person"> import useSum from '@/hooks/useSum' import useDog from '@/hooks/useDog' const { sum, add } = useSum() const { dogList, getDog } = useDog() </script> <style scoped> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 10px; padding: 20px; button { margin: 0 5px; } img { height: 100px; margin-right: 10px; } } </style>
src/hooks/useDog
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 { reactive,onMounted } from 'vue' import axios from 'axios' export default function() { //数据 let dogList = reactive([ 'https://images.dog.ceo/breeds/pembroke/n02113023_3885.jpg' ]) //方法 const getDog = async () => { try { let res = await axios.get('https://dog.ceo/api/breed/pembroke/images/random') dogList.push(res.data.message) } catch (err) { alert(err) } } //钩子 onMounted(async () => { await getDog() }) //向外部提供数据和方法 return {dogList, getDog} }
src/hooks/useSum
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { ref,onMounted } from 'vue' export default function () { //数据 let sum = ref(0) //方法 const add = () => { sum.value++ } //钩子 onMounted(() => { add() }) //向外部提供数据和方法 return {sum, add} }
4.2.11 路由 npm i vue-router
4.2.11.1 基本切换 src/App.vue
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 <template> <div class="app"> <h2 class="title">Vue路由测试</h2> <!-- 导航区 --> <div class="nav"> <RouterLink to="/home" active-class="ac">首页</RouterLink> <RouterLink to="/news" active-class="ac">新闻</RouterLink> <RouterLink to="/about" active-class="ac">关于</RouterLink> </div> <!-- 展示区 --> <div class="main-content"> <RouterView /> </div> </div> </template> <script lang="ts" setup name="App"> import { RouterView,RouterLink } from 'vue-router'; </script> <style scoped> .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; } .nav{ display: flex; justify-content: space-around; margin: 0 100px; } .nav 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; } .nav a.ac{ 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>
src/router/index.ts
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 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 } ] }) export default router
src/pages/About.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template> <div class="about"> <h2>鱼鱼鱼鱼</h2> </div> </template> <script lang="ts" setup 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>
src/pages/News.vue
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 class="news"> <ul> <li ref="#">新闻1</li> <li ref="#">新闻2</li> <li ref="#">新闻3</li> <li ref="#">新闻4</li> </ul> </div> </template> <script lang="ts" setup 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: 18px; 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>
src/pages/Home.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template> <div class="home"> <img src="http://www.atguigu.com/images/index_new/logo.png" alt="logo" /> </div> </template> <script lang="ts" setup name="Home"> </script> <style scoped> .home { display: flex; justify-content: center; align-items: center; height: 100%; } </style>
4.2.11.2 两个注意点
路由组件通常放在pages或views文件夹中,一般组件通常放在components文件夹。
通过点击导航,视觉效果上”消失”了的路由组件,默认是被卸载掉的,需要的时候再去挂载
路由组件:靠路由的规则渲染出来的
一般组件:亲手写标签出来的
4.2.11.3 路由的工作模式 history模式
优点:URL更美观,不带有#,更接近传统的网站URL
缺点:后期项目上线,需要服务器配合处理路径问题,否则刷新会有404错误
1 2 3 4 5 const router = createRouter({ // 指定路由的模式 history: createWebHistory(), //history模式 /******/ })
hash模式
优点:兼容性更好,因为不需要服务器端处理路径
缺点:URL带有#不大美观,且在SEO优化方面相对较差
1 2 3 4 5 const router = createRouter({ // 指定路由的模式 history: createWebHashHistory(), //hash模式 /******/ })
4.2.11.4 to的两种写法 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 <template> <div class="app"> <h2 class="title">Vue路由测试</h2> <!-- 导航区 --> <div class="nav"> <RouterLink to="/home" active-class="ac">首页</RouterLink> //字符串写法 <RouterLink :to="{path:'/news'}" active-class="ac">新闻</RouterLink> //对象写法 <RouterLink to="/about" active-class="ac">关于</RouterLink> </div> <!-- 展示区 --> <div class="main-content"> <RouterView /> </div> </div> </template> <script lang="ts" setup name="App"> import { RouterView,RouterLink } from 'vue-router'; </script> <style scoped> .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; } .nav{ display: flex; justify-content: space-around; margin: 0 100px; } .nav 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; } .nav a.ac{ 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>
4.2.11.5 命名路由 src/App.vue
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 <template> <div class="app"> <h2 class="title">Vue路由测试</h2> <!-- 导航区 --> <div class="nav"> <RouterLink to="/home" active-class="ac">首页</RouterLink> <RouterLink :to="{path:'/news'}" active-class="ac">新闻</RouterLink> <RouterLink :to="{name: 'guanyu'}" active-class="ac">关于</RouterLink> </div> <!-- 展示区 --> <div class="main-content"> <RouterView /> </div> </div> </template> <script lang="ts" setup name="App"> import { RouterView,RouterLink } from 'vue-router'; </script> <style scoped> .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; } .nav{ display: flex; justify-content: space-around; margin: 0 100px; } .nav 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; } .nav a.ac{ 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>
src/router/index.ts
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 import { createRouter,createWebHistory } from 'vue-router' import Home from '@/pages/Home.vue' import News from '@/pages/News.vue' import About from '@/pages/About.vue' const router = createRouter ({ history : createWebHistory (), routes : [ { name : 'zhuye' , path : '/home' , component :Home }, { name : 'xinwen' , path : '/news' , component :News }, { name : 'guanyu' , path : '/about' , component :About } ] }) export default router
4.2.11.6 嵌套路由 src/pages/detail.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <ul class="news-list"> <li>编号:</li> <li>标题:</li> <li>内容:</li> </ul> </template> <script setup lang="ts" name="Detail"> </script> <style scoped> .news-list { list-style: none; padding-left: 20px; } .news-list>li { line-height: 30px; } </style>
src/router/index.ts
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 import { createRouter,createWebHistory } from 'vue-router' import Home from '@/pages/Home.vue' import News from '@/pages/News.vue' import About from '@/pages/About.vue' import Detail from '@/pages/Detail.vue' const router = createRouter ({ history : createWebHistory (), routes : [ { name : 'zhuye' , path : '/home' , component :Home }, { name : 'xinwen' , path : '/news' , component :News , children :[ { path : 'detail' , component :Detail } ] }, { name : 'guanyu' , path : '/about' , component :About } ] }) export default router
4.2.11.7 路由参数
query参数
src/pages/detail.vue
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> <ul class="news-list"> <li>编号:{{ query.id }}</li> <li>标题:{{ query.title }}</li> <li>内容:{{ query.content }}</li> </ul> </template> <script setup lang="ts" name="Detail"> import { toRefs } from 'vue'; import { useRoute } from 'vue-router'; const route = useRoute(); const {query} = toRefs(route); </script> <style scoped> .news-list { list-style: none; padding-left: 20px; } .news-list>li { line-height: 30px; } </style>
src/pages/News.vue
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 <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 /> </div> </div> </template> <script lang="ts" setup name="News"> import { onMounted,onUnmounted,reactive } from 'vue' import { RouterView,RouterLink } from 'vue-router' onMounted(() => { console.log('News mounted') }) onUnmounted(() => { console.log('News unmounted') }) const newsList = reactive([ {id: 1, title: '新闻1', content: '新闻1的内容'}, {id: 2, title: '新闻2', content: '新闻2的内容'}, {id: 3, title: '新闻3', content: '新闻3的内容'}, {id: 4, title: '新闻4', content: '新闻4的内容'}, ]) </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: 18px; 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>
params参数
src/pages/detail.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <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="Detail"> import { useRoute } from 'vue-router'; const route = useRoute(); </script> <style scoped> .news-list { list-style: none; padding-left: 20px; } .news-list>li { line-height: 30px; } </style>
src/pages/News.vue
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 <template> <div class="news"> <!-- 导航区 --> <ul> <li v-for="news in newsList" :key=news.id> <!-- 第一种 --> <RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`">{{ news.title }}</RouterLink> <!-- 第二种 --> <RouterLink :to="{ name:'xiangxi', params:{ id: news.id, title: news.title, content: news.content } }" > {{ news.title }} </RouterLink> </li> </ul> <!-- 展示区 --> <div class="news-content"> <RouterView /> </div> </div> </template> <script lang="ts" setup name="News"> import { onMounted,onUnmounted,reactive } from 'vue' import { RouterView,RouterLink } from 'vue-router' onMounted(() => { console.log('News mounted') }) onUnmounted(() => { console.log('News unmounted') }) const newsList = reactive([ {id: 1, title: '新闻1', content: '新闻1的内容'}, {id: 2, title: '新闻2', content: '新闻2的内容'}, {id: 3, title: '新闻3', content: '新闻3的内容'}, {id: 4, title: '新闻4', content: '新闻4的内容'}, ]) </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: 18px; 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>
src/router/index.ts
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 import { createRouter,createWebHistory } from 'vue-router' import Home from '@/pages/Home.vue' import News from '@/pages/News.vue' import About from '@/pages/About.vue' import Detail from '@/pages/Detail.vue' const router = createRouter ({ history : createWebHistory (), routes : [ { name : 'zhuye' , path : '/home' , component :Home }, { name : 'xinwen' , path : '/news' , component :News , children :[ { name : 'xiangxi' , path : 'detail/:id/:title/:content?' , component :Detail } ] }, { name : 'guanyu' , path : '/about' , component :About } ] }) export default router
4.2.11.8 路由props配置 src/pages/detail.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <ul class="news-list"> <li>编号:{{ id }}</li> <li>标题:{{ title }}</li> <li>内容:{{ content }}</li> </ul> </template> <script setup lang="ts" name="Detail"> defineProps(['id', 'title', 'content']); </script> <style scoped> .news-list { list-style: none; padding-left: 20px; } .news-list>li { line-height: 30px; } </style>
src/router/index.ts
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 import { createRouter,createWebHistory } from 'vue-router' import Home from '@/pages/Home.vue' import News from '@/pages/News.vue' import About from '@/pages/About.vue' import Detail from '@/pages/Detail.vue' const router = createRouter ({ history : createWebHistory (), routes : [ { name : 'zhuye' , path : '/home' , component :Home }, { name : 'xinwen' , path : '/news' , component :News , children :[ { name : 'xiangxi' , path : 'detail/:id/:title/:content?' , component :Detail , props :true } ] }, { name : 'guanyu' , path : '/about' , component :About } ] }) export default router
4.2.11.9 路由replace属性 路由一般为push模式,即可以回退;replace后即为直接覆盖,不可回退
src/App.vue
加入replace即可
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 <template> <div class="app"> <h2 class="title">Vue路由测试</h2> <!-- 导航区 --> <div class="nav"> <RouterLink replace to="/home" active-class="ac">首页</RouterLink> <RouterLink replace :to="{path:'/news'}" active-class="ac">新闻</RouterLink> <RouterLink replace :to="{name: 'guanyu'}" active-class="ac">关于</RouterLink> </div> <!-- 展示区 --> <div class="main-content"> <RouterView /> </div> </div> </template> <script lang="ts" setup name="App"> import { RouterView,RouterLink } from 'vue-router'; </script> <style scoped> .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; } .nav{ display: flex; justify-content: space-around; margin: 0 100px; } .nav 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; } .nav a.ac{ 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>
4.2.11.10 编程式路由导航 脱离实现路由跳转
src/pages/News.vue
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 <template> <div class="news"> <!-- 导航区 --> <ul> <li v-for="news in newsList" :key=news.id> <button @click="showNewsDtail(news)">查看新闻</button> <RouterLink :to="{ name:'xiangxi', params:{ id: news.id, title: news.title, content: news.content } }" > {{ news.title }} </RouterLink> </li> </ul> <!-- 展示区 --> <div class="news-content"> <RouterView /> </div> </div> </template> <script lang="ts" setup name="News"> import { reactive } from 'vue' import { RouterView,RouterLink,useRouter } from 'vue-router' const newsList = reactive([ {id: 1, title: '新闻1', content: '新闻1的内容'}, {id: 2, title: '新闻2', content: '新闻2的内容'}, {id: 3, title: '新闻3', content: '新闻3的内容'}, {id: 4, title: '新闻4', content: '新闻4的内容'}, ]) const router = useRouter() interface NewsInter{ id: number, title: string, content: string } const showNewsDtail = (news:NewsInter) => { router.push({ //push replace两种方式 name:'xiangxi', params:{ id: news.id, title: news.title, content: news.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: 18px; 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>
场景:1.符合某些条件跳转 2.鼠标滑过一个东西跳转
4.2.11.11 路由重定向 src/router/index.ts
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 // 创建一个路由器,并暴露出去 // 引入 createRouter import { createRouter,createWebHistory } from 'vue-router' // 引入路由配置 import Home from '@/pages/Home.vue' import News from '@/pages/News.vue' import About from '@/pages/About.vue' import Detail from '@/pages/Detail.vue' // 创建一个路由器 const router = createRouter({ // 指定路由的模式 history: createWebHistory(), // 配置路由 routes: [ { name: 'zhuye', path: '/home', component:Home }, { name: 'xinwen', path: '/news', component:News, children:[ { name: 'xiangxi', path: 'detail/:id/:title/:content?', // ?表示content可传可不传 component:Detail, // 第一种写法,将路由收到的所有params参数作为props传递给路由组件 props:true // 第二种写法函数写法,可以自己决定将什么作为props给路由组件 // props(route){ // return route.query // } // 第三种写法对象写法,可以自己决定将什么作为props给路由组件 // props:{ // id:1, // title:'标题', // content:'内容' // } }, { path: '', redirect:'/news/detail/1/新闻1/新闻1的内容' } ] }, { name: 'guanyu', path: '/about', component:About }, { path: '/', redirect: '/home' } ] }) // 暴露出去router export default router
4.2.12 Pinia 生成id
npm i nanoid
npm i uuid
集中式状态(数据)管理
4.2.12.1 准备 src/components/Count.vue
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> <div class="count"> <h2>当前求和:{{ sum }}</h2> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="add">+</button> <button @click="minus">-</button> </div> </template> <script lang="ts" setup name="Count"> import { ref } from 'vue'; let sum = ref(1); //和 let n = ref(1); //用户选中数字 const add = () => { sum.value += n.value; }; const minus = () => { sum.value -= n.value; }; </script> <style scoped> .count { background-color: skyblue; padding: 10px; border-radius: 10px; box-shadow: 0 0 10px; } .selected { margin: 0 5px; height: 25px; } </style>
src/components/LoveTalk.vue
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 class="talk"> <button @click="getLoveTalk">获取土味情话</button> <ul> <li v-for="talk in talkList" :key="talk.id">{{ talk.content }}</li> </ul> </div> </template> <script lang="ts" setup name="LoveTalk"> import { reactive } from 'vue' import axios from 'axios' import { nanoid } from 'nanoid'; let talkList = reactive([ { id: '1', content: '“你最近是不是又胖了?” “没有啊,为什么这么说?” “那为什么在我心里的分量越来越重了?”' }, { id: '2', content: '我觉得你这个人不适合谈恋爱” “为什么?” “适合结婚。”' }, { id: '3', content: '最近有谣言说我喜欢你,我要澄清一下,那不是谣言。' } ]) const getLoveTalk = async() => { //发送请求 下面这行的写法是连续结构赋值加重命名 let {data:{content:content}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') //把请求回来的字符串,包装成一个对象 let obj = {id:nanoid(),content} //属性名相同可以不写:content //把这个对象添加到talkList中 talkList.unshift(obj) } </script> <style scoped> .talk { background-color: pink; padding: 10px; border-radius: 10px; box-shadow: 0 0 10px; } </style>
src/App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template> <div> <Count /> <hr> <LoveTalk/> </div> </template> <script lang="ts" setup name="App"> import Count from './components/Count.vue' import LoveTalk from './components/LoveTalk.vue'; </script> <style scoped> </style>
4.2.12.2 搭建pinia环境 npm i pinia
src/main.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { createApp } from "vue" ;import App from "./App.vue" ;import { createPinia } from "pinia" ;const app = createApp (App )const pinia = createPinia ()app.use (pinia) app.mount ("#app" )
4.2.12.3 存储+读取数据 reactive里的ref会自动拆包,不用.value
src/store/count.ts
1 2 3 4 5 6 7 8 9 10 import { defineStore } from "pinia" export const useCountStore = defineStore ('count' ,{ state ( ){ return { sum :1 } } })
src/store/loveTalk.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { defineStore } from "pinia" export const useTalkStore = defineStore ('talk' , { state ( ) { return { talkList : [ { id : '1' , content : '“你最近是不是又胖了?” “没有啊,为什么这么说?” “那为什么在我心里的分量越来越重了?”' }, { id : '2' , content : '我觉得你这个人不适合谈恋爱” “为什么?” “适合结婚。”' }, { id : '3' , content : '最近有谣言说我喜欢你,我要澄清一下,那不是谣言。' } ] } } })
4.2.12.4 修改数据的三种方式 src/count.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { defineStore } from "pinia" export const useCountStore = defineStore ('count' ,{ actions :{ increment (val :number ){ this .sum += val } }, state ( ){ return { sum :1 , school :'hbu' , address :'wuhan' } } })
src/components/Count.vue
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 <template> <div class="count"> <h2>当前求和:{{ countStore.sum }}</h2> <h3>欢迎来到:{{ countStore.school }},坐落于:{{ countStore.address }}</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="add">+</button> <button @click="minus">-</button> </div> </template> <script lang="ts" setup name="Count"> import { ref } from 'vue'; import { useCountStore} from '@/store/count' const countStore = useCountStore() //以下两种方法都可以拿到state数据 // countStore.sum // countStore.$state.sum let n = ref(1); //用户选中数字 const add = () => { // 第一种 //countStore.sum += n.value; // 第二种 // countStore.$patch((state) => { // state.sum += n.value, // state.school = '清华大学', // state.address = '北京' // }); // 第三种 countStore.increment(n.value) }; const minus = () => { countStore.sum -= n.value; }; </script> <style scoped> .count { background-color: skyblue; padding: 10px; border-radius: 10px; box-shadow: 0 0 10px; } .selected { margin: 0 5px; height: 25px; } </style>
4.2.12.5 storeToRefs src/components/Count.vue
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 <template> <div class="count"> <h2>当前求和:{{ sum }}</h2> <h3>欢迎来到:{{ school }},坐落于:{{ address }}</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="add">+</button> <button @click="minus">-</button> </div> </template> <script lang="ts" setup name="Count"> import { ref } from 'vue'; import { useCountStore} from '@/store/count' import { storeToRefs } from 'pinia'; const countStore = useCountStore() //storeToRefs只会关注数据,不会对方法进行ref const {sum, school, address}= storeToRefs(countStore) //以下两种方法都可以拿到state数据 // countStore.sum // countStore.$state.sum let n = ref(1); //用户选中数字 const add = () => { // 第一种 //countStore.sum += n.value; // 第二种 // countStore.$patch((state) => { // state.sum += n.value, // state.school = '清华大学', // state.address = '北京' // }); // 第三种 countStore.increment(n.value) }; const minus = () => { countStore.sum -= n.value; }; </script> <style scoped> .count { background-color: skyblue; padding: 10px; border-radius: 10px; box-shadow: 0 0 10px; } .selected { margin: 0 5px; height: 25px; } </style>
4.2.12.6 getters src/store/count.ts
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 import { defineStore } from "pinia" export const useCountStore = defineStore ('count' ,{ actions :{ increment (val :number ){ this .sum += val } }, state ( ){ return { sum :1 , school :'hbu' , address :'wuhan' } }, getters :{ bigSum :state => state.sum *10 , upperSchool ():string { return this .school .toUpperCase () } } })
src/components/Count.vue
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 <template> <div class="count"> <h2>当前求和:{{ sum }},放大十倍后:{{ bigSum }}</h2> <h3>欢迎来到:{{ school }},坐落于:{{ address }},大写:{{ upperSchool }}</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="add">+</button> <button @click="minus">-</button> </div> </template> <script lang="ts" setup name="Count"> import { ref } from 'vue'; import { useCountStore} from '@/store/count' import { storeToRefs } from 'pinia'; const countStore = useCountStore() //storeToRefs只会关注数据,不会对方法进行ref const {sum, school, address,bigSum,upperSchool}= storeToRefs(countStore) //以下两种方法都可以拿到state数据 // countStore.sum // countStore.$state.sum let n = ref(1); //用户选中数字 const add = () => { // 第一种 //countStore.sum += n.value; // 第二种 // countStore.$patch((state) => { // state.sum += n.value, // state.school = '清华大学', // state.address = '北京' // }); // 第三种 countStore.increment(n.value) }; const minus = () => { countStore.sum -= n.value; }; </script> <style scoped> .count { background-color: skyblue; padding: 10px; border-radius: 10px; box-shadow: 0 0 10px; } .selected { margin: 0 5px; height: 25px; } </style>
4.2.12.7 $subscribe src/store/loveTalk.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { defineStore } from "pinia" import axios from "axios" import { nanoid } from "nanoid" export const useTalkStore = defineStore ('talk' , { actions : { async getATalk ( ) { let { data : { content : content } } = await axios.get ('https://api.uomg.com/api/rand.qinghua?format=json' ) let obj = { id : nanoid (), content } this .talkList .unshift (obj) } }, state ( ) { return { talkList :JSON .parse (localStorage .getItem ('talkList' ) as string ) || [] } } })
src/components/LoveTalk.vue
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 <template> <div class="talk"> <button @click="getLoveTalk">获取土味情话</button> <ul> <li v-for="talk in talkList" :key="talk.id">{{ talk.content }}</li> </ul> </div> </template> <script lang="ts" setup name="LoveTalk"> import { useTalkStore } from '@/store/loveTalk' import { storeToRefs } from 'pinia'; const talkStore = useTalkStore() const { talkList } = storeToRefs(talkStore) talkStore.$subscribe((mutation, state) => { console.log(mutation, state) localStorage.setItem('talkList', JSON.stringify(state.talkList)) //将数据存储到浏览器本地 }) const getLoveTalk = () => { talkStore.getATalk() } </script> <style scoped> .talk { background-color: pink; padding: 10px; border-radius: 10px; box-shadow: 0 0 10px; } </style>
4.2.12.8 store组合式写法 src/store/loveTalk.ts
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 import { defineStore } from "pinia" import axios from "axios" import { nanoid } from "nanoid" import { reactive, ref } from 'vue' export const useTalkStore = defineStore ('talk' , () => { const talkList = reactive ( JSON .parse (localStorage .getItem ('talkList' ) as string ) || [] ) async function getATalk ( ) { let { data : { content : content } } = await axios.get ('https://api.uomg.com/api/rand.qinghua?format=json' ) let obj = { id : nanoid (), content } talkList.unshift (obj) } return { talkList, getATalk } })
4.2.13 组件通信 4.2.13.1 方式一props 可父传子也可子传父