Vue3学习笔记

436 阅读11分钟

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插件设置

image.png

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})
  • 作用:监视数据的变化
  • 只能监视以下四种数据
    1. ref定义的数据
      • 情况一:监视ref定义的基本类型的数据
      • 情况二:监视ref定义的对象类型数据,监视的是对象的地址值,若想监视对象内部的数据,要手动开启深度监视。

    注意:

    • 如果修改的是ref定义的对象中的属性,newValue和oldValue都是新值,因为他们是同一个对象
    • 如果修改的是整个ref定义的对象,newValue是新值,oldValue是旧值,因为不是同一个对象了
    1. reactive定义的数据
      • 监视reactive定义的对象类型数据,且默认开启了深度监视。newVal和oldVal始终相同,因为对象地址值没变,都是一个对象。
      • 监视ref或reactive定义的对象类型数据中的某个属性,注意如下:
        1. 如果该属性不是对象类型,需要写成函数形式(一个getter函数,能返回一个值),例如只想监视person对象中的name基本属性
    image.png 2. 如果该属性值依然是对象类型,可直接编,也可写成函数,不过建议写成函数。写成函数后如果想监视这个对象的细枝末节,需要deep:true 3. 一个函数,返回一个值(见第2条#) 4. 一个包含上述内容的数组
    // 需求:监视人的名字和第一辆车
    watch([()=>person.name,()=>person.car.c1],(newVal,oldVale)=>{
        console.log('person改变了',newVal,oldVale);
    })
    

watchEffect

  • 立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数
  • watch对比watchEffect
    1. 都能监视响应式数据的变化,不同的是监听数据变化的方式不同
    2. watch:要明确指出监视的数据
    3. 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})暴露自己的数据

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的对应关系
  • 多个路由,需要经过路由器的管理
  • 路由的规则就是什么路径对应什么组件
  • 路由组件通常存放在pageviews文件夹,一般组件通常存放在components文件夹
  • 怎么区分路由组件和一般组件
    1. 路由组件是靠路由规则渲染出来的
    2. 一般组件是手动写的比如
  • 基本使用
    1. 安装:npm i vue-router
    2. 编写路由器
    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
    
    3.在main.js中挂载
    import router from './router/index'
    import { createApp } from 'vue'
    import App from './App.vue'
    
    const app = createApp(App)
    app.mount('#app')
    //挂载到app容器中
    app.use(router)
    
    1. 使用(注意: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
    <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>
    
    Detail.vue中,注意:想要获取route,需要通过hooks,即useRoute获取
    <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传参第二种方式
    <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>
    
    Detail.vue
    <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传参第一种方式,需要先在路由配置中占位
    {
        name: 'xinwen',
        path: '/news',
        component: News,
        children: [
            {
                path: 'detail/:id/:title/:content',
                component: Detail
            }
        ]
    },
    
    News.vue
    <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
    {
        name: 'xinwen',
        path: '/news',
        component: News,
        children: [
            {
                name:'xiangqing',
                path: 'detail/:id/:title/:content',
                component: Detail
            }
        ]
    },
    
    News.vue
    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
    import {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
                }
            }
        }
    })
    
    Count.vue
    <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

父子间通信

image.png

自定义事件

  • 用于子传父

image.png

mitt

  • 可以实现任意组件间通信
  • 安装:npm i mitt
  • 在util文件夹下新建emitter.js文件 emitter.js
import mitt from 'mitt'
export default mitt()

image.png

v-model

  • v-model 既能子传父,也能父传子

image.png

image.png 重命名modelValue

image.png

$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>

效果图

image.png

具名插槽

  • 所谓默认插槽也是有名字的,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,并对其依赖项跟踪和更新触发进行逻辑控制

image.png

全局API转移到应用对象

从 Vue. 变成了 app.

  • app.component
  • app.config
  • app.directive
  • app.mount
  • app.unmount
  • app.use