vue3学习笔记
【尚硅谷Vue3入门到实战,最新版vue3+TypeScript前端开发教程】 www.bilibili.com/video/BV1Za…
tips
-
解构赋值
let {data:{content:title}} = ....将解构出来的data中的content重命名为title
-
vscode 多行注释:Alt + Shift + A
ref和reactive
- ref用来定义:基本、对象
- reactive 用来定义:对象
- ref创建的变量必须使用 .value(可以使用volar插件自动添加.value)
- reactive重新分配一个新对象,会失去响应式(可以使用Object.assign)去整体替换
- 使用原则
- 基本类型的响应式数据必须使用ref
- 响应式对象的层级不深,ref和reactive都可以
- 需要一个层级较深的响应式对象,推荐使用reactive
- volar插件设置
toRefs
- toRefs在下面的代码中的作用是,将响应式对象person中的每一组键值对取出来变成组合成一个新的对象,这个对象中的每一个元素都是Ref类型的响应式对象,并且依然保持着响应式的能力,修改name和age的时候,person中的name和age也会变。
- 而toRef和toRefs功能相似,只不过一次只能取出响应式对象中的一个元素:
let nl = toRef(person,'age')
let person = reactive({
name:'张三',
age:13
})
//解构赋值,修改新的name和age`无法`对person对象的属性产生影响
let {name,age} = person
//应该使用toRefs
let {name,age} = toRefs(person)
计算属性
let firstName = ref('tom')
let lastName = ref('jerry')
//只读的计算属性(常用)
let fullName = computed(()=>{
return firstName.value + '-' + lastName.value
})
//可读可写的计算属性
let fullName = computed({
get(){
return firstName.value + '-' + lastName.value
},
set(newVal){
//数组也是可以解构赋值的
const [str1,str2] = newVal.split('-')
firstName.value = str1
lastName.value = str2
}
})
监视属性
- 语法
watch(监视谁,(newVal,oldVal)=>{
//......
//watch的第三个参数是配置对象,其中 immediate:true 表示初始时就立即监视
},{deep:true,immediate:true})
- 作用:监视数据的变化
- 只能监视以下四种数据
- ref定义的数据
- 情况一:监视ref定义的基本类型的数据
- 情况二:监视ref定义的
对象类型数据,监视的是对象的地址值,若想监视对象内部的数据,要手动开启深度监视。
注意:
- 如果修改的是ref定义的对象中的属性,newValue和oldValue都是新值,因为他们是同一个对象
- 如果修改的是整个ref定义的对象,newValue是新值,oldValue是旧值,因为不是同一个对象了
- reactive定义的数据
- 监视reactive定义的对象类型数据,且默认开启了深度监视。newVal和oldVal始终相同,因为对象地址值没变,都是一个对象。
- 监视ref或reactive定义的对象类型数据中的某个属性,注意如下:
- 如果该属性不是对象类型,需要写成函数形式(
一个getter函数,能返回一个值),例如只想监视person对象中的name基本属性
- 如果该属性不是对象类型,需要写成函数形式(
2. 如果该属性值依然是对象类型,可直接编,也可写成函数,不过建议写成函数。写成函数后如果想监视这个对象的细枝末节,需要deep:true 3. 一个函数,返回一个值(见第2条#) 4. 一个包含上述内容的数组
// 需求:监视人的名字和第一辆车 watch([()=>person.name,()=>person.car.c1],(newVal,oldVale)=>{ console.log('person改变了',newVal,oldVale); }) - ref定义的数据
watchEffect
- 立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数
- watch对比watchEffect
- 都能监视响应式数据的变化,不同的是监听数据变化的方式不同
- watch:要明确指出监视的数据
- watchEffect:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)
- 代码示例---需求:当水温达到60度或水位达到80cm时,给服务器发送请求
let temp = ref(0)
let height = ref(0)
//watch实现
watch([temp,height],(newVal)=>{
let [newTemp,newHeight] = newVal
if(newTemp >= 60 || newHeight >= 80){
console.log('发送请求');
}
})
//watchEffect实现
watchEffect(()=>{
if(temp.value >= 60 || height.value >= 80 ){
console.log('发送请求');
}
})
ref
- 作用:用于注册模板引用
- 用在普通DOM标签上,获取的是DOM节点
- 用在组件标签上,获取的是组件实例对象
- 例如在App组件的子组件Person标签上加了ref,此时App无法获取子组件Person上的数据,需要在Person中使用
defineExpose({xxx,xxx,xxx})暴露自己的数据
- 例如在App组件的子组件Person标签上加了ref,此时App无法获取子组件Person上的数据,需要在Person中使用
ts语法
types/index.ts 文件
/**
* Person接口
* x 可有可无
*/
export interface PersonInter{
id:string,
name:string,
age:number,
x?:number
}
/**
* 一个自定义类型
*/
// export type PersonList = Array<PersonInter>
//或
export type PersonList = PersonInter[]
view/home/homIndex.vue文件
import type { PersonInter,PersonList } from "@/types";
let person: PersonInter = {id:'wef23',name:'tom',age:13}
let personList: PersonList = [
{id:'wef23',name:'tom',age:13},
{id:'we3',name:'tom',age:13},
{id:'we',name:'tom',age:13},
]
- 推荐使用泛型的方式
let personList: reactive<PersonList> = [
{id:'wef23',name:'tom',age:13},
{id:'we3',name:'tom',age:13},
{id:'we',name:'tom',age:13},
]
defineProps
在父组件中
<template>
<Person a="hello" :list="PersonList" />
</template>
子组件接收父组件传来的数据
import {defineProps,withDefaults} from 'vue'
import type {PersonList} from '@/types'
//只接收list
defineProps(['list'])
//只接收list并保存props
let x = defineProps(['list'])
//只接收list并限制类型
defineProps<{list:PersonList}>()
//只接收list + 限制类型 + 限制必要性
defineProps<{list?:PersonList}>()
//只接收list + 限制类型 + 限制必要性 + 指定默认值
withDefaults(defineProps<{list?:PersonList}>(),{
list:()=>[{id:'001',name:'jerry',age:23}]
})
- 注意:以
define开头的宏函数一般不需要引入,比如上面的defineProps、defineExpose
vue3生命周期
- 创建阶段:setup
- 挂载阶段:onBeforeMount、onMounted
- 更新阶段:onBeforeUpdate、onUpdated
- 销毁阶段:onBeforeUnmount、onUnmounted
hooks
- 将不同功能的数据和方法写在一起很混乱,使用hooks进行模块化开发
- 新建hooks文件夹,在其中新建不同的useXxx文件,比如useOrder.js
- 举例,useDog.js,需要注意的几点
- 同一个功能点的数据+方法写在一个hooks文件中
- 数据和方法要包裹在一个方法中,最后使用
return向外提供 - 使用默认暴露的方式,可以不起方法名,如下代码
import { reactive,onMounted } from 'vue'
import axios from 'axios';
export default function () {
let dogList = reactive([
"https://images.dog.ceo/breeds/pembroke/n02113023_1151.jpg"
])
//方法
async function getDog() {
try {
let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
console.log(result.data.message);
dogList.push(result.data.message)
} catch (error) {
alert(error)
}
}
onMounted(()=>{
getDog()
})
//向外部提供东西
return {dogList,getDog}
}
- 在需要使用的组件中使用如下
<template>
<img v-for="(dog, index) in dogList" :src="dog" :key="index">
<button @click="getDog">再来一只狗</button>
</template>
<script setup lang="ts" name="Person">
import useDog from '@/hooks/useDog'
const {dogList,getDog} = useDog()
</script>
路由
- 路由就是一组key-value的对应关系
- 多个路由,需要经过
路由器的管理 - 路由的规则就是什么路径对应什么组件
- 路由组件通常存放在
page或views文件夹,一般组件通常存放在components文件夹 - 怎么区分路由组件和一般组件
- 路由组件是靠路由规则渲染出来的
- 一般组件是手动写的比如
- 基本使用
- 安装:
npm i vue-router - 编写路由器
3.在main.js中挂载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 routerimport router from './router/index' import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.mount('#app') //挂载到app容器中 app.use(router)- 使用(注意:active-class表示超链接被激活时的css样式)
<script setup name="App"> </script> <template> <h2>路由测试</h2> <!-- 导航区 --> <div class="navigate"> <router-link to="/home" active-class="nav-item">首页</router-link> <router-link to="/news" active-class="nav-item">新闻</router-link> <router-link :to="{path:'/about'}" active-class="nav-item">关于</router-link> </div> <!-- 展示区 --> <div class="main-content"> <router-view></router-view> </div> </template> - 安装:
- 路由器工作模式
- history模式
优点:url美观,不带# 缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404错误
- hash模式
优点:兼容性更好,因为不需要服务端配合处理路径问题 缺点:url带有#,不美观,且在seo优化方面相对较差
- 路由跳转时传参
- query传参第一种方式 News.vue
Detail.vue中,注意:想要获取route,需要通过hooks,即useRoute获取<template> <div> <ul> <li v-for="news in newsList" :key="news.id"> <router-link :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`">{{ news.title }}</router-link> </li> </ul> </div> <!-- 展示区 --> <div class="news-content"> <router-view></router-view> </div> </template> <script setup name="News"> import {reactive} from 'vue' const newsList = reactive([ {id:'322323',title:'十种好吃的食物',content:'十种好吃的食物adsfjasdkljglkdsa;jgkdsa'}, {id:'765478',title:'发家致富的途径',content:'发家致富的途径asdfdasgasdfdsafdasf'}, {id:'952524',title:'周末的打工人',content:'周末的打工人adsfadsfasdfasdfds'}, ]) </script><template> <div> <ul> <li>编号:{{ route.query.id }}</li> <li>标题:{{ route.query.title }}</li> <li>内容:{{ route.query.content }}</li> </ul> </div> </template> <script setup name="Detail"> import {useRoute} from 'vue-router' const route = useRoute() console.log(route); </script>- query传参第二种方式
Detail.vue<ul> <li v-for="news in newsList" :key="news.id"> <router-link :to="{ path:'/news/detail', query:{ id:news.id, title:news.title, content:news.content } }"> {{ news.title }} </router-link> </li> </ul><template> <div> <ul> <li>编号:{{ query.id }}</li> <li>标题:{{ query.title }}</li> <li>内容:{{ query.content }}</li> </ul> </div> </template> <script setup name="Detail"> import {useRoute} from 'vue-router' import {toRefs} from 'vue' const route = useRoute() // 解构赋值会失去响应式,需要使用toRefs let {query} = toRefs(route) </script>- params传参第一种方式,需要先在路由配置中占位
News.vue{ name: 'xinwen', path: '/news', component: News, children: [ { path: 'detail/:id/:title/:content', component: Detail } ] },<div> <ul> <li v-for="news in newsList" :key="news.id"> <router-link :to="`/news/detail/${news.id}/${news.title}/${news.content}`">{{ news.title }}</router-link> </li> </ul> </div>- params传参第二种方式,to的对象式写法,需要先在路由配置中占位,且需要使用路由的name,且params传参不支持对象和数组 路由配置name
News.vue{ name: 'xinwen', path: '/news', component: News, children: [ { name:'xiangqing', path: 'detail/:id/:title/:content', component: Detail } ] },div> <ul> <li v-for="news in newsList" :key="news.id"> <router-link :to=" { name:'xiangqing', params:{ id:news.id, title:news.title, content:news.content } } "> {{ news.title }} </router-link> </li> </ul> </div>
路由规则的props配置
- 作用:让
路由组件更方便收到参数(可以将路由参数作为props传给组件) - props与params结合使用
路由配置文件
{
name: 'xinwen',
path: '/news',
component: News,
children: [
{
name: 'xiangqing',
path: 'detail/:id/:title/:content',
component: Detail,
//第一种写法:将路由收到的所有params参数作为props传给路由组件
props: true
}
]
},
Detail.vue
<template>
<div>
<ul>
<li>编号:{{ id }}</li>
<li>标题:{{ title }}</li>
<li>内容:{{ content }}</li>
</ul>
</div>
</template>
<script setup name="Detail">
defineProps(['id','title','content'])
</script>
- props与query结合使用 路由配置文件
{
name: 'xinwen',
path: '/news',
component: News,
children: [
{
name: 'xiangqing',
path:'detail',
component: Detail,
//第二种写法,函数写法:自己决定将什么作为props给路由组件
props(route){
return route.query
}
}
]
},
push和replace
- 控制路由跳转时操作浏览器历史记录的模式,默认为push
- push是追加历史记录,replace是替换当前记录
- 开启replace模式:
<router-link replace to="/home">首页</router-link>
编程式路由导航
- 路由组件的两个重要属性:
$route和$router变成了两个hooks - 使用
<ul>
<li v-for="news in newsList" :key="news.id">
<button @click="showNewsDetail(news)">查看新闻</button>
<router-link :to="{
name: 'xiangqing',
query: {
id: news.id,
title: news.title,
content: news.content
}
}
">
{{ news.title }}
</router-link>
</li>
</ul>
<script setup name="News">
import { reactive } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const newsList = reactive([
//略
])
function showNewsDetail(news) {
//push和to的写法相似,都可以写 字符串或对象。使用replace模式只需要 router.replace()
router.push({
name: 'xiangqing',
query: {
id: news.id,
title: news.title,
content: news.content
}
})
}
</script>
路由重定向
- 比如访问项目根路径的时候,重定向到'/home' 路由配置文件中,和一级路由同级
{
path:'/',
redirect:'/home'
}
状态管理工具 Pinia
- 安装
npm i pinia - 引入
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')
- 在src下新建 store/组件名.js,例如LoveTalk.js
import { defineStore } from 'pinia'
//语法:export const useXxxStore = defineStore(id,配置项)
export const useTalkStore = defineStore('talk', {
state() {
return {
talkList: [
{ id: '24335', title: '今天你怪可爱的' },
{ id: '94545', title: '爱你死心塌地' },
{ id: '19082', title: '看到你就晴天了' },
]
}
}
})
在LoveTalk.vue中获取pinia中存储的数据
<template>
<div>
<button @click="getLoveTalk">获取一句情话</button>
<ul>
<li v-for="talk in talkStore.talkList" :key="talk.id">{{ talk.title }}</li>
</ul>
</div>
</template>
<script setup name="LoveTalk">
import { reactive } from "vue";
import axios from "axios";
//npm i nanoid
import {nanoid} from 'nanoid'
import {useTalkStore} from '../store/loveTalk'
const talkStore = useTalkStore()
async function getLoveTalk(){
let {data} = await axios.get('https://qinghua.haom.ren/api.php')
//把请求回来的字符串包装成对象
let obj = {id:nanoid(),title:data}
console.log(obj);
talkList.unshift(obj)
}
</script>
- 修改数据的三种方式
- 方式一:直接修改
countStore.sum = 666- 方式二:批量修改
countStore.$patch({ sum:999, school:'qinghua' })- 方式三:借助action修改(action中可以写一些业务逻辑) count.js
Count.vueimport {defineStore} from 'pinia' //语法:export const useXxxStore = defineStore(id,配置项) export const useCountStore = defineStore('count',{ state(){ return{ sum:6, school:'清华' } }, //actions里面放置的是一个一个的方法,用于响应组件中的”动作“ actions:{ increment(value){ // console.log('increment被调用了',value); //修改数据 // console.log(this.sum); if(this.sum < 10){ this.sum += value } } } })<script setup name="Count"> import {ref} from 'vue' import {useCountStore} from '../store/count' const countStore = useCountStore() //reactive中定义的ref数据可以直接拿,不需要.value console.log(countStore.sum); // 用户选择的数字 let n = ref(1) function add() { countStore.increment(n.value) } function sub() { } </script>
storeToRefs
- 从pinia获取数据后,想要解构赋值出来,方便使用,例如Count.vue中
import {useCountStore} from '../store/count'
import {storeToRefs} from 'pinia'
const countStore = useCountStore()
// const {sum,school} = toRefs(countStore) //不建议使用toRefs,其所包含的对象太多,增大了开销
//storeToRefs只会关注store中的数据,不会对方法进行 ref包裹
const {sum,school} = storeToRefs(countStore)
getters
- 当state中的数据,需要经过处理后再使用时,可以使用getters配置
getters:{
// bigSum(){
// return this.sum * 10
// }
//或
bigSum(state){
return state.sum * 10
}
}
$subscribe
- 通过store的$subscribe()方法侦听state及其变化
- 应用场景举例:每次数据变化,就将数据存到localStorage中
/**
talkStore.$subscribe((本次修改的信息,数据)=>{
})
*/
talkStore.$subscribe((mutate,state)=>{
console.log('talkStore里的数据发生了变化',mutate,state);
localStorage.setItem('talkList',JSON.stringify(state.talkList))
})
loveTalk.js中取出数据,注意:如果本地没有数据,列表为null会报错,因此使用 || []
state() {
return {
talkList: JSON.parse(localStorage.getItem('talkList')) || []
}
},
store的组合式写法
- 注意:最后需要 return
import { defineStore } from 'pinia'
import axios from "axios";
import {nanoid} from 'nanoid'
import {reactive} from 'vue'
export const useTalkStore = defineStore('talk',()=>{
const talkList = reactive(JSON.parse(localStorage.getItem('talkList')) || [])
async function getATalk() {
let { data } = await axios.get('https://qinghua.haom.ren/api.php')
//把请求回来的字符串包装成对象 npm i nanoid
let obj = { id: nanoid(), title: data }
talkList.unshift(obj)
}
return {talkList,getATalk}
})
组件通信
props
父子间通信
自定义事件
- 用于子传父
mitt
- 可以实现任意组件间通信
- 安装:
npm i mitt - 在util文件夹下新建
emitter.js文件 emitter.js
import mitt from 'mitt'
export default mitt()
v-model
- v-model 既能子传父,也能父传子
重命名modelValue
$attrs
- $attrs 用于实现当前组件的父组件向当前组件的子组件通信(祖到孙)
- $attrs是一个对象,包含所有父组件传入的标签属性
- $attrs会自动排除props中声明的属性(可以认为声明过的props被子组件自己消费了)
爷爷组件
<template>
<div class="home">
Home
<hr>
{{ name }}---{{ age }}
<Child :age :name :address="{province:'山东',city:'青岛'}" :toChangeAge="changeAge"></Child>
</div>
</template>
<script setup name="Home">
import Child from "./Child.vue";
import {ref} from 'vue'
//数据
let name = ref('张三')
let age = ref(35)
//方法
function changeAge(val){
age.value += val
}
</script>
父组件
<template>
<div class="child">
子组件
<GranChild v-bind="$attrs"></GranChild>
</div>
</template>
<script setup name="Child">
import GranChild from './GranChild.vue';
</script>
孙组件
<template>
<div class="granChild">
孙子
<hr>
{{ name }}---{{ age }}
<br>
{{ address }}
<button @click="addAge">点我改变年龄+10</button>
</div>
</template>
<script setup name="GranChild">
const props = defineProps(['name','age','toChangeAge','address'])
function addAge(){
props.toChangeAge(10)
}
</script>
$refs和$parent
$refs用于父->子,$parent用于子->父- 子或父需要使用
defineExpose向外暴露自己的数据或方法
| 属性 | 说明 |
|---|---|
| $refs | 值为对象,包含所有被ref属性标识的DOM元素或组件实例 |
| $parent | 值为对象,当前组件的父组件实例对象 |
provide&inject
- 适用于祖先和后代之间通信,可以跨级通信,不需要像$attrs那样祖孙通信中间要经过父组件
祖先
<template>
<div class="home">
Home
<hr>
<h4>祖先自己的数据:{{ disable }}---{{ data }}</h4>
<Child></Child>
</div>
</template>
<script setup name="Home">
import Child from "./Child.vue";
import {provide, ref} from 'vue'
//数据
let disable = ref(false)
let data = ref({
name:'jerry',
age:2
})
//方法
function changeDisable(bool){
disable.value = bool
}
//将数据和方法暴露给后代(注意,ref类型的数据不要.value,否则传给后代的就不是响应式数据了)
provide('disableContext',{disable,changeDisable})
provide('data',data)
</script>
父
<template>
<div class="child">
子组件
<GranChild></GranChild>
</div>
</template>
<script setup name="Child">
import GranChild from './GranChild.vue';
</script>
孙子
<template>
<div class="granChild">
孙子
<hr>
<h4>接收祖先的数据:{{ disableContext.disable }}</h4>
<h4>接收祖先的数据:{{ data }}</h4>
<button @click="editDisable">点我修改祖先的disable为true</button>
</div>
</template>
<script setup name="GranChild">
import { inject } from 'vue';
let disableContext = inject('disableContext')
let data = inject('data',{name:'默认值',age:0})
//方法
function editDisable(){
disableContext.changeDisable(true)
}
</script>
插槽
默认插槽
Home.vue
<template>
<div class="home">
<Category title="列表">
<ul>
<li v-for="todo in todoList" :key="todo.id">{{ todo.content }}</li>
</ul>
</Category>
<Category title="图片">
<img :src="imgUrl" alt="">
</Category>
<Category title="文字">
<span>{{ textContent }}</span>
</Category>
</div>
</template>
<script setup name="Home">
import Category from "./Category.vue";
import {ref} from 'vue'
//数据
let todoList = ref([
{id:1,content:'起床'},
{id:2,content:'刷牙'},
{id:3,content:'吃早饭'}
])
let imgUrl = ref('https://img.zcool.cn/community/01df7b56de44db6ac72531cb2906b9.JPG@1280w_1l_2o_100sh.jpg')
let textContent = ref('今天周四了')
</script>
Category.vue
<template>
<div class="main">
<h2>{{ title }}</h2>
<slot></slot>
</div>
</template>
<script setup name="Category">
defineProps(['title'])
</script>
效果图
具名插槽
- 所谓默认插槽也是有名字的,name为default,不过一般省略不写
- 具名插槽有两种语法:
v-slot:xxx或#xxx - 并非先传入插槽的展示在前,而是根据插槽的先后顺序排列
- 包裹插槽具体内容的
template标签,在传入插槽时会自动去除 - 代码示例如下
<template>
<div class="home">
<Category>
<template v-slot:header>
<h4>列表</h4>
</template>
<template v-slot:content>
<ul>
<li v-for="todo in todoList" :key="todo.id">{{ todo.content }}</li>
</ul>
</template>
</Category>
<Category>
<template #header>
<h4>图片</h4>
</template>
<template #content>
<img :src="imgUrl" alt="">
</template>
</Category>
<Category>
<template #header>
<h4>文字</h4>
</template>
<template #content>
<span>{{ textContent }}</span>
</template>
</Category>
</div>
</template>
<script setup name="Home">
import Category from "./Category.vue";
import { ref } from 'vue'
//数据
let todoList = ref([
{ id: 1, content: '起床' },
{ id: 2, content: '刷牙' },
{ id: 3, content: '吃早饭' }
])
let imgUrl = ref('https://img.zcool.cn/community/01df7b56de44db6ac72531cb2906b9.JPG@1280w_1l_2o_100sh.jpg')
let textContent = ref('今天周四了')
</script>
<template>
<div class="main">
<slot name="header"></slot>
<slot name="content"></slot>
</div>
</template>
<script setup name="Category">
</script>
作用域插槽
核心:数据在子组件中,但是根据数据生成什么样的解构由父组件决定- 语法:
v-slot="传过来的数据对象"v-slot="{对传过来的对象解构}v-slot:default="obj"(v-slot后跟 :插槽名 ,默认为default)#插槽名="传来的对象"
- 代码示例
<template>
<div class="home">
<Category>
<template v-slot:header>
<h4>列表1</h4>
</template>
<template v-slot:content="{todo}">
<ul>
<li v-for="todo in todo" :key="todo.id">{{ todo.content }}</li>
</ul>
</template>
</Category>
<Category>
<template v-slot:header>
<h4>列表2</h4>
</template>
<template #content="{todo}">
<ol>
<li v-for="todo in todo" :key="todo.id">{{ todo.content }}</li>
</ol>
</template>
</Category>
<Category>
<template v-slot:header>
<h4>列表3</h4>
</template>
<template v-slot:content="{todo}">
<ul>
<h5 v-for="todo in todo" :key="todo.id">{{ todo.content }}</h5>
</ul>
</template>
</Category>
</div>
</template>
<script setup name="Home">
import Category from "./Category.vue";
</script>
<template>
<div class="main">
<slot name="header"></slot>
<slot name="content" :todo="todoList"></slot>
</div>
</template>
<script setup name="Category">
import { ref } from 'vue'
//数据
let todoList = ref([
{ id: 1, content: '起床' },
{ id: 2, content: '刷牙' },
{ id: 3, content: '吃早饭' }
])
</script>
其他
toRaw与markRaw
- toRaw 作用:用于获取一个响应式对象的原始对象,toRaw返回的对象不再是响应式的,不会触发视图更新。
- markRaw 作用:标记一个对象,使其永远不会变成响应式的。
自定义ref : customRef
- 作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制
全局API转移到应用对象
从 Vue. 变成了 app.
- app.component
- app.config
- app.directive
- app.mount
- app.unmount
- app.use