1.compostition api(组合式api) a. vue2 的选项是api一个业务函数的实现设计到的数据太分散,一个业务函数需要到data,method,watch等配置项中去找。vue3呢类似java可以使用set up语法糖。当然也兼容vue2的写法 b. vue3中可以混合写入vue2:即我写了setup也可以声明data(){}和method,然后方法和变量的引用可以使用(this.[])的方式访问读取。但是注意vue2选项式的写法可以访问到vue3中setup组合写法里面的数据(因为setup中的数据解析的比data中的快,在vue3.3+的版本只需声明一个script标签即可,3.3版本以下的需要额外多定义一个script声明定义组件名),vue3中无法使用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 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="person"> <h2>name:{{ name }}</h2> <h2>age:{{ age }}</h2> <h2>sex:{{ sex }}</h2> <button @click="handleshow" style="border: solid 2px">123</button> <button @click="modifyname">修改名字</button> <button @click="modifyage">修改年龄</button> <hr /> <text @click="p">{{ a }}</text> <hr></hr> <text>{{ c }}</text> </div> </template> <script lang="ts"> import { ref } from "vue"; export default { //data可以共存 data() { return { a: 100, c: this.sex, }; }, //方法也能共存 methods:{ p(){ alert(this.a) } }, setup() { //这里访问vue2的数据访问不到 // let vue3 = this.a let name = ref("张三"); let age = ref(18); let sex = ref("男"); function handleshow() { console.log("点击了"); } function modifyname() { name.value = "王五"; } function modifyage() { age.value += 1; } return { name, age, sex, handleshow, modifyname, modifyage, }; }, }; </script> <style scoped> .person { padding: 20px; background-color: rgb(33, 94, 74); } </style>
2. ref和reactive的区别: ** ref声明的变量.value(包括对象)访问,reactive无需(只能声明对象)
** reactive 重新分别配一个对象会失去响应式需要使用解决方法
1.object.assing(car,xxxx)
2.toRefs ,toRef(单个)
原因:因为在 JavaScript 中,变量存储的是对象的引用,重新赋值操作会让变量指向一个新的对象,而这个新对象并没有被 Proxy 包装,也就不具备响应式特性。
** reactive 使用 JavaScript 的 Proxy(代理)来实现响应式,而 ref 使用 getter/setter。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let car = reactive({ name1: "小汽车", price: 1000 }) //这样将name1和price解构出来然后对解构出来的对象进行操作会失去响应式 let {name1, price} = car function changecar() { name1+='~' price+=1 } //需要这么做 let {name1, price} = toRefs(car) function changecar() { //注意这里要加上.value name1.value += '~' price.value += 1 }
3.computed
计算属性具有缓存,如果计算属性的值没有发生改变就不会重新执行代码,直接使用缓存里面的值
计算属性是可读的,要修改需要加上set函数
1 2 3 4 5 6 7 8 let full nae = computed({ get(){ return .... }, set(val){ } )
4. watch和watchEffect
监视数据的变化,只能监视以下四种数据: a. ref定义的数据(监听其value的值:地址) b.reactive定义的数据 c.函数返回的一个值(getter函数) d.一个包含上述内容的数组
情况1:监听ref定义的基本类型数据
1 2 3 4 5 6 7 8 //watch() 返回一个函数stopWatch自己定义的stopWatch,这个函数就是停止监听的方法 const stopWatch = watch(sum,(newValue,oldValue)=>{ console.log('sum变化洛',newValue,oldValue) if(newValue>=10){ //停止监听的方法 stopWatch() } })
情况2:监听ref的对象类型,监视的是对象的地址值,若想要监视对象内部属性的变化,需要手动深度监视。ref包装的对象默认只监听引用变化,不监听内部属性变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 let car = ref({ name1: "小汽车", price: 1000 }) // let car = toRefs(car) watch(car,function(newValue, oldValue) { console.log("sssss", newValue, oldValue); },{deep:true,immediate:true})//immediate:true立即执行监听无需手动触发 //不添加deep的话无法监听到内部的属性变化 // 不加deep: // - 只监听car.value整个对象的替换 // - car.value.name1修改不会触发(内部属性变化) // car.value = { ...car.value, name1: car.value.name1 + '~' }这种情况整个对象的替换就无需加deep
情况3:监听reactive定义的【对象类型】的数据,且默认开启了深度监视(自动可以监听到对象的属性改变底层隐式的创建了深度监听且无法关闭 )。然后newValue和oldValue返回的是一样的是因为trigger(target, key)这个方法没有传入 oldValue!
1 2 3 4 5 6 7 8 9 let car = ref({ name1: "小汽车", price: 1000 }) //无需加deep watch(car,function(newValue, oldValue) { console.log("sssss", newValue, oldValue); })
情况4:只需要监听对象中的某一个属性 1. 监视某个对象的属性的时候,需要写成函数形式 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 let person = reactive({ name:"张三", age:18 aaa:{ color:"black" tall:28 } }) function change(){ //这里替换了,该对象的地址也会被替换,所以会导致监听失败 person.aaa={color:"yellow",tall:30} } function change1(){ person.name="李四" } //监听对象的属性,如调用change1方法即会触发watch watch(()=>{return person.name},(newValue,oldValue)=>{ console.log('person.name',oldValue,) //监听对象的属性(对象aaa),可直接写 //但是如果该对象的属性被直接替换了失去如调用change方法,该监听不会被触发 所以建议写函数式 watch(aaa,(newValue,oldValue)=>{ console.log('person.name',oldValue,) }) //函数式写法此时作为watch的第一个参数(()=>person.car)现在监听的是该对象(person)的该属性(aaa)的地址值,即重新赋予一个对象改变地址的时候能够监听到。但是对象(aaa)里面的属性变化的时候监听不到了,此时加上深度监听即可{deep:true} watch(()=>person.aaa,(newValue,oldValue),{deep:true})=>{ console.log('person.name',oldValue,) })
情况5:监视上述的多个数据,用数组包裹
1 2 3 watch([()=>person.name,()=>person.car],(newValue,oldValue),{deep:true})=>{ console.log('person.name',oldValue,) }
watchEffect
之前如果使用watch的话监听多个对象或值的时候需要加到watch的第一个入参中的数组中,假设需要监听很多个,那么就需要写很多个监听的对象和值,此时可以使用watchEffect自动分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 //watch 写法 watch([temp, height], (value) => { console.log("temp, height变化了", value); //解构出来 let [temp, height] = value; if (temp > 80 || height > 80) { alert("水温或者水位达到80了"); } }); //watchEffect写法,会自动分析,并且会立即运行无需触发 watchEffect(()=>{ if(temp > 80 || height > 80){ alert("水温或者水位达到80了"); } })
5. Typescript一些小知识 如果要重新加载项目使用ctrl+shift+p 输入了reload window
声明一个types文件夹,下面在新建一个index.ts用于类型定义
1 2 3 4 5 6 7 8 9 10 ### index.ts export interface person { name: string; age: number; sex: string; } //声明数组类型 export type persons = Array<person>; // export type persons = person[];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ### person.vue //先引入类型 import {type person,type persons} from "@/types/index"; //规定该对象的类型为:person let person_one :person = { name: "张三", age: 18, sex: "男" } //声明person_two为数组类型,然后数组里面的值(对象)都为person类型 let person_two :persons=[ { name: "张三", age: 18, sex: "男" }, { name: "张三", age: 18, sex: "男" }, { name: "张三", age: 18, sex: "男" } ] console.log("person_one",person_one); console.log("person_two",person_two);
6. Props
父组件将要显示的数据传给子组件显示简介解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 父组件引入 <template> <div class="app"> <car a="haha"></car> </div> </template> 子组件car.vue <template> <div class="car"> {{ a }} </div> </template> <script setup lang="ts"> import { defineProps} from "vue"; //必须是中括号 defineProps(['a']) </script>defineProps<{list:Car}>
即实现了父组件传递了haha给子组件,子组件显示,这样就可以实现我不同页面(父组件)可以动态的控制该组件在父组件中引入子组件的位置显示父组件想显示的值。
这里会出现一些问题比如defineProps([‘a’]) 这里不定义接受的类型,如果父亲传递的是错误的值也会被子组件渲染上去。所以子组件接收的时候最好限定接受类型,或者当父组件没有传数据的时候子组件显示默认值
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 class="car"> <ul> <li v-for="value in list" key="value.id"> {{ value.name }}--{{ value.price }}--{{ value.color }} </li> </ul> </div> </template> <script setup lang="ts"> import { type Cars } from "../types"; //接受list + 限制类型 // defineProps<{ list: Cars }>(); //接受list + 限制类型+限制必要性+指定默认值 //这样即使父亲没有传递list属性,也不会报错 withDefaults(defineProps<{ list?: Cars }>(), { list: () => [{ id:"dfffffff",name: "小汽车", price: 1000, color: "red" }], }); </script> <style scoped></style>
7. vue的生命周期 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 27 28 29 30 31 32 33 34 //创建前的钩子 1. beforeCreate(){ } //创建好的钩子 2. created(){ } //创建前的钩子 3. beforeMount(){ } 4. mounted(){ } 5.//更新前 beforeUpdate(){ } 6.//更新完毕 updata(){ } 7.//销毁前 beforeDestory(){ } 8.//销毁完毕 destroyed(){ }
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 没有了beforeCreate和created钩子,用setup函数代替 1. 挂载前调用 ()=>{回调函数}这个函数 onBeforeMount( ()=>{回调函数} ){ } 2. onMounted(()=>{ cosolog.log("挂载完毕) }) 3. 更新前 onBeforeUpdate(()=>{ }) 4. 更新完毕 onUpdate(()=>{ }) 5. 卸载前 onBeforeUnmount(()=>{ }) 6. 卸载后 onUnmouted(()=>{ }) 然后的话父组件中用了子组件在项目运行的时候会先挂载子组件,等子组件改在完毕后挂载父组件
8. hooks vue3的特点在于组合式api。hooks就是一个很好的体现,把属于一类的资源和方法放在了hooks里面维护。 比如我的一个页面既要显示求和的功能,又要显示一个列表的功能,我可以把求和功能涉及到的资源和方法封装到一个hooks内,把显示列表涉及到的资源和方法在封装成另一个hooks。这样在大型项目中可以分别维护自己的hook。 新建一个hooks文件夹,该目录下分别创建求和的hook:useSum.ts,列表显示的hook:useList.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //页面业务文件调用useSum和useList <template> <h2>总和为:{{sum}}<h2> <ur> <li v-for="(item,index) in List" :key="index"> {{item.name}}---{{item.age}} </li> </ul> <button @click="add"></button> <button @click="getList"></button> </template> <script> import useSum from "@/hooks/useSum" import usrList from "@/hooks/useList" //解构出hook函数里面的资源和方法,就可以直接在template里面使用了 const {sum,add} = useSum(); cosnt { list, getList} = useList(); </script>
1 2 3 4 5 6 7 8 9 10 11 *****usrSum.ts //注意一定要将导出的方法和数据放在一个方法里面 export default function(){ let sum=ref(0) function add(){ sum.value++; } //给外部提供 return {sum,add} }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 *****useList.ts export default funciton(){ let list = reactive({ name:"张三", age:15 }) function getList(){ list.push({ name:"李四", age:25 }) } }
9. 路由器router route路由 路由变化url改变,路由器监听到根据该改变的路由重新寻找匹配的那一个组件
路由组件通常存放在 pages 或 views 文件夹
一般组件通常存放在 components 文件夹 路由组件的加载特性: 通过点击导航,视觉效果上”消失”了的路由组件,默认是被卸载掉的,需要的时候再去挂载。
先在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 //1.引入 import { createRouter, createWebHistory } from "vue-router"; import buycar from "@/page/buycar.vue"; import detail from "@/page/detail.vue"; import home from "@/page/home.vue"; //2.创建一个路由器,并暴露出去 export const router = createRouter({ //vue2:mode:'history' //vue3:history: createWebHistory(), history: createWebHistory(), routes: [ { path: "/home", component: home, }, { path: "/detail", component: detail, }, { path: "/buycar", component: buycar, }, ], }); export default router;
main.ts
1 2 3 4 5 6 7 8 9 import { createApp } from "vue"; import App from "./App.vue"; import router from "./router"; const app = createApp(App); //挂载路由 app.use(router); app.mount("#app");
app.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 //注意routerlink是一个组件最好用 active-class <div class="navbar"> //还有一种写法(对象写法) <RouterLink :to="{path:/home}" active-class="click">首页</RouterLink> 或者 <RouterLink :to="{name:home}" active-class="click">首页</RouterLink> <div class="active"> <RouterLink to="/home" active-class="click">首页</RouterLink> </div> <div class="active"> <RouterLink to="/detail" active-class="click">详情</RouterLink> </div> <div class="active"> <RouterLink to="/buycar" active-class="click">购物车</RouterLink> </div> </div>
3.嵌套路由比如我的一个路由页面里面还有嵌套一个路由可以在路由配置文件下配置该路由的子路由
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 //1.引入 import { createRouter, createWebHistory } from "vue-router"; import buycar from "@/page/buycar.vue"; import detail from "@/page/detail.vue"; import home from "@/page/home.vue"; import News from "@/page/News.vue"; //2.创建一个路由器,并暴露出去 export const router = createRouter({ history: createWebHistory(), routes: [ { path: "/home", component: home, }, { name: "n_detail", path: "/detail", component: detail, //配置该路由的子路由 children: [ { name: "n_news", //也可以提前配好占位符,然后再使用rootlink的时候可以直接:to=/detail/news/哈哈/嘿嘿/嘻嘻 随便传 //path: "news", //path: "detail/:x/:y/:z" component: News, }, ], }, { path: "/buycar", component: buycar, }, ], }); export default router;
然后现在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 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="content"> <ul> <li v-for="item in source" :key="item.id"> <!-- <RouterLink //1.路由传参使用模板字符串 :to="`/detail/news?a=${item.name}&b=${item.age}&c=${item.sex}`" >{{ item.name }}</RouterLink > --> <RouterLink //也可以用对象的形式配置path或者name,传递的参数放入query中 :to="{ //path:'/detail/news', //也可以这样写使用路由name方式跳转 name: 'n_news', query: { a: item.name, b: item.age, c: item.sex, }, }" >{{ item.name }}</RouterLink > </li> </ul> <div class="container"> <RouterView></RouterView> </div> </div> </template> <script lang="ts" setup> import { reactive } from "vue"; import { RouterLink, RouterView } from "vue-router"; const source = reactive([ { id: 1, name: "新闻1", age: 18, sex: "男" }, { id: 2, name: "新闻2", age: 19, sex: "女" }, { id: 3, name: "新闻3", age: 20, sex: "中" }, { id: 4, name: "新闻4", age: 21, sex: "小" }, ]); </script> <style scoped> li { /* list-style: none; */ text-align: center; margin: 10px; padding: 10px; font-size: 25px; } li::marker { color: rgb(47, 51, 125); } .content { display: flex; } .container { width: 80%; margin: 35px; height: 500px; border-radius: 20px; border: solid 2px rgb(47, 51, 125); } </style>
子路由页面收到路由传递的参数并渲染 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 <template> <ul class="list"> <li>{{ route.query.a }}</li> <li>{{ route.query.b }}</li> <li>{{ route.query.c }}</li> </ul> </template> <script lang="ts" setup> import { useRoute } from "vue-router"; //这个引入是Vue Router 4中的组合式API函数,用于获取当前路由对象的信息。 //具体来说,useRoute返回一个路由对象,该对象包含当前路由的信息,如路径、参数、查询参数等。 //在组合式API中,我们可以使用这个函数来访问当前路由的状态,而不必通过this.$route(在选项式API中) let route = useRoute(); console.log("@", route.query);//这里拿到传递的参数 //但是如果直接将query结构出来会导致其失去响应式无法导致路由参数传递过来了,但是渲染的时候因为失去了响应式导致无法实时渲染即: //let {query} = route //会导致失去响应式 let {query} = ref(route)// 这样即可 </script> <style scoped> li { list-style: none; text-align: center; margin: 10px; padding: 10px; font-size: 25px; } </style>
路由传参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 31 32 33 34 35 36 37 //1.引入 import { createRouter, createWebHistory } from "vue-router"; import buycar from "@/page/buycar.vue"; import detail from "@/page/detail.vue"; import home from "@/page/home.vue"; import News from "@/page/News.vue"; //2.创建一个路由器,并暴露出去 export const router = createRouter({ history: createWebHistory(), routes: [ { path: "/home", component: home, }, { name: "n_detail", path: "/detail", component: detail, children: [ { name: "n_news", //可以指定占位符 path: "news/:name/:age/:sex", //表明可传可不传sex //path: "news/:name/:age/:sex?", component: News, }, ], }, { path: "/buycar", component: buycar, }, ], }); export default router;
传递路由信息使用params
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!-- <RouterLink //路由传参使用模板字符串 :to="`/detail/news?a=${item.name}&b=${item.age}&c=${item.sex}`" >{{ item.name }}</RouterLink > --> > //也可以这样写使用路由name方式跳转, 1.注意是用params不能用path路径只能用路由配置的name 2.如果占位有参数但是params里面没有传也不行,不过可以设置path: "news/:name/:age/:sex?",表示sex可选传递 且params不能传递数组 <RouterLink :to="{ name: 'n_news', params: { name: item.name, age: item.age, sex: item.sex, }, }" >{{ item.name }}</RouterLink >
Props传参 props: true不限制传参方式,只是自动传递params作为props,query还是要手动获取。 不管是在routerlink中还是router路由配置的path中占位符传参都需要定义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 props: true意味着将路由的params(即路径参数)作为props传递给组件。 { name: "n_detail", path: "/detail", component: detail, children: [ { name: "n_news", //要设置占位符让props知道该传递哪个,路由页面不提供props传递值的入口只能通过路由规则 path: "news/:name/:age/:sex", component: News, //第一种写法:设置这个然后再接受路由信息的页面拿到显示即可 props: true, //第二种写法:函数写法,可以自己决定将什么作为props传递给路由组件 props(route){ return route.query } //第三种写法:对象写法,可以自己决定将什么作为props给路由组件 props:{ a:100, b:200 } }, ], },
路由的 replace和push router.replace()和router.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 <script setup> import { useRouter } from 'vue-router' const router = useRouter() //比如登录成功 function goToHome() { router.replace('/home') // 或者使用对象形式 // router.replace({ path: '/home' }) // 或者命名路由 // router.replace({ name: 'home' }) } </script> 或者直接在routerlink上加上该属性 <div class="navbar"> <div class="active"> <RouterLink replace to="/home" active-class="click">首页</RouterLink> </div> <div class="active"> <RouterLink replace to="/detail" active-class="click">详情</RouterLink> </div> <div class="active"> <RouterLink replace to="/buycar" active-class="click">购物车</RouterLink> </div> </div>
编程式路由 符合某些条件在跳转
指的是在Vue组件中通过调用router.push、router.replace等方法进行路由跳转,并传递参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <ul> <li v-for="item in source" :key="item.id"> <button @click="shownews(item)">点我看新闻</button> function shownews(item: newintergface) { router.push({ name: "n_news", params: { name: item.name, age: item.age, sex: item.sex, }, }); } </li> </ul>
重定向
1 2 3 4 5 进入项目默认进入home页 { path: "/", redirect: "/home", },
10.computed 1 2 3 4 5 1. npm install pinia 在main.ts中引入、创建并安装 import {createPinia} from 'pinia' const pinia = createPinia() app.use(pinia)
新建一个store文件,创建你需要的pinia文件如count.ts pinia中的state中的数据底层使用reactive实现响应式,在别的文件引入使用的时候无需.value引用 然后如果实在state外面定义的ref还是需要.value使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import {defineStore} from 'pinia' export const userTalk = defineStore('talk',{ state:()=>{ return{ talklist:[ {id:1,name:'张三',msg:'你好啊',time:'9:00'}, {id:2,name:'李四',msg:'今天天气不错',time:'10:00'}, {id:3,name:'王五',msg:'我们去打球吧',time:'11:00'}, {id:4,name:'赵六',msg:'学习使我快乐',time:'12:00'}, {id:5,name:'田七',msg:'加油加油',time:'13:00'}, sum:100 ] } } })
再别的页面可以直接使用
1 2 3 4 import {useTalk} from "@/store/talk" const talkStore = userTalk(); const list = talkStore.talklist let sum = talkStore.sum
修改数据:
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 1.pinia中直接可以直接修改,但是如果修改多个话会触发对个响应式重新计算 let sum = talkStore.sum++ //如果修改多个数据使用这个 2.第二种修改方式 couteStore.$petch({ sum:888, .... }) 3.Actions 就是 Pinia/Vuex Store 中定义的“方法”(函数)。 它们专门用来封装修改状态或执行副作用的业务逻辑。 在store里面封装修改的方法action:{ //提供给外部使用 } count.ts export const useCount = defineStore('count',{ state:()=>{ return { sum:6, school:'ltzx', address:'北京' } } actions:{ increment(value) { console.log("increment开始执行") this.sum += value }, } }) 使用pinia的页面 import { useCount } from '@/store/count' let n = ref(1) const countStore = useCount(); //直接使用即可 countStore.increment(n.value)
读取pinia里面的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { useCount } from '@/store/count' import {toRefs } from "vue" import { storeToRefs } from "pinia" let n = ref(1) const countStore = useCount(); //直接使用即可 countStore.increment(n.value) //这样会导致解构出来的数据失去响应式 const {sum,school,adress} = countStore //两种方法:1.这种方法会导致store里面所有的数据都是响应式的 const {sum,school,adress} = toRefs(countStore) //2. 这样只会把store中的数据进行包裹,方法不会 const {sum,school,adress} = storeRefs(countStore)
getters action → 获取数据、修改状态、执行业务逻辑(“做事”)
getter → 基于现有状态进行计算、格式化、筛选(“算数”) 记住这个黄金法则:
从服务器拿数据、修改数据、执行业务流程 → action
对已有数据进行计算、格式化、筛选、统计 → getter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 export const useCount = defineStore("count",{ state:()=>({ sum:6, school:'ltzx', address:'北京' }), getters:{ bigSum(state){ return state.sum*10 }, upperSchool(){ this.school.toUpperCase } } })
$subscribe const talkStore = usrTalkStore() const {talkList} = storeToRefs(talkStore) //mutate为本次修改的信息 state为真正的数据 talkStore.$subcribe((mutation,state)=>{ console.log(“talkStore里面的数据发生变化了”,mutate,state) })
store组合式写法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 //talk.ts import {reactive} from 'vue' export const useTalkStore =defineStore("talk",{ //自己定义就无需写state action这些了 const talkList = reactive( { xxx:xxx, xxx:xxx } ) async function getTalk(){ xxxxx } return {talkList,getTalk} })
11.组件通信 1.props 父传子:属性值是非函数 子传父:属性值是函数 传递超过两层不建议使用props
父亲:props_father.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 <template> <div class="contianer"> <h2>我是父组件</h2> <h2>父亲有一辆车,品牌是:{{ car }}</h2> <h2 v-show="toy">我的孩子有啥玩具:{{ toy }}</h2> <PropsChildren :car="car" :sendtoy="getToy" /> </div> </template> <script setup lang="ts"> import PropsChildren from "./props_children.vue"; import { ref } from "vue"; let toy = ref(""); // defineProps<{ msg: string }>(); let car = ref("大众"); function getToy(value: string) { toy.value = value; } </script> <style scoped> .contianer { text-align: center; width: 80%; height: 40%; margin: 20px auto; background-color: rgb(145, 181, 181); border: solid 2px rgb(44, 38, 38); border-radius: 20px; } </style>
孩子:props_children.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="contianer1"> <text class="wenben">我是子组件</text> <text class="wenben">我有玩具:{{ toy }}</text> <text class="wenben">我的父亲有:{{ car }}</text> <button @click="sendtoy(toy)">点击发送玩具给父亲</button> </div> </template> <script lang="ts" setup> //拿到父组件传来的参数,当点击按钮时父组件的sendtoy函数会被调用 // 父组件通过 :sendtoy="getToy" 把 getToy 函数赋值给了 sendtoy 这个 prop。所以子组件调用 sendtoy(toy) 实际上就是在调用父组件的 // getToy(toy) 函数。 defineProps(["car", "sendtoy"]); import { ref } from "vue"; let toy = ref("变形金刚"); </script> <style scoped> .contianer1 { display: flex; justify-content: center; flex-direction: column; align-items: center; width: 80%; height: 50%; min-height: 80px; margin: 20px auto; background-color: rgb(121, 155, 229); border: solid 2px rgb(44, 38, 38); border-radius: 20px; z-index: 10; -webkit-box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.5); } .wenben { text-align: center; font-size: 20px; margin-bottom: 10px; } </style>
2. 自定义事件=>实现子传父
event._father.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 <template> <div class="contianer"> <h2>我是父组件</h2> <h2 v-show="toy">我的孩子有啥玩具:{{ toy }}</h2> <!-- @send-toy="getToy" 表示监听子组件触发的 send-toy 自定义事件,当事件触发时调用父组件的 getToy 方法。 @ 是 v-on: 的简写,用于事件监听。 --> //命名传递的事件建议使用kabab-case 命名(x-x),方法使用驼峰命名xxYyy <PropsChildren @send-toy="getToy" /> </div> </template> <script setup lang="ts"> import PropsChildren from "./event_child.vue"; import { ref } from "vue"; let toy = ref(""); function getToy(value: string) { toy.value = value; } </script> <style scoped> .contianer { text-align: center; width: 80%; height: 40%; margin: 20px auto; background-color: rgb(145, 181, 181); border: solid 2px rgb(44, 38, 38); border-radius: 20px; } </style>
event_child.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 <template> <div class="contianer1"> <text class="wenben">我是子组件</text> <text class="wenben">我有玩具:{{ toy }}</text> //2. 触发事件:emit('事件名', 参数) 是固定写法,第一个参数是事件名,后面是传递的数据 <button @click="emit('send-toy', toy)">点击发送玩具给父亲</button> </div> </template> <script lang="ts" setup> import { ref } from "vue"; //1. 必须使用 emit:const emit = defineEmits(["send-toy"]) 声明可以触发的事件 const emit = defineEmits(["send-toy"]); let toy = ref("变形金刚"); </script> <style scoped> .contianer1 { display: flex; justify-content: center; flex-direction: column; align-items: center; width: 80%; height: 50%; min-height: 80px; margin: 20px auto; background-color: rgb(121, 155, 229); border: solid 2px rgb(44, 38, 38); border-radius: 20px; z-index: 10; -webkit-box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.5); } .wenben { text-align: center; font-size: 20px; margin-bottom: 10px; } </style>
3. mitt 任意组件通信通过事件总线 1.npm install mitt 2.新建一个utils目录,该目录下新建一个emmitter.ts
1 2 3 4 5 import mitt from "mitt"; const emitter = mitt(); export default emitter;
3.组件1:component1.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 <template> <div class="contianer1"> <text class="wenben">我是组件1</text> <text class="wenben">{{ toy }}</text> <!-- 这里的意思是点击后发送 toy 给 component2,通过 mitt 事件总线发送 toy 给 component2 --> <button @click="emitter.emit('send-toy', toy)"> 发送玩具给component2 </button> </div> </template> <script lang="ts" setup> import { ref } from "vue"; import emitter from "../../utils/emitter"; let toy = ref("奥特曼") </script> <style scoped> .contianer1 { display: flex; justify-content: center; flex-direction: column; align-items: center; width: 80%; height: 150px; min-height: 80px; margin: 20px auto; background-color: rgb(121, 155, 229); border: solid 2px rgb(44, 38, 38); border-radius: 20px; z-index: 10; -webkit-box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.5); } .wenben { text-align: center; font-size: 20px; margin-bottom: 10px; } </style>
组件2:component2
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="contianer1"> <text class="wenben">我是组件2</text> <text class="wenben">收到了component1传递的组件{{ toy }}</text> </div> </template> <script lang="ts" setup> import { ref, onUnmounted } from "vue"; import emitter from "../../utils/emitter"; let toy = ref(""); emitter.on("send-toy", (value: any) => { toy.value = value; }); onUnmounted(()=>{ //组件卸载的时候解绑sent-toy事件 emitter.off('send-toy') }) </script> <style scoped> .contianer1 { display: flex; justify-content: center; flex-direction: column; align-items: center; width: 80%; height: 150px; min-height: 80px; margin: 20px auto; background-color: rgb(121, 155, 229); border: solid 2px rgb(44, 38, 38); border-radius: 20px; z-index: 10; -webkit-box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.5); } .wenben { text-align: center; font-size: 20px; margin-bottom: 10px; } </style>
4.v-model //father.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template> //也可以直接v-model,下面等同于v-model <zpqInput v-model="username"></zpqInput> <zpq-input //数据流向页面但是没有双向绑定 :modelvalue="username" //监听update:modelvalue事件,如果子组件触发这个事件,就将该事件触发时传递过来的 $event值赋给username @update:modelvalue="username=$event"> </zpq-input> </template> <script setup lang="ts"> import {ref} from "vue" import zpqInput from "./zpq-input.vue"; let username = ref("12345") </script>>
//zpq-input.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 <template> <div class="header"> <input type="text" :value="modelvalue" @input="handleInput" > </div> </template> <script setup lang="ts"> //- Props = 接收数据 //- Emits = 发送事件 //defineProps:声明并接收父组件通过 :modelvalue="xxx" 传来的数据(父→子) defineProps(["modelvalue"]) //声明可以触发的事件,让父组件通过 @event 监听 const emmit=defineEmits(["update:modelvalue"]) function handleInput(event:Event){ const target = enent.target as HTMLInput emit("update:modelvalue",target.value) } </script> 注意:如果父组件直接使用<zpqInput v-model="username"></zpqInput> 那么子组件的:value="modelvalue" 只能写成这样 所以也可以自定义:这里定义为name,注意格式 <zoqinput v-model:name="username"/> 底层为 <zpqInput :modelvalue="name" @update:name="username=$event"> </zpqInput> 然后再子组件中: <inpurt :value="name" @input="handleInput"> function handleInput(evnt: Event) { const target = event.target as HTMLIputElement emit("update:name",target.value) }
5. $attrs 访问未被 props 接收的父组件属性,注意接收了的不会在进行传递了,需要个中间组件如果祖孙组件通信的话。 实现属性透传到子组件的内部元素
爷爷.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 <template> <div class="contianer"> <h2>爷爷</h2> a: {{ a }}<br /> b: {{ b }}<br /> c: {{ c }}<br /> d: {{ d }}<br /> <father :a="a" :b="b" :c="c" :d="d" :add="add"></father> </div> </template> <script setup lang="ts"> import father from "./父亲.vue"; import { ref } from "vue"; let a = ref(1); let b = ref(2); let c = ref(3); let d = ref(4); function add() { a.value++; b.value++; c.value++; d.value++; } </script>
父亲.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 <template> <div class="contianer"> <h2>父亲</h2> <!-- 当你在模板中使用 {{ $attrs }} 时,Vue 会调用 JSON.stringify() 来转换为字符串显示。 JSON.stringify() 会忽略函数,所以 add 函数不会在模板渲染中显示出来。 --> <h2>{{ $attrs }}</h2> //将没有接收的props全部绑定给son组件,因此son组件中可以用props接收 <son v-bind="$attrs" /> </div> </template> <script setup lang="ts"> import son from "./儿子.vue"; import { useAttrs, onMounted } from "vue"; defineProps<{}>(); const attrs = useAttrs(); console.log("父亲组件接收到的属性:", attrs); onMounted(() => { console.log("父亲组件接收到的属性:", attrs); console.log("add 方法:", attrs.add); }); </script> <style scoped> </style>
儿子.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 <template> <div class="contianer"> <h2>儿子</h2> <h2>a:{{ a }}</h2> <h2>b:{{ b }}</h2> <h2>c:{{ c }}</h2> <h2>d:{{ d }}</h2> <button @click="add">点击增加</button> </div> </template> <script setup lang="ts"> defineProps(["a", "b", "c", "d", "add"]); </script> <style scoped> .contianer { display: flex; justify-content: center; flex-direction: column; /* text-align: center; */ width: 80%; height: 40%; margin: 20px auto; background-color: rgb(145, 181, 181); border: solid 2px rgb(44, 38, 38); border-radius: 20px; } </style>
6.$refs 与 $patent $refs 用于:父->子 子要将defineExpose({xxx }); 通信的信息暴露出去 $patent 用于: 子->父 父要将defineExpose({xxx }); 通信的信息暴露出去
$refs 父亲操作子组件的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 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 <template> <div class="contianer"> <h2>我是父组件</h2> <h2>我是{{ num }}本书</h2> <button @click="changeToy" style="width: 80px">修改孩子1的内容</button> <!-- 这里就是获取子组件的所有实例--> <button @click="getAllChild($refs)" style="width: 80px"> 获取所有子组件实例 </button> //也可以通过ref一个一个的获取 <child1 ref="c1"></child1> <child2 ref="c2"></child2> </div> </template> <script setup lang="ts"> import child1 from "./child1.vue"; import child2 from "./child2.vue"; import { ref } from "vue"; //为什么要定义ref 因为要通过 ref 拿到子组件的实例, //为什么可以拿到实例呢?因为子组件的实例被保存在 ref 中,所以可以通过 ref 拿到实例。 //保存到哪个ref中呢? 通过 ref="c1" 这个属性保存到 c1 中 ,如果没有将c1 绑定到 ref 中,那么 c1 就会变成 undefined。 // 为什么呢? 因为没有地方保存子组件的实例。 let c1 = ref(); let c2 = ref(); let num = ref(5); //这个是为了给$parent 暴露出数据给子组件 defineExpose({ num }); //$ref获取到所有的子组件的实例 function getAllChild(refs: any) { console.log("所有子组件实例", refs); for (const key in refs) { console.log("子组件实例", key, refs[key]); if (key === "c1") { refs[key].toy = "aaaa"; } else if (key === "c2") { refs[key].computer = "Dell"; } } } function changeToy() { console.log("111111", c1.value); //可以直接修改子组件的值,也可以理解可以组件通信 c1.value.toy = "cccc"; c2.value.computer = "MacBook Pro"; } </script> <style scoped> .contianer { display: flex; justify-content: center; flex-direction: column; /* text-align: center; */ width: 80%; height: 80%; margin: 20px auto; background-color: rgb(145, 181, 181); border: solid 2px rgb(44, 38, 38); border-radius: 20px; } </style>
child1.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="contianer"> <h2>我是孩子1</h2> <h2>我有个玩具:{{ toy }}</h2> <!-- 获取父组件的实例-> <button @click="fight($parent)">获取父亲的信息</button> </div> </template> <script setup lang="ts"> import { ref } from "vue"; const toy = ref("变形金刚"); //暴露出去,这是宏函数 作用是暴露属性给父组件,父组件通过 ref="c1" 拿到子组件的实例 defineExpose({ toy }); function fight(parent: any) { //修改父组件的值,组件通信 console.log("父亲信息", parent); parent.num -= 1; } </script> <style scoped> .contianer { display: flex; justify-content: center; flex-direction: column; /* text-align: center; */ width: 80%; height: 80%; margin: 20px auto; background-color: rgb(145, 181, 181); border: solid 2px rgb(44, 38, 38); border-radius: 20px; } </style>
7._provide_inject 可以实现组件直接通信
发出信息的组件 1.import { provide } from ‘vue’ //假设 let car = reactive({ name:”保时捷”, monet:1000 }) 2.provid(“car”,car)//第一个入参为提供的对象自定义名称,第二个入参是对象实例
收到信息的组件 import { inject } from ‘vue’
let car = inject(“car”) 然后再templa中就可以直接和 这样使用了
8.slot: 它允许父组件向子组件动态注入内容/结构,从而创建高度定制化且可复用的组件。
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 55 56 57 58 59 60 //父组件 <template> <div class="contianer"> <!-- <h2>我是爸爸</h2> --> <cartegory title="热梦游戏列表"> <ul> <li v-for="item in game" key="name">{{ item.name }}</li> </ul> </cartegory> <cartegory title="今日美食城市"> <img :src="imgurl" /> </cartegory> <cartegory title="今日影视推荐"> <video :src="vdiourl" controls="true" /> </cartegory> </div> </template> <script setup lang="ts"> import cartegory from "./cartegory.vue"; import { ref, reactive } from "vue"; let title = ref("热梦游戏列表"); let game = ref([ { name: "《英雄联盟》" }, { name: "《Counter-Strike: Global Offensive》" }, { name: "《Dota 2》" }, ]); let imgurl = "https://raw.githubusercontent.com/Lazi66/image/master/ikun.svg"; let vdiourl = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"; </script> <style scoped> li { margin-top: 20px; } video, img { height: auto; width: 100%; } .contianer { display: flex; justify-content: space-evenly; align-items: center; /* justify-content: center; flex-direction: column; */ width: 100%; height: 80%; margin: 20px auto; background-color: rgb(145, 181, 181); border: solid 2px rgb(44, 38, 38); border-radius: 20px; } </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 <template> <div class="contianer1"> <h2 style=" text-align: center; margin-bottom: 10px; background-color: orange; "> {{ title }} </h2> <div class="show"> //默认插槽 <slot name="default"></slot> <slot></slot> </div> </div> </template> <script setup lang="ts"> import { ref } from "vue"; defineProps(["title"]); </script> <style scoped> .contianer1 { height: 300px; width: 200px; border-radius: 15px; box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.5); background-color: aquamarine; border: solid 2px red; } </style>
2.具名插槽 在父组件中有两种写法v-slot和#,一般来说具名插槽展示的结构相同数据不同。
1 2 3 4 5 6 7 8 9 <cartegory> <tenplate v-slot:s1> </template> <tenplate #s1> </template> </cartegory>
1 2 3 4 子组件 <slot name="s1"></slot> <slot name="s2"></slot>
3.作用域插槽 有子组件维护所有的数据和交互,呈现的结构有父组件决定
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 //将games数据传递给父组件拿到使用 //作用域插槽也可以取名字,然后父组件使用的时候v-slot:"zpq"也许指明名字 //<slot name="zpq" :youxi="games" :a="hah" :b="heihei"></slot> <slot :youxi="games" :a="hah" :b="heihei"></slot> let game = reactive([ { id:'asd', name:英雄联盟 }, { id:'asd', name:英雄联盟 }, { id:'asd', name:英雄联盟 } ])
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 //{youxi}这样可以直接拿到youxi对象的数据, //如果直接slot="a" consol.log(a) 会显示子组件传递的game a b 的整个对象的数据 //slot = "{youxi} 这样可以直接结构出来 //如果子组件中定义了作用域插槽的名字就要v-slot:"zpq" //<template slot = "{youxi}" v-slot:"zpq"> <template slot = "{youxi}"> <ul> //没有解构的话 //<li v-for="item in a.games" :key="item.id">{{item.name}}</li> <li v-for="item in games" :key="item.id">{{item.name}}</li> </ul> </template>
![总结]https://raw.githubusercontent.com/Lazi66/image/master/微信图片_20251214181143_45_48.png )
杂 toRow: 用于获取一个响应式的对象的原始对象,toRow返回的对象不再是响应式的,不会触发视图更新
1 2 3 4 5 6 let person = reactive({ name:"toy", age:18 }) let person1 = toRaw(person)
markRow 标记一个对象使其永远不会变成响应式
1 2 3 4 5 6 7 let car = markRow({ brand:"benchi", age:18 }) //此时car并不会称为响应式的对象 let car2 = reactive(car)
customRef 作用创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制 场景比如定义一个响应式的数据,数据发生修改页面延迟1s钟进行渲染 在set中设置一个setTimeout(()=>{},1000)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let initvalue = "hello world" let timer :number //track 和 trigger let msg = customRef((track,trigger)=>{ return { //msg被读取的时候调用 get(){ track() return initvalue }, //msg被修改时调用 set(value){ celearTimeout(timer) timer = setTimeout({ initvlue = value trigger() },1000) } })