工具 vue3 Vue3+Ts 定西 2024-02-06 2024-06-08 创建vue3项目 不适用vue-cli了,直接上vite
npm create vue@latest
然后选择一些配置项就行了,ts就ts呗
Vue3核心语法 setup setup
是vue3中一个新的配置项,值是一个函数,它是Composition Api
表演的舞台,组件中所用到的:数据,方法,计算属性,监视等等,均配置在setup中
setup中无法使用this,是undefined
想要只是用一个script,但又想自定义组件名,可以使用这个插件
1 npm i vite-plugin-vue-setup-extend -D
然后在vite.config.ts
中引入
1 2 3 4 import VueSetupExtend from 'vite-plugin-vue-setup-extend' plugins : [ VueSetupExtend (), ],
setup基本使用
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 <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 lang ="ts" setup name ="Person1" > let name = '张三' let age = 18 let tel = '12321312' function changeName ( ) { name = 'zhangsan' } function changeAge ( ) { age += 1 } function showTel ( ) { alert (tel) } </script > <style scoped > .person { background-color : blue; box-shadow : 0 0 10px ; padding : 20px ; } button { margin : 0 5px ; } </style >
ref 使用ref创建基本类型的响应式数据
上面写的数据都不是响应式的,更改了数据,界面也不会变化,使用ref可以创建响应式的数据
使用时,要注意:
插值语法中不写.value
函数中使用必须写.value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { ref } from 'vue' let name = ref ('张三' )let age = ref (18 )function changeName ( ) { name.value = 'zhangsan' } function changeAge ( ) { age.value += 1 }
ref定义引用类型的数据
实际上ref中的value还是Proxy,也就是说还是ractive
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 <template > <div class ="person" > <h2 > 一辆{{ car.brand }}值{{ car.price }}</h2 > <button @click ="changePrice" > 修改汽车价格</button > <hr > <h2 > 游戏列表</h2 > <ul > <li v-for ="g in games" :key ="g.id" > {{ g.name }}</li > </ul > <button @click ="changeFirstGame" > 修改第一个游戏</button > <hr > <h2 > {{ obj.a }}</h2 > </div > </template > <script lang ="ts" setup name ="Person" > import { ref,reactive } from 'vue' let car = ref ({ brand : '奔驰' , price : 100 }) let games = ref ([ { id : 'dasfs1' , name : '元神' }, { id : 'dasfs2' , name : '源神' }, { id : 'dasfs3' , name : '猿神' } ]) let obj = reactive ({ a : 999 }) console .log (car) console .log (obj) function changePrice ( ) { car.value .price += 10 console .log (car.value .price ) } function changeFirstGame ( ) { games.value [0 ].name = '原神' console .log (games) } </script > <style scoped > .person { background-color : blue; box-shadow : 0 0 10px ; padding : 20px ; } button { margin : 0 5px ; } </style >
reactive 使用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 49 50 51 <template > <div class ="person" > <h2 > 一辆{{ car.brand }}值{{ car.price }}</h2 > <button @click ="changePrice" > 修改汽车价格</button > <hr > <h2 > 游戏列表</h2 > <ul > <li v-for ="g in games" :key ="g.id" > {{ g.name }}</li > </ul > <button @click ="changeFirstGame" > 修改第一个游戏</button > </div > </template > <script lang ="ts" setup name ="Person" > import { reactive } from 'vue' let car = reactive ({ brand : '奔驰' , price : 100 }) let games = reactive ([ { id : 'dasfs1' , name : '元神' }, { id : 'dasfs2' , name : '源神' }, { id : 'dasfs3' , name : '猿神' } ]) function changePrice ( ) { car.price += 10 console .log (car.price ) } function changeFirstGame ( ) { games[0 ].name = '原神' console .log (games) } </script > <style scoped > .person { background-color : blue; box-shadow : 0 0 10px ; padding : 20px ; } button { margin : 0 5px ; } </style >
ref对比reactive
ref用来定义:基本数据类型,引用数据类型
reactive用来定义:引用数据类型
区别
ref创建的变量必须使用.value
(可以使用volar创建自动添加.value)
reactive重新分配一个新的对象,会失去 响应式(可以使用Object.assign
去整体替换)
使用原则
若需要一个基本类型的响应式数据,必须使用ref
若需要一个响应式对象,层级不深,ref,reactive都可以
若需要一个响应式对象,且层级较深,推荐使用reactive
**注意Object.assign(car,{ brand: ‘bwm’, price: 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 47 48 49 50 51 52 53 54 <template > <div class ="person" > <h2 > 一辆{{ car.brand }}值{{ car.price }}</h2 > <button @click ="changePrice" > 修改汽车价格</button > <button @click ="changeCar" > 修改汽车</button > <hr > <h2 > 当前求和为:{{ sum }}</h2 > <button @click ="changeSum" > 点我sum+1</button > </div > </template > <script lang ="ts" setup name ="Person" > import { ref, reactive } from 'vue' let car = reactive ({ brand : '奔驰' , price : 100 }) let sum = ref (0 )console .log (car) function changeCar ( ) { Object .assign (car,{ brand : 'bwm' , price : 1 }) } function changePrice ( ) { car.price += 10 console .log (car.price ) } function changeSum ( ) { sum.value += 1 } </script > <style scoped > .person { background-color : blue; box-shadow : 0 0 10px ; padding : 20px ; } button { margin : 0 5px ; } </style >
toRefs 将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 <template > <div class ="person" > <h2 > 姓名{{ person.name }}</h2 > <h2 > 年龄{{ age }}</h2 > <button @click ="changeName" > 修改姓名</button > <button @click ="changeAge" > 修改年龄</button > </div > </template > <script lang ="ts" setup name ="Person" > import { ref, reactive, toRefs } from 'vue' let person = reactive ({ name : '张三' , age : 18 }) let { name, age } = toRefs (person)console .log (name, age)function changeAge ( ) { age.value += 1 } function changeName ( ) { name.value += '~' } </script > <style scoped > .person { background-color : blue; box-shadow : 0 0 10px ; padding : 20px ; } button { margin : 0 5px ; } </style >
计算属性 写法还是有两种,一种是箭头函数,但是相当于只读,只有get,写上set,就可以读写了
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 <template > <div class ="person" > 姓: <input type ="text" v-model ="firstName" > <br > 名: <input type ="text" v-model ="lastName" > <br > 全名: <span > {{ fullName }}</span > <button @click ="changeFullName" > 将全民改为li-si</button > </div > </template > <script lang ="ts" setup name ="Person" > import { ref, computed } from 'vue' let firstName = ref ('张' )let lastName = ref ('三' )let fullName = computed ({ get ( ) { return firstName.value .slice (0 , 1 ).toUpperCase () + firstName.value .slice (1 ) + '-' + lastName.value }, set (val ) { const [str1,str2] = val.split ('-' ) firstName.value = str1 lastName.value = str2 console .log ('set' , val) } }) function changeFullName ( ) { fullName.value = 'li-si' } </script > <style scoped > .person { background-color : skyblue; box-shadow : 0 0 10px ; padding : 20px ; } button { margin : 0 5px ; } </style >
watch vue3中的watch只能监视以下四种数据
ref定义的数据
reactive定义的数据
函数返回一个值
一个包含上述内容的数组
情况一 监视ref定义的基本类型数据:直接写数据名即可,监视的是其value值的改变
1 2 3 4 watch (sum, (newValue, oldValue ) => { console .log ("sum变化了" ) console .log (newValue, oldValue) })
停止监视:
1 2 3 4 5 6 7 8 const stopWatch = watch (sum, (newValue, oldValue ) => { console .log ("sum变化了" ) console .log (newValue, oldValue) if (newValue >= 10 ) { stopWatch () } })
情况二 监视ref定义的对象类型的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 watch (person, (newValue, oldValue ) => { console .log ('person变化了' ) console .log (newValue, oldValue) }, { deep : true })
情况三 监视reactive定义的对象类型的数据,且默认开启了深度监视
该深度监视无法关闭
1 2 3 4 5 watch (person, (newValue ) => { console .log ('person变化了' ) console .log (newValue) })
情况四 监视ref或reactive定义的对象类型数据的某个属性,注意点如下
若该属性不是对象类型,需要写成函数形式
若该属性值是对象类型,可直接编,也可写成函数式,不过建议写成函数
第一种:
1 2 3 4 watch (() => { return person.name }, (newValue ) => { console .log ("person.name变化了" ) })
箭头函数如果只有return语句,那么花括号和return可以省略
1 2 3 watch (() => person.name , (newValue ) => { console .log ("person.name变化了" ) })
第二种: 对象属性仍然是对象,那么可以直接写,也可以写成函数式
1 2 3 4 5 6 7 8 9 10 watch (person.car , (newValue ) => { console .log ("person.car变化了" ) }) watch (() => person.car , (newValue ) => { console .log ("person.car变化了" ) },{deep :true })
结论就是,如果监视的是对象的对象属性,那么最好写成函数式,需要深度监视就写上deep
情况五 监视上述多个数据
就是直接拿数组包起来,然后看是写箭头函数还是写普通属性
1 2 3 watch ([() => person.name , () => person.car .c1 ], () => { console .log ("person.name或person.car.c1变化了" ) })
总结 实际用到时情况一和情况四较常用,也就是基本类型和监视对象
watchEffect 当监视多个属性时,使用watch要写多个,是比较麻烦的
1 2 3 4 5 6 7 watch ([temp, height], (value ) => { let [newTemp, newHeight] = value if (newTemp >= 60 || newHeight >= 80 ) { console .log ("发送请求" ) } })
使用watchEffect
相当于监听函数中用到的属性,变化时就会执行整个函数
1 2 3 4 5 watchEffect (() => { if (temp.value >= 60 || height.value >= 80 ) { console .log ("send" ) } })
标签的ref属性 可以使用在普通html标签上,或者dom元素,也可以使用在组件上,来获取组件实例,但是默认是拿不到组件内的实例数据的,可以在组件的最后使用defineExpose
来暴露数据
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 <template > <div class ="person" > <h1 > 中国</h1 > <h2 ref ="title2" > beijing</h2 > <button @click ="showLog" > 点我输出h2元素</button > </div > </template > <script lang ="ts" setup name ="Person" > import { ref } from 'vue' let title2 = ref ()let a = ref (2 )function showLog ( ) { console .log (title2.value ) } defineExpose ({ a })</script > <style scoped > .person { background-color : skyblue; box-shadow : 0 0 10px ; padding : 20px ; } button { margin : 0 5px ; } </style >
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template > <Person ref ="ren" > </Person > <button @click ="showLog" > 测试</button > </template > <script lang ="ts" setup name ="App" > import Person from './components/Person.vue' import { ref } from 'vue' let ren = ref ()function showLog ( ) { console .log (ren.value .a ) } </script > <style > </style >
TS中的接口,自定义类型,泛型 注意写法
ts文件:
1 2 3 4 5 6 7 8 9 10 11 12 export interface PersonInter { id : string , name : string , age : number , x?: number } export type Persons = Array <PersonInter >
使用时(导入时,如果ts的文件名是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 <template > <div class ="person" > ??? </div > </template > <script lang ="ts" setup name ="Person" > import { type PersonInter , type Persons } from '@/types' let person : PersonInter = { id : '312231123' , name : '张三' , age : 60 }let personList1 : Array <PersonInter > = [ { id : '312231131223' , name : '张三' , age : 60 }, { id : '31223112123' , name : '张1' , age : 61 }, { id : '312231213123' , name : '张2' , age : 62 }, { id : '312231113223' , name : '张3' , age : 63 } ] let personList2 : Persons = [ { id : '312231131223' , name : '张三' , age : 60 }, { id : '31223123' , name : '张1' , age : 61 }, { id : '312231213123' , name : '张2' , age : 62 }, { id : '312231113223' , name : '张3' , age : 63 } ] console .log (personList1)console .log (personList2)</script > <style scoped > .person { background-color : skyblue; box-shadow : 0 0 10px ; padding : 20px ; } button { margin : 0 5px ; } </style >
涉及到响应式的数据,有别的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { type Persons } from '@/types' let personList : Persons = reactive ([ { id : '1' , name : '张三' , age : 18 }, { id : '2' , name : '里斯' , age : 19 }, { id : '3' , name : '王五' , age : 20 } ]) let personList = reactive<Persons >([ { id : '1' , name : '张三' , age : 18 }, { id : '2' , name : '里斯' , age : 19 }, { id : '3' , name : '王五' , age : 20 } ])
props props的几种写法 父组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template > <Person :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 personList = reactive<Persons >([ { id : '1' , name : '张三' , age : 18 }, { id : '2' , name : '里斯' , age : 19 }, { id : '3' , name : '王五' , age : 20 } ]) console .log (personList)</script > <style > </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 <template > <div class ="person" > <ul > <li v-for ="l in list" :key ="l.id" > 姓名:{{ l.name }},年龄:{{ l.age }},</li > </ul > </div > </template > <script lang ="ts" setup name ="Person" > import { type Persons } from '@/types' import { defineProps, withDefaults } from 'vue' withDefaults (defineProps<{ list?: Persons }>(), { list : () => [{ id : '1' , name : '涨啊' , age : 1 }] }) </script > <style scoped > .person { background-color : skyblue; box-shadow : 0 0 10px ; padding : 20px ; } button { margin : 0 5px ; } </style >
生命周期 分为4种,创建,挂载,更新,卸载,而创建,就是setup
父子组件的挂载顺序: 子先挂载,父后挂载
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 <template > <div class ="person" > <h2 > 当前求和为:{{ sum }}</h2 > <button @click ="add" > 点击加一</button > </div > </template > <script lang ="ts" setup name ="Person" > import { onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, ref } from 'vue' let sum = ref (0 )function add ( ) { sum.value += 1 } 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 ; padding : 20px ; } button { margin : 0 5px ; } </style >
常用的钩子
挂载完毕
更新完毕
卸载之前
hooks 所以你明白了vue3的组合式api有什么用了吧,把一个功能的数据和方法甚至钩子都能写到一个ts文件中
注意hooks的命名,以及hooks里面要写上一个函数,并返回数据和函数之类的东西
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" > <h2 > 当前求和为:{{ sum }}</h2 > <h2 > 放大:{{ bigSum }}</h2 > <button @click ="add" > 点我sum+1</button > <hr > <img v-for ="dog in dogList" :src ="dog" > <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, bigSum } = useSum ()const { dogList, getDog } = useDog ()</script > <style scoped > img { height : 100px ; margin-right : 10px ; } .person { background-color : skyblue; box-shadow : 0 0 10px ; padding : 20px ; } button { margin : 0 5px ; } </style >
两个use
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { computed, onMounted, ref } from 'vue' export default function ( ) { let sum = ref (0 ) let bigSum = computed (() => { return sum.value * 10 }) onMounted (() => { add () }) function add ( ) { sum.value += 1 } return { sum, add, bigSum } }
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 { onMounted, reactive } from 'vue' import axios from 'axios' export default function ( ) { let dogList = reactive ([ 'https://images.dog.ceo/breeds/pembroke/n02113023_6140.jpg' ]) async function getDog ( ) { try { let result = await axios.get ('https://dog.ceo/api/breed/pembroke/images/random' ) dogList.push (result.data .message ) } catch (error) { alert (error) } } onMounted (() => { getDog () }) return { dogList, getDog } }
路由 基本使用 路由的配置文件
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 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
然后去main.ts中要引入
1 2 3 import router from './router' createApp (App ).use (router).mount ('#app' )
使用RouterView和RouterLink
来实现路由跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template > <div class ="app" > <h2 > vue 路由测试</h2 > <div class ="navigate" > <RouterLink active-class ="active" to ="/home" > 首页</RouterLink > <RouterLink active-class ="active" to ="/news" > 新闻</RouterLink > <RouterLink active-class ="active" to ="/about" > 关于</RouterLink > </div > <div class ="main-content" > <RouterView > </RouterView > </div > </div > </template > <script lang ="ts" setup name ="App" > import { RouterView ,RouterLink } from "vue-router" ;</script >
两个注意点
路由组件通常存放到pages或views文件夹下,一般组件通常存放在components文件夹
通过点击导航,视觉效果上消失了的路由组件,默认是被卸载的,需要的时候再去挂载
to的两种写法 一种直接写路径,一种给to加上冒号,然后写对象
1 2 3 4 5 <div class ="navigate" > <RouterLink active-class ="active" to ="/home" > 首页</RouterLink > <RouterLink active-class ="active" to ="/news" > 新闻</RouterLink > <RouterLink active-class ="active" :to ="{ path: '/about' }" > 关于</RouterLink > </div >
命名路由 其实就是给路由的配置上起名字,然后跳转时可以使用对象形式指定路由名
1 2 3 4 5 { name : 'zhuye' , path : '/home' , component : Home },
1 2 3 4 5 <div class ="navigate" > <RouterLink active-class ="active" to ="/home" > 首页</RouterLink > <RouterLink active-class ="active" :to ="{ name: 'xinwen' }" > 新闻</RouterLink > <RouterLink active-class ="active" :to ="{ path: '/about' }" > 关于</RouterLink > </div >
路由嵌套 这个很久之前用到过,后来写课设好像就再没用到过了哈哈啊哈操,注意子路由不写斜杠
1 2 3 4 5 6 7 8 9 10 11 12 { name : 'xinwen' , path : '/news' , component : News , children : [ { path : 'detail' , component : Detail } ] },
路由传参 query参数 两种写法,第一种拼接,太麻烦了,如果传参比较多,还是用对象形式比较好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <li v-for ="news in newsList" :key ="news.id" > <RouterLink :to ="{ path: '/news/detail', query: { id: news.id, title: news.title, content: news.content, } }" > {{ news.title }} </RouterLink > </li >
接收时,要使用useRoute
1 2 3 4 5 6 import { toRefs } from "vue" ;import { useRoute } from "vue-router" ;const route = useRoute ()let { query } = toRefs (route)
然后就可以使用query来获取数据了
1 2 3 4 5 <ul > <li > 编号: {{ query.id }}</li > <li > 标题: {{ query.title }}</li > <li > 内容: {{ query.content }}</li > </ul >
params 这个东西说实话没有query好用,我还以为是请求体那种呢
用的时候要把参数拼接到路由配置文件中
注意事项:
如果想要传递params参数,只能使用name
path中拼接参数
无法传递对象和数组,只能传递基本类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { name : 'xinwen' , path : '/news' , component : News , children : [ { name : 'xiangqing' , path : 'detail/:id/:title/:content' , component : Detail } ] },
传递时依然是两种写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <li v-for ="news in newsList " :key ="news.id" > <RouterLink :to ="{ // 如果想要传递params参数,只能使用name // 且params不能传递对象和数组,只能传递基本类型 name: 'xiangqing', params: { id: news.id, title: news.title, content: news.content } }" > {{ news.title }}</RouterLink > </li >
接收和query类似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template > <ul > <li > 编号: {{ params.id }}</li > <li > 标题: {{ params.title }}</li > <li > 内容: {{ params.content }}</li > </ul > </template > <script lang ="ts" setup name ="Detail" > import { toRefs } from "vue" ;import { useRoute } from "vue-router" ;const route = useRoute ()let {params} = toRefs (route)</script > <style > </style >
路由props 现在的问题就是,接收的地方太麻烦了,props可以解决这个问题
可以实现直接使用,不显示接受 ,这个就相当于,在路由时,给路由组件传递了props
简单的使用
在路由的配置上加上props: true
路由组件接收propsdefineProps(['id', 'title', 'content'])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { name : 'xinwen' , path : '/news' , component : News , children : [ { name : 'xiangqing' , path : 'detail/:id/:title/:content' , component : Detail , props : true } ] },
但是这种写法,只能和params配合,并不能使用query参数
第二种写法:函数式写法,可自己决定将什么作为props给路由组件,主要用于query传递
注意props函数接收和返回的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { name : 'xinwen' , path : '/news' , component : News , children : [ { name : 'xiangqing' , path : 'detail' , component : Detail , props (route ) { return route.query } } ] },
接收时仍一样
还有一种写法,就是直接用props,相当于在路由组件上写props,适合自定义数据,不依赖query和params
1 2 3 4 props :{ a :100 , b :200 }
replace 路由跳转默认是push,也就是带有历史记录的跳转
而replace,会清除之前的记录
1 <RouterLink replace active-class ="active" to ="/home" > 首页</RouterLink >
编程式路由导航 还是这个好用,甚至之前开发都几乎没用过标签来导航
用起来区别仅在于,不能使用this.router.push
了,而要引入useRouter
,然后获取到router来使用
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 > <ul > <li v-for ="news in newsList " :key ="news.id" > <button @click ="showNewsDetail(news)" > 查看新闻</button > <RouterLink :to ="{ // 如果想要传递params参数,只能使用name // 且params不能传递对象和数组,只能传递基本类型 name: 'xiangqing', query: { id: news.id, title: news.title, content: news.content } }" > {{ news.title }}</RouterLink > </li > </ul > <div class ="news-content" > <RouterView > </RouterView > </div > </template > <script lang ="ts" setup name ="News" > import { reactive } from "vue" import { RouterView , RouterLink , useRouter } from "vue-router" const newsList = reactive ([ { id : '001' , title : '十种食物' , content : '你猜是什么' }, { id : '002' , title : '如何一夜暴富' , content : '一步登天' }, { id : '003' , title : '好消息' , content : '快过年了' } ]) interface NewsInter { id : string, title : string, content : string } const router = useRouter ()function showNewsDetail (news: NewsInter ) { router.push ({ path : '/news/detail' , query : { id : news.id , title : news.title , content : news.content } }) } </script > <style > </style >
重定向 这玩意没啥说的,不过我一般习惯直接将根目录设置为某个组件
1 2 3 4 { path : '/' , redirect : '/home' },
pinia 代替vue2中的vuex(vuex我都忘了怎么用了)
再说一下解构赋值 1 2 3 4 5 6 7 8 9 10 11 async function getLoveTalk ( ) { let { data : { content : title } } = await axios.get ('https://api.uomg.com/api/rand.qinghua?format=json' ) let obj = { id : nanoid (), title } talkList.unshift (obj) }
let { data: { content: title }
就是解构出result中的data,再从data中解构出content,并命名为title
下面的obj就可以直接写title了(title:title替换成title)
搭建pinia环境 npm i pinia
就安装好了
然后到main.ts中引入配置
1 2 3 4 5 6 7 8 9 import { createPinia } from 'pinia' const pinia = createPinia ()createApp (App ).use (pinia).mount ('#app' )
使用pinia
首先在src目录下创建store目录
然后创建组件名小驼峰的ts文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { defineStore } from "pinia" export const useTalkLoveStore = defineStore ('talkLove' , { state ( ) { return { talkList : [ { id : '001' , title : '哈哈哈' }, { id : '001' , title : '嘿嘿嘿' }, { id : '001' , title : '呵呵呵' }, ] } }, })
引入useTalkLoveStore
1 2 3 import { useTalkLoveStore } from '@/store/talkLove' const talkLoveStore = useTalkLoveStore ()
此时,这个talkLoveStore中就可以直接取到里面的数据了:talkLoveStore.talkList
修改数据的方法
就是直接修改
使用$patch
,适用于批量修改1 2 3 4 5 countStore.$patch({ address : '汉中' , nickName : '灼灼某人' })
第三种,使用action(注意使用this来获取state中的数据)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 (n: any ) { this .sum += n } }, state ( ) { return { sum : 1 , address : 'hanzhong' , nickName : 'zzmr' } }, })
然后在组件中直接使用useCountStore
来调用increment即可
但是现在每次读取数据时,都要使用store.xx
,不够优雅
1 2 3 <h2 > 当前求和为:{{ countStore.sum }}</h2 > <h3 > 地址: {{ countStore.address }}</h3 > <h3 > 昵称: {{ countStore.nickName }}</h3 >
所以可以使用解构赋值,但是直接解构会导致数据丢失响应式,但如果加上toRefs()
,那又太多余了,因为只有store中的数据需要进行响应式,所以可以使用storeToRefs
来只使store中的数据变成响应式.
1 2 3 4 5 6 import { storeToRefs } from 'pinia' ;import { useCountStore } from '@/store/count' const countStore = useCountStore ()let { sum, address, nickName } = storeToRefs (countStore)
getters 对store中的数据进行处理后再返回
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" export const useCountStore = defineStore ('count' , { actions : { increment (n: number ) { this .sum += n } }, state ( ) { return { sum : 1 , address : 'hanzhong' , nickName : 'zzmr' } }, getters : { bigSum (state ) { return state.sum * 10 }, upperNickName (state ) { return this .nickName .toUpperCase () } } })
其实就是加上getters配置,然后写函数,在使用时,可以直接将函数名当函数的返回值使用
1 2 3 <h3 > bigSum:{{ bigSum }}</h3 > <h3 > upperNickName:{{ upperNickName }}</h3 > let { sum, address, nickName, bigSum, upperNickName } = storeToRefs(countStore)
订阅 其实就是监视store中的数据变化
1 2 3 4 talkLoveStore.$subscribe((mutate,state )=> { console .log ('数据发生变化' ) console .log (mutate,'@' ,state) })
mutate是修改的操作,state中能取到store中的数据
store组合式写法 就是,就是去掉配置项,数据就直接定义,函数就可以直接使用变量
组件间通信 好像之前写的时候,基本就是本地存储+事件总线,而且后面写项目事件总线都用的少了哈哈哈操
props 父与子,子与父传递数据
父给子传,就直接:xxx="xxx"
这样就能把父亲中的car传递给子组件,而子组件中需要接收:
这样子组件就能拿到父组件给的car了
子给父传,需要父提供一个方法,并将这个方法像参数一样传递给子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template > <div class ="father" > <h3 > 父组件</h3 > <h4 > 汽车:{{ car }}</h4 > <h4 v-show ="toy" > 子给的玩具:{{ toy }}</h4 > <Child :car ="car" :sendToy ="getToy" /> </div > </template > <script setup lang ="ts" name ="Father" > import Child from './Child.vue' import {ref} from 'vue' let car = ref ('奔驰' ) let toy = ref ('' ) function getToy (value:string ){ toy.value = value } </script >
子组件中,接收方法后,直接调用该方法及传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template > <div class ="child" > <h3 > 子组件</h3 > <h4 > 玩具:{{ toy }}</h4 > <h4 > 父给的车:{{ car }}</h4 > <button @click ="sendToy(toy)" > 把玩具给父亲</button > </div > </template > <script setup lang ="ts" name ="Child" > import {ref} from 'vue' let toy = ref ('奥特曼' ) defineProps (['car' ,'sendToy' ]) </script >
这里是直接在template中调用了sendToy
并传递了数据,如果要在script中写,就要接收到defineProps
返回的数据来调用了
自定义事件 首先说一下就是一个点击事件,如果没有传递参数,是可以接受到事件的dom对象的,但是如果传递了参数,那就不能接收到dom元素了,此时可以使用$event
来传递dom元素
1 <button @click ="test(1, $event)" > 点我</button >
自定义事件,常用于子传父 父组件如下,就是在父组件中给子组件标识一个@sendToy="getToy"
,因为是@开头,所以是事件的意思,@什么,子组件就可以使用什么方法,=的方法就是这个事件的回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <div class ="father" > <h3 > 父组件</h3 > <h4 > {{ str }}</h4 > <Child @sendToy ="getToy" /> </div > </template > <script setup lang ="ts" name ="Father" > import Child from './Child.vue' import { ref } from 'vue' let str = ref ('你好' )function getToy (toy: string ) { str.value = toy } </script >
子组件如下,要使用defineEmits(['sendToy'])
来接收,然后使用emit('sendToy',参数)
来触发自定义事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template > <div class ="child" > <h2 > 子组件</h2 > <h3 > 玩具:{{ toy }}</h3 > <button @click ="emit('sendToy',toy)" > 发送玩具</button > </div > </template > <script setup lang ="ts" name ="Child" > import { ref } from "vue" ;let toy = ref ('奥特曼' )const emit = defineEmits (['sendToy' ])</script >
mitt 把这玩意当成vue2的事件总线
安装:npm i mitt
创建一个工具类?名叫emitter.ts
1 2 3 4 5 6 7 import mitt from "mitt" const emitter = mitt ()export default emitter
emitter有以下的函数:
方法使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 emitter.on ('test1' , () => { console .log ('test1被调用了' ) }) emitter.on ('test2' , () => { console .log ('test2被调用了' ) }) setInterval (() => { emitter.emit ('test1' ) emitter.emit ('test2' ) }, 2000 ) setTimeout (() => { emitter.off ('test1' ) }, 5000 )
组件间通信实现 还是这个简单还用,记得使用前导入依赖:import emitter from '@/utils/emitter';
1 2 3 4 5 6 7 8 let toy = ref ('奥特曼' )function sendToy ( ) { emitter.emit ('toy' , toy.value ) }
孩子二接收,在卸载组件时解绑事件
1 2 3 4 5 6 7 8 9 10 11 let toy = ref ('' )emitter.on ('toy' , (value: any ) => { toy.value = value }) onUnmounted (()=> { emitter.off ('toy' ) })
v-model 一般不会自己写组件然后用这个来传递数据,不过ui组件库大量使用的就是这个
这玩意还挺神奇,之前用el-input
时可以直接使用v-model
来绑定数据,但是我们自己的组件并不能实现,底层是这么写的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template > <div class ="father" > <h3 > 父组件</h3 > <ZzmrInput v-model ="username" /> </div > </template > <script setup lang ="ts" name ="Father" > import { ref } from "vue" import ZzmrInput from './ZzmrInput.vue' let username = ref ('zhangsan' )</script >
ZzmrInput.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 <template > <input type ="text" :value ="modelValue" @input ="emit('update:modelValue', (<HTMLInputElement>$event.target).value)" > </template > <script lang ="ts" setup name ="ZzmrInput" > defineProps (['modelValue' ])const emit = defineEmits (['update:modelValue' ])</script > <style > </style >
这里多次用到了$event
对于原生事件:$event
就是事件对象,能.target
对于自定义事件:$event
就是触发事件时,所传递的数据,不能.target
$attrs
$attrs
用于实现当前组件的父组件,向当前组件的子组件通信 :祖->孙
具体说明:$attrs
是一个对象,包含所有父组件传入的标签属性
父传给子数据,子不接收,这个数据就存到了$attrs
中,子再将$attrs
用v-bind="$attrs"
传给孙组件,就实现了祖->孙
到时候我肯定不用,说不定还是mitt梭哈
$refs,$parent
$refs
用于父传子$parent
用于子传父
到时候我肯定不用,说不定还是mitt梭哈 +1
provide_inject 这玩意也是用于祖孙之间传递的,与$attrs
不同在于,这个不需要打扰中间人,可以直接实现祖孙传递
插槽 之前好像也不怎么用这个玩意
默认插槽 总结就是,将组件标签写成双标签,然后里面写上内容,这就是插槽的内容,然后到组件中,写上slot
标签,就是展示这些内容的位置
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 class ="father" > <div class ="content" > <Game title ="热门游戏" > <ul > <li v-for ="item in games" :key ="item.id" > {{ item.name }}</li > </ul > </Game > <Game title ="今日美食" > <img :src ="imageUrl" > </Game > <Game title ="影视推荐" > <video :src ="videoUrl" controls > </video > </Game > </div > </div > </template > <script setup lang ="ts" name ="Father" > import Game from './Game.vue' import { ref, reactive } from 'vue' let games = reactive ([ { id : '001' , name : 'war3' }, { id : '002' , name : 'rdr2' }, { id : '003' , name : 'gta5' } ]) let imageUrl = ref ('http://47.109.139.173:9000/food.guide/b1ba1c68-7b43-4c9f-9082-09bfcc9d8c57.jpg' )let videoUrl = ref ('http://47.109.139.173:9000/food.guide/20231017_222546.mp4' )</script >
1 2 3 4 5 6 7 8 9 10 11 12 13 <template > <div class ="game" > <h2 > {{ title }}</h2 > <slot > </slot > </div > </template > <script setup lang ="ts" name ="Game" > defineProps (['title' ])</script >
具名插槽 当一个插槽不能满足需求时,可以使用具名插槽,就是给插槽的内容加上名字,使用template分开,一个萝卜一个坑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <Game title ="热门游戏" > <template v-slot:s1 > <ul > <li v-for ="item in games" :key ="item.id" > {{ item.name }}</li > </ul > </template > <template v-slot:s2 > s2呼呼哈嘿 </template > </Game > <Game title ="今日美食" > <template v-slot:s1 > <img :src ="imageUrl" > </template > </Game > <Game title ="影视推荐" > <template v-slot:s1 > <video :src ="videoUrl" controls > </video > </template > </Game >
slot标签上也加上名字
1 2 3 4 5 6 <div class ="game" > <h2 > {{ title }}</h2 > <slot name ="s1" > s1插槽默认内容</slot > <br > <slot name ="s2" > s2插槽默认内容</slot > </div >
对于template上写v-slot:xx
这种写法,可以简写成:#xx
1 2 3 4 5 6 <Game title ="影视推荐" > <template #s1 > <video :src ="videoUrl" controls > </video > </template > </Game >
作用域插槽 插槽的数据在插槽组件中,而结构或者说使用插槽的地方可以接收插槽的数据
插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template > <div class ="game" > <h2 > 游戏列表</h2 > <slot :games ="games" > </slot > </div > </template > <script lang ="ts" setup name ="Game" > import { reactive } from 'vue' let games = reactive ([ { id : '001' , name : 'war3' }, { id : '002' , name : 'rdr2' }, { id : '003' , name : 'gta5' } ]) </script >
使用插槽的地方,要使用v-slot="params"
,也可以解构赋值v-slot="{games}"
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 > <div class ="father" > <h1 > 父组件</h1 > <div class ="content" > <Game > <template v-slot ="params" > <ul > <li v-for ="g in params.games" :key ="g.id" > {{ g.name }}</li > </ul > </template > </Game > <Game > <template v-slot ="params" > <ol > <li v-for ="g in params.games" :key ="g.id" > {{ g.name }}</li > </ol > </template > </Game > <Game > <template v-slot ="{games}" > <h3 v-for ="g in games" :key ="g.id" > {{ g.name }}</h3 > </template > </Game > </div > </div > </template > <script setup lang ="ts" name ="Father" > import { ref, reactive } from 'vue' import Game from './Game.vue' </script >
如果要给插槽加名字,那么可以在v-slot:xx="{xxx}"
,冒号后面就可以跟名字,也可以#xx="{xxx}"
这么些
ui组件库中会大量使用这个作用域插槽
其他API shallowRef与shallowReactive shallowRef
作用:创建一个响应式数据,但只对顶层属性进行响应式处理
特点:只跟踪引用值的变化,不关心值内部的属性变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let person = shallowRef ({ name : '张三' , age : 18 }) let car = shallowReactive ({ brand : 'bwm' , options : { color : '白色' , price : 10 } })
readonly与shallowReadonly readonly就是根据一个响应式数据生成一个只读的响应式数据,尝试修改只读数据的话,会报错
shallowReadonly
是浅层次的只读,对于对象的第一层属性是只读的,但是深层的属性是可以修改的
toRaw 用于获取一个响应式对象的原始对象,toRaw返回的对象不再是响应式的,不会触发视图更新
官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法,不建议保存对原始对象的持久引用,请谨慎使用
何时使用?在需要将响应式对象传递给非Vue的库或外部系统时,使用toRaw可以确保它们收到的是普通对象
1 2 3 function getRaw ( ) { console .log (toRaw (person)) }
markRaw 标记一个数据,让其不可成为响应式数据,如下,就算bookR使用了reactive
,也无法变成响应式数据
例如使用mock.js时,为了防止误把mock.js变为响应式对象,可以使用markRaw
去标记mock.js
1 2 3 4 5 6 7 8 9 10 let book = markRaw ({ name : 'gc' , price : 1 }) let bookR = reactive (book)function changeName ( ){ bookR.name = 's' console .log (bookR) }
customRef 作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制
使用注意事项
创建一个初始数据
注意get中的track()
调用,告诉vue数据msg很重要,要对msg进行持续关注,一旦msg变化就去更新
注意set中的trigger()
调用,通知vue一下数据变化了
下面实现了一个防抖的效果,后面写项目可以考虑用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let initValue = '你好' let timer : number let msg = customRef ((track, trigger ) => { return { get ( ) { track () return initValue }, set (value ) { clearTimeout (timer) timer = setTimeout (() => { initValue = value trigger () }, 1000 ) } } })
但一般开发会将自定义的ref封装到hooks中
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 { customRef } from 'vue' export default function (initValue: string , delay: number ) { let timer : number let msg = customRef ((track, trigger ) => { return { get ( ) { track () return initValue }, set (value ) { clearTimeout (timer) timer = setTimeout (() => { initValue = value trigger () }, delay) } } }) return { msg } }
然后组件中直接调用
1 2 3 4 import useMegRefs from '@/hooks/useMegRefs' let { msg } = useMegRefs ('zzmr' , 2000 )
Vue3新组件 Teleport 这玩意?我会用得到?
就是可以把<teleport>
标签中包裹的标签,传送到指定的标签内:
1 2 3 4 5 6 7 <teleport to ="body" > <div class ="modal" v-show ="isShow" > <h2 > 弹窗标题</h2 > <h2 > 弹窗内容</h2 > <el-button @click ="isShow = false" > 关闭弹窗</el-button > </div > </teleport >
App.vue中,Modal是放到app的div下的,但是使用了teleport
就可以直接将这个玩意传送到body下
1 2 3 4 <div class ="app" > <img src ="http://47.109.139.173:9000/food.guide/b1ba1c68-7b43-4c9f-9082-09bfcc9d8c57.jpg" alt ="" > <Modal > </Modal > </div >
Suspense 等待异步组件时渲染一些额外内容,让应用有更好的用户体验
子组件中包含着异步任务,在请求成功之前,用于展示一些东西,给用户更好的体验 ,但是无法作为骨架屏使用,而且这个东西是一个实验性的组件,未来可能移除
全局API转移到应用对象
app.component
app.config
app.directive
app.mount
app.unmount
app.use
其他
过渡类名 v-enter
修改为 v-enter-from
、过渡类名 v-leave
修改为 v-leave-from
。
keyCode
作为 v-on
修饰符的支持。
v-model
指令在组件上的使用已经被重新设计,替换掉了 v-bind.sync。
v-if
和 v-for
在同一个元素身上使用时的优先级发生了变化。
移除了$on
、$off
和 $once
实例方法。
移除了过滤器 filter
。
移除了$children
实例 propert
。