再学Vue3-进阶与总结

298 阅读9分钟

Options API的弊端

  • Options API将一个功能的数据、方法、计算属性分散在datamethodscomputed等配置项中,如果想新增或者修改一个需求,就需要分别对datamethodscomputed进行分别修改,不利于复用与维护。

Composition API的优势

可以用函数的方式,更加优雅的组织代码,让一个功能相关的代码更加有序集中地组织在一块区域。

拉开序幕的setup

  1. 理解:setup是Vue3新增的一个配置项,它的值是一个函数。
  2. setup是所有Composition API"表演的舞台"。
  3. 组件中所用到的:数据、方法等等,均要在setup中配置。

setup函数的两种返回值

  1. 如果返回一个对象,则对象的属性、方法在模板中均可以直接使用
  2. 如果返回一个渲染函数:则可以自定义渲染内容
<template>
    <div>
        姓名:{{ name }}
        年龄:{{ age }}
    </div>
</template>

<script>
import { h } from 'vue'
export default {
    setup() {
        // 1.返回一个对象
        return {
            name: '兰青',
            age: 23
        }
        // 2.返回一个渲染函数
        // return () => h('div', '兰艺')
    }
}
</script>
<style scoped></style>

注意事项

  1. 尽量不要与Vue2混用。
  • Vue2可以访问setuo函数中的属性和方法
  • setup不能访Vue2的配置
  • 如果有重名,setup优先

2.setup不能是一个async函数,因为返回值不再是return对象,而是promise,模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但是需要Suspense和异步组件的配合)

ref

<template>
    <div>
        姓名:{{ name }}
        年龄:{{ age }}
        <button @click="changeInfo">改变信息</button>
    </div>
</template>

<script>
import { ref } from 'vue'
export default {
    setup() {
        // 1.返回一个对象
        let name = ref('兰青')
        let age = ref(18)
        console.log('name', name);
        console.log('age', age);
        let changeInfo = () => {
            console.log('changeInfo');
            name.value += '1'
            age.value += 1
        }
        return {
            name,
            age,
            changeInfo
        }
    }
}
</script>
<style scoped></style>

输出结果 image.png

解读一下名词: RefImpl:Ref的意思是reference(引用) Impl的意思是Implement(实现),合起来为引用实现的实例对象(简称:引用对象)

ref总结

作用:定义一个响应式的数据

语法:const xxx=ref(initValue)

  • 创建一个包含响应式数据的引用对象(reference对象)
  • JS中操作数据:xxx.value
  • 模板中读取数据:不需要.value

备注:

  • 接收的数据可以是基本类型也可以是对象类型
  • 基本类型的数据:响应式依然是靠Object.defineProperty()中的getset完成的
  • 对象类型的数据:内部“求助”了Vue3.0中的一个新函数-reactive函数

reactive

作用:定义一个对象类型的响应式数据(基本类型不要使用它)

语法: const 代理对象=reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)

reactive定义的响应式数据是深层次

内部基于ES6的proxy实现,通过代理对象操作源对象内部数据进行操作

Vue3响应式

实现原理:

  • 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的增删改查
  • 通过Reflect(反射):对源对象的属性进行操作
<body>
    <script>
        let person = {
            name: '兰青',
            age: 23
        }
        //模拟Vue3响应式 
        let p = new Proxy(person, {
            // 查
            get(target, propName) {
                console.log(`读取了p上的${propName}`);
                return Reflect.get(target, propName)
            },
            // 增,改
            set(target, propName, value) {
                console.log(`修改了p上的${propName}修改值为${value}`);
                return Reflect.set(target, propName, value)
            },
            // 删
            deleteProperty(target, propName) {
                console.log(`删除了p上的${propName}`);
                return Reflect.deleteProperty(target, propName)
            }
        })
    </script>
</body>

reactive对比ref

1.从定义数据角度对比:

ref用来定义:基本数据类型

reactive用来定义:对象(或数组)类型数据

注意:ref也可以用来定义数组或者对象,但是其内部也是通过reactive转为代理对象

2.从原理角度对比: ref通过Object.defineProperty()getset来实现响应式(数据劫持)

reactive通过使用Proxy来实现响应式(数据劫持),并且通过Reflect操作源对象内部的数据。(Reflect相关内容不是很会

3.从使用角度对比: ref定义的数据:操作数据需要.value,读取数据时模板直接读取不需要.value

setup两个注意点

1.setup执行时机:在beforeCreate之前执行一次,thisundefined

2.setup的参数

props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性

context:上下文对象

  • attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs

  • slots:收到的插槽内容,相当于this.$slots

  • emit:分发自定义事件的函数,相当于this.$emit

计算属性

<template>
    <div>
        name:{{ name }}
        lastName:{{ lastName }}
        fullName:{{ fullName }}
        <button @click="add">+</button>
    </div>
</template>

<script>
import { ref, reactive, computed } from 'vue'
export default {
    props: ['msg', 'sex'],
    setup() {
        let name = ref('John');
        let lastName = ref('Doe');

        // 简写方式
        let fullName = computed(() => {
            return `${name.value} ${lastName.value}`;
        })

        //全写方式
        let fullName = computed({
            // get方法负责返回计算结果
            get() {
                return `${name.value} ${lastName.value}`;
            },
            // set方法则在外部试图更改计算属性时被调用,用于更新依赖的响应式状态
            set(newValue) {
                console.log('set 方法newValue', newValue);
                const [firstName, lastName] = newValue.split(' ');
                console.log('firstName', firstName);
                console.log('lastName', lastName);
                name.value = firstName;
                lastName.value = lastName;
            },
        });
        const add = () => {
            console.log('add');
            fullName.value = '111 11'
        }
        return {
            name,
            lastName,
            fullName,
            add
        };
    },
}
</script>
<style scoped></style>

watch属性

watch监听ref定义的数据

1.监听单个数据

2.监听多个数据

<template>
    <div>
        {{ a }} <br />
        {{ b }}
    </div>
    <button @click="add">+</button>
</template>

<script>
import { ref, watch } from 'vue'
export default {
    setup() {
        let a = ref(0)
        let b = ref(0)
        const add = () => {
            a.value++
            b.value++
        }
        // 监听一个数据
        // watch(a, (newValue) => {
        //     console.log('newValue', newValue); // 1
        // }, { immediate: true })
        // 监听多个数据
        watch([a, b], (newValue) => {
            console.log('newValue', newValue); // [1,1]
        }, { immediate: true })

        return {
            a,
            b,
            add
        };
    },
}
</script>
<style scoped></style>

watch监听reactive定义的数据(一堆坑)

坑1:watch监听reactive定义的数据,oldValue居然跟newValue同步了

<template>
    <div>
        {{ person.name }}
        {{ person.age }}
    </div>
    <button @click="handleChangeName">修改姓名</button>
    <button @click="handleChangeAge">增加年龄</button>
</template>

<script>
import { ref, watch, reactive } from 'vue'
export default {
    setup() {
        let person = reactive({
            name: '兰清',
            age: 23
        })
        let handleChangeName = () => {
            person.name += '1'
        }
        let handleChangeAge = () => {
            person.age += 1
        }

        watch(person, (newValue, oldValue) => {
            console.log('person变化了', newValue, oldValue); //oldValue居然跟newValue同步了
        })

        return {
            person,
            handleChangeName,
            handleChangeAge
        }
    },
}
</script>
<style scoped></style>

坑2:要想监听ref定义的对象数据,watch第一个参数需要填xxx.value,否则无法监听变化

坑3:ref定义的数据xxx.value可以监听到变化,但是oldValue依然跟newValue同步了,原因在于ref处理对象数组类型的数据时内部依然借助reactive函数,所以现象跟监听reactive函数定义的数据是一样的

<template>
    <div>
        {{ person.name }}
        {{ person.age }}
    </div>
    <button @click="handleChangeName">修改姓名</button>
    <button @click="handleChangeAge">增加年龄</button>
</template>

<script>
import { ref, watch, reactive } from 'vue'
export default {
    setup() {
        let person = ref({
            name: '兰清',
            age: 23
        })
        let handleChangeName = () => {
            person.value.name += '1'
        }
        let handleChangeAge = () => {
            person.value.age += 1
        }
        //  1.无法监听变化
        watch(person, (newValue, oldValue) => {
            console.log('person变化了', newValue, oldValue);
        })
        //  2.可以监听变化,但是
        watch(person, (newValue, oldValue) => {
            //可以监听到变化,但是oldValue依然跟newValue同步了,原因在于ref处理对象数组类型的数据时内部依然借助reactive函数,所以现象跟监听reactive函数定义的数据是一样的
            console.log('person变化了', newValue, oldValue);
        })
        return {
            person,
            handleChangeName,
            handleChangeAge
        }
    },
}
</script>
<style scoped></style>

虽然oldValue不怎么需要,假如真想获得一个对象oldValue怎么办?:将想获得oldValue的属性单独用ref定义监听

坑3:watch监听reactive定义的数据深层次变化时默认deep:true,并且以前deep:false无效的问题修复了

<template>
    <div>
        姓名:{{ person.name }}
        年龄:{{ person.age }}
        薪水:{{ person.job.j1.salary }}
    </div>
    <button @click="handleChangeName">修改姓名</button>
    <button @click="handleChangeAge">增加年龄</button>
    <button @click="person.job.j1.salary++">加钱!</button>
</template>

<script>
import { ref, watch, reactive } from 'vue'
export default {
    setup() {
        let person = reactive({
            name: '兰清',
            age: 23,
            job: {
                j1: { salary: 13 }
            }
        })
        let handleChangeName = () => {
            person.name += '1'
        }
        let handleChangeAge = () => {
            person.age += 1
        }
        // reactive定义的对象深层次变化时,可以直接监听到数据变化(可以理解为自动设置了deep:true)
        watch(person, (newValue, oldValue) => {
            console.log('newValue', newValue);
            console.log('oldValue', oldValue);
        }) //这个deep:false以前的版本设置了也没用依然会监听得到深层次的数据变化,但是现在vue3完善了,deep设置为false可以生效了
        return {
            person,
            handleChangeName,
            handleChangeAge
        }
    },
}
</script>
<style scoped></style>

坑4:监听reactive定义数据的某个属性

直接监听reactive数据的某一个属性会有警告且无效:

<template>
    <div>
        姓名:{{ person.name }}
        年龄:{{ person.age }}
        薪水:{{ person.job.j1.salary }}
    </div>
    <button @click="handleChangeName">修改姓名</button>
    <button @click="handleChangeAge">增加年龄</button>
    <button @click="person.job.j1.salary++">加钱!</button>
</template>

<script>
import { ref, watch, reactive } from 'vue'
export default {
    setup() {
        let person = reactive({
            name: '兰清',
            age: 23,
            job: {
                j1: { salary: 13 }
            }
        })
        let handleChangeName = () => {
            person.name += '1'
        }
        let handleChangeAge = () => {
            person.age += 1
        }
        // 这样直接监听person.name会有警告
        watch(person.name, (newValue) => {
            console.log('newValue', newValue);
        })
        return {
            person,
            handleChangeName,
            handleChangeAge
        }
    },
}
</script>
<style scoped></style>

警告内容: image.png

因为watch监听的数据源只有:

  1. Getter/effect function:一个返回值用于观察的函数。
  2. Ref:通过 ref() 创建的基本数据类型的响应式引用。
  3. Reactive object:通过 reactive() 创建的对象类型的响应式代理。
  4. Array:包含上述任意类型的一个数组,用于同时监听多个源。

所以需要修改数据源:

// 监听数据源改为一个函数可以监听成功
        watch(() => person.name, (newValue) => {
            console.log('newValue', newValue);
        })

注意监听这种普通类型的数据时可以拿到oldValue!!!!!

<template>
    <div>
        姓名:{{ person.name }}
        年龄:{{ person.age }}
        薪水:{{ person.job.j1.salary }}
    </div>
    <button @click="handleChangeName">修改姓名</button>
    <button @click="handleChangeAge">增加年龄</button>
    <button @click="person.job.j1.salary++">加钱!</button>
</template>

<script>
import { ref, watch, reactive } from 'vue'
export default {
    setup() {
        let person = reactive({
            name: '兰清',
            age: 23,
            job: {
                j1: { salary: 13 }
            }
        })
        let handleChangeName = () => {
            person.name += '1'
        }
        let handleChangeAge = () => {
            person.age += 1
        }
        watch(() => person.name, (newValue, oldValue) => {
            console.log(newValue, oldValue); //兰清1 兰清
        }, { deep: true })

        return {
            person,
            handleChangeName,
            handleChangeAge
        }
    },
}
</script>
<style scoped></style>

坑5 监视reactive所定义的一个响应式数据中的某些属性

// 监听reactive定义数据里的多个属性
        watch([() => person.name, () => person.age], (newValue) => {
            console.log('newValue', newValue);
        })

坑6 监听reactive定义数据的深层次结构属性

<template>
    <div>
        姓名:{{ person.name }}
        年龄:{{ person.age }}
        薪水:{{ person.job.j1.salary }}
    </div>
    <button @click="handleChangeName">修改姓名</button>
    <button @click="handleChangeAge">增加年龄</button>
    <button @click="person.job.j1.salary++">加钱!</button>
</template>

<script>
import { ref, watch, reactive } from 'vue'
export default {
    setup() {
        let person = reactive({
            name: '兰清',
            age: 23,
            job: {
                j1: { salary: 13 }
            }
        })
        let handleChangeName = () => {
            person.name += '1'
        }
        let handleChangeAge = () => {
            person.age += 1
        }
        watch(() => person.job, (newValue) => {
            console.log('newValue', newValue);
        }, { deep: true }) //监听深层结构的属性需要加deep:true,跟直接监听整个reactive不一样了!!!
        return {
            person,
            handleChangeName,
            handleChangeAge
        }
    },
}
</script>
<style scoped></style>

总结

最后总结一下关于监听reactive定义的数据时oldValue的坑:只要监听是对象数组类型oldValue拿不到,普通类型可以拿到

监听ref所定义的对象数据的问题

直接监听失效

<template>
    <div>
        姓名:{{ person.name }}
        年龄:{{ person.age }}
        薪水:{{ person.job.j1.salary }}
        爱好:{{ person.habit }}
    </div>
    <button @click="person.name += '1'">修改姓名</button>
    <button @click="person.age++">增加年龄</button>
    <button @click="person.job.j1.salary++">加钱!</button>
    <button @click="person.habit.push(1)">改变爱好</button>
</template>

<script>
import { ref, watch, reactive } from 'vue'
export default {
    setup() {
        let person = ref({
            name: '兰清',
            age: 23,
            job: {
                j1: { salary: 13 }
            },
            habit: []
        })
        console.log('person', person);
        // 无法触发监听,因为person本质上是一个RefImpl实例,我更改的数据实际上是更改这个RefImpl里面的.value
        // 这个.value本质上是一个借助Reactive生成的Proxy对象,所以要想监听成功除非让这个代理对象整个内存地址发生变化
        watch(person, (newValue, oldValue) => {
            console.log('newValue', newValue);
            console.log('oldValue', oldValue);
        })

        return {
            person,
        }
    },
}
</script>
<style scoped></style>

解决办法1:监听.value 其本质是监听一个reactive定义的数据

watch(person.value, (newValue, oldValue) => {
            console.log('newValue', newValue);
            console.log('oldValue', oldValue);
        })

解决办法2:设置deep:true

watch(person, (newValue, oldValue) => {
            console.log('newValue', newValue);
            console.log('oldValue', oldValue);
        }, { deep: true })

watchEffect函数

1.watch的套路是:既要指明监视的属性,也要指明监视的回调

2.watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性

3.watchEffect有点像computed(可以跟面试官吹牛)。但是computed注重的是计算出来的值(回调函数的返回值),所以必须要写返回值。watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值

4.watchEffect组件一加载就会触发一次回调

<template>
    <div>
        姓名:{{ person.name }}
        年龄:{{ person.age }}
        薪水:{{ person.job.j1.salary }}
        爱好:{{ person.habit }}
        数字:{{ num }}
    </div>
    <button @click="person.name += '1'">修改姓名</button>
    <button @click="person.age++">增加年龄</button>
    <button @click="person.job.j1.salary++">加钱!</button>
    <button @click="person.habit.push(1)">改变爱好</button>
    <button @click="num++">改变数字</button>
</template>

<script>
import { ref, watch, reactive, watchEffect } from 'vue'
export default {
    setup() {
        let num = ref(0)
        let person = reactive({
            name: '兰清',
            age: 23,
            job: {
                j1: { salary: 13 }
            },
            habit: []
        })
        console.log('person', person);
        // 1.一上来就执行一次回调
        // 2.watchEffect函数体里用到了哪个响应式数据,就会监听此数据
        watchEffect(() => {
            let a = person.job.j1.salary //能够监听person.job.j1.salary
            let b = num.value
            console.log('watchEffect回调执行了');
        })
        return {
            person,
            num
        }
    },
}
</script>
<style scoped></style>

watch与watchEffect的区别(面试题,4点)

watchwatchEffect 都是监听器,watchEffect 是一个副作用函数。它们之间的区别有:

  • watch :既要指明监视的数据源,也要指明监视的回调。

  • watchEffect 可以自动监听数据源作为依赖。不用指明监视哪个数据,监视的回调中用到哪个数据,那就监视哪个数据。

  • watch 可以访问改变之前和之后的值,watchEffect 只能获取改变后的值。

  • watch 运行的时候不会立即执行,值改变后才会执行,而 watchEffect 运行后可立即执行。这一点可以通过 watch 的配置项 immediate 改变。

  • watchEffect有点像 computed

    • computed 注重的计算出来的值(回调函数的返回值), 所以必须要写返回值。
    • watcheffect注重的是过程(回调函数的函数体),所以不用写返回值

生命周期

Options API的生命周期:

  1. beforeCreate: 在实例初始化之后、数据观测(initState)和 event/watcher 事件配置之前被调用。 对于此时做的事情,如注册组件使用到的store或者service等单例的全局物件。 相比Vue2没有变化。
  2. created: 一个新的 Vue 实例被创建后(包括组件实例),立即调用此函数。 在这里做一下初始的数据处理、异步请求等操作,当组件完成创建时就能展示这些数据。 相比Vue2没有变化。
  3. beforeMount: 在挂载之前调用,相关的render函数首次被调用,在这里可以访问根节点,在执行mounted钩子前,dom渲染成功,相对Vue2改动不明显。
  4. onMounted: 在挂载后调用,也就是所有相关的DOM都已入图,有了相关的DOM环境,可以在这里执行节点的DOM操作。在这之前执行beforeUpdate。
  5. beforeUpdate: 在数据更新时同时在虚拟DOM重新渲染和打补丁之前调用。我们可以在这里访问先前的状态和dom,如果我们想要在更新之前保存状态的快照,这个钩子非常有用。相比Vue2改动不明显。
  6. onUpdated:在数据更新完毕后,虚拟DOM重新渲染和打补丁也完成了,DOM已经更新完毕。这个钩子函数调用时,组件DOM已经被更新,可以执行操作,触发组件动画等操作
  7. beforeUnmount:在卸载组件之前调用。在这里执行清除操作,如清除定时器、解绑全局事件等。
  8. onUnmounted:在卸载组件之后调用,调用时,组件的DOM结构已经被拆卸,可以释放组件用过的资源等操作。
  • onActivated – 被 keep-alive 缓存的组件激活时调用。
  • onDeactivated – 被 keep-alive 缓存的组件停用时调用。
  • onErrorCaptured – 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

Composition API的生命周期:

除了beforecatecreated(它们被setup方法本身所取代),我们可以在setup方法中访问的上面后面9个生命钩子选项:

自定义hook函数

  • 什么是hook?--本质上是一个函数,把setup函数中使用的Composition API进行了封装。
  • 类似于Vue2的mixin
  • 自定义hook的优势:复用代码,让setup中的逻辑更加清楚易懂

案例:

usePoint.js

import { reactive, onMounted, onBeforeUnmount } from 'vue'
export function getMousePoint() {
    let point = reactive({
        x: 0,
        y: 0
    })
    function savePoint(event) {
        point.x = event.pageX
        point.y = event.pageY
    }

    onMounted(() => {
        window.addEventListener('click', savePoint)
    })

    onBeforeUnmount(() => {
        window.removeEventListener('click', savePoint)
    })
    return point
}

Demo.vue

<template>
    <div>鼠标点击的坐标:x:{{ point.x }} y:{{ point.y }}</div>
</template>

<script>
import { getMousePoint } from '../hooks/usePoint.js'
export default {
    setup() {
        let point = getMousePoint()

        return {
            point
        }
    },
}
</script>
<style scoped></style>

toRef和toRef(很有用)

案例:当我面对模板上需要显示很多对象的属性时,本来代码是这么写的

<template>
    <div>
        姓名:{{ person.name }}
        年龄:{{ person.age }}
        薪资:{{ person.job.j1.salary }}
        <br />
        <button @click="person.name += 1">改变姓名</button>
        <button @click="person.age += 1">改变年龄</button>
        <button @click="person.job.j1.salary += 1">加钱</button>
    </div>
</template>

<script>
import { reactive } from 'vue'
export default {
    setup() {
        let person = reactive({
            name: '兰清',
            age: 23,
            job: {
                j1: {
                    salary: 13
                }
            }
        })

        return {
            person
        }
    },
}
</script>
<style scoped></style>

但是我不想写那么一大串所以自己尝试进行了所谓的“优化”

<template>
    <div>
        姓名:{{ name }}
        年龄:{{ age }}
        薪资:{{ salary }}
        <br />
        <button @click="name += 1">改变姓名</button>
        <button @click="age += 1">改变年龄</button>
        <button @click="salary += 1">加钱</button>
    </div>
</template>

<script>
import { reactive } from 'vue'
export default {
    setup() {
        let person = reactive({
            name: '兰清',
            age: 23,
            job: {
                j1: {
                    salary: 13
                }
            }
        })

        return {
            name: person.name,
            age: person.age,
            salary: person.job.j1.salary
        }
    },
}
</script>
<style scoped></style>

这么写,我发现无法触发响应式了!

模拟一下响应式过程:

let name=p.name 只是单纯创建一个字符串变量无法触发响应式

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        let person = {
            name: '兰清',
            age: 23
        }
        let p = new Proxy(person, {
            set(target, propName, value) {
                console.log(`${propName}被修改了,我要去更新界面了`);
                Reflect.set(target, propName, value)
            }
        })
        let name = p.name //name是自己新定义的变量,单纯的一个字符串变量,已经失去了响应式效果
    </script>
</body>

</html>

所以这种情况就得使用toRef了 语法:

const name = toRef(person, 'name') //name2是个ref对象
console.log('name', name); //name2是个ref对象

toRef创建的数据其内存指向概念图,其本质依然是指向person.name image.png

用toRef改造代码:响应式实现,并且person对象里的数据会同步

<template>
    <div>
        {{ person }}
        姓名:{{ name }}
        年龄:{{ age }}
        薪资:{{ salary }}
        <br />
        <button @click="name += 1">改变姓名</button>
        <button @click="age += 1">改变年龄</button>
        <button @click="salary += 1">加钱</button>
    </div>
</template>

<script>
import { reactive, toRef } from 'vue'
export default {
    setup() {
        let person = reactive({
            name: '兰清',
            age: 23,
            job: {
                j1: {
                    salary: 13
                }
            }
        })
        return {
            name: toRef(person, 'name'), //响应式实现,并且person对象里的数据会同步
            age: toRef(person, 'age'),
            salary: toRef(person.job.j1, 'salary'),
            person
        }
    },
}
</script>
<style scoped></style>

注意:一眼看过去我觉得ref也能实现toRef的功能,但是用Ref创建的是一个独立的Ref对象,所以无法做到与person同步

<template>
    <div>
        {{ person }}
        <br />
        姓名:{{ name }}
        年龄:{{ age }}
        薪资:{{ salary }}
        <br />
        <button @click="name += 1">改变姓名</button>
        <button @click="age += 1">改变年龄</button>
        <button @click="salary += 1">加钱</button>
    </div>
</template>

<script>
import { ref, reactive, toRef } from 'vue'
export default {
    setup() {
        let person = reactive({
            name: '兰清',
            age: 23,
            job: {
                j1: {
                    salary: 13
                }
            }
        })
        return {
            name: ref(person.name), //响应式实现,并且person对象里的数据会同步
            age: ref(person.age),
            salary: ref(person.job.j1.salary),
            person
        }
    },
}
</script>
<style scoped></style>

toRefs

 const p = toRefs(person)
 console.log('p', p);

输出结果

image.png

利用toRefs完善:

<template>
    <div>
        {{ person }}
        <br />
        姓名:{{ name }}
        年龄:{{ age }}
        薪资:{{ job.j1.salary }}
        <br />
        <button @click="name += 1">改变姓名</button>
        <button @click="age += 1">改变年龄</button>
        <button @click="job.j1.salary += 1">加钱</button>
    </div>
</template>

<script>
import { ref, reactive, toRef, toRefs } from 'vue'
export default {
    setup() {
        let person = reactive({
-             name: '兰清',
            age: 23,
            job: {
                j1: {
                    salary: 13
                }
            }
        })
        return {
            ...toRefs(person), //toRefs返回一个对象,可以利用...将其展开方便在模板使用
            person
        }
    },
}
</script>
<style scoped></style>

总结

toRef与toRefs

  • 作用:创建一个ref对象,其value值指向另一个对象中的某个属性
  • 应用:要将响应式对象中的某个属性单独提供给外部使用时

shallowReactive与shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)
  • shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理

什么时候用?

  • 如果有一个对象数据,结构比较深,但变化时只是外层属性变化===>shallowReactive
  • 如果有一个对象数据,确定不会修改对象的属性,而是生成新的对象来替换===>shallowRef

readonly与shallowReadonly

  • readonly:接收一个ref对象、reactive对象、普通对象,让一个响应式数据变为只读的(深只读)
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)
  • 应用场景:不希望数据被修改时

toRaw与markRaw

toRaw:

  • 作用:将一个由reactive生成的响应式对象转为普通对象
  • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新

markRaw:

  • 作用:标记一个对象,使其永远不会再成为响应式对象
  • 应用场景:1.有些值不应被设置为响应式的,例如复杂的第三方库等 2.当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

customRef

作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制。

案例:

<template>
    <div>
        <input type="text" v-model="keyWord" />
        <h3>{{ keyWord }}</h3>
    </div>
</template>

<script>
import { ref, customRef } from 'vue'
export default {
    setup() {
        // 自定义一个ref--名为myRef
        function myRef(value, delay) {
            let timer
            return customRef((track, trigger) => {
                return {
                    get() {
                        console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`);
                        track()//通知vue追踪数据的变化(提前和get商量一下,让他认为这个value是有用的)
                        return value
                    },
                    set(newValue) {
                        console.log(`有人把myRef这个容器中数据改为了,${newValue}`);
                        clearTimeout(timer)
                        timer = setTimeout(() => {
                            value = newValue
                            trigger()//通知vue去重新解析模板
                        }, delay)
                    }
                }
            })
        }

        let keyWord = myRef('hello', 500)//实现myRef防抖维护数据
        return {
            keyWord
        }
    },
}
</script>
<style scoped></style>

provide与inject

作用:实现祖孙通信

套路:父组件有一个provide来提供数据,子组件有一个inject来使用这些数据

具体写法:

祖先组件

provide('car',car)

孙组件

const car=inject('car')

响应式数据的判断

  • isRef:检查一个值是否是一个ref对象
  • isReactive:检查一个对象是否是由reactive创建的响应式对象
  • isReadonly:检查一个对象是否是由readonly创建的只读代理对象
  • isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理

新的组件

Fragment

  • 在Vue2中:组件必须有一个根标签
  • 在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处:减少标签层级,减少内存占用

Teleport

背景:比如深层次结构时,一个在底层组件的弹窗会受到其许多上级组件的影响,比如样式 image.png

使用teleport组件做完善

Dialog.vue

<template>
    <div>
        <button @click="handleShowDialog">点击弹窗</button>
        <teleport to="body">
            <div class="dialog" v-if="isShow">
                <h3>我是一个弹窗</h3>
                <h4>一些内容</h4>
                <h4>一些内容</h4>
                <h4>一些内容</h4>
                <button @click="handleCloseDialog">关闭弹窗</button>
            </div>
        </teleport>
    </div>
</template>

<script>
import { ref } from 'vue'
export default {
    name: 'Dialog',
    setup() {
        let isShow = ref(false)
        let handleShowDialog = () => {
            isShow.value = true
        }
        let handleCloseDialog = () => {
            isShow.value = false
        }
        return {
            isShow,
            handleShowDialog,
            handleCloseDialog
        }
    }
}
</script>
<style scoped>
.dialog {
    width: 300px;
    height: 300px;
    background-color: green;
}
</style>

to 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body 标签下”。

会发现Dialog组件中被teleport组件包裹的部分被传送到body标签下了,这使得弹窗样式摆脱父级元素的影响,可以直接参考body标签样式。

image.png

Suspense

Suspense本质是用一个slot写的

背景:异步加载一个组件,会让App组件先显示,再显示Son组件,这会使页面比较抖动。

<template>
    <div class="app">
        <h3>我是APP组件</h3>
        <Son />
    </div>

</template>

<script>
// import Son from './components/Son.vue' 静态引入,等加载最慢的组件渲染成功,才显示整个页面
import { defineAsyncComponent } from 'vue';
const Son = defineAsyncComponent(() => import('./components/Son.vue')) //动态引入
export default {
    components: { Son },
    setup() {

    },
}
</script>
<style scoped>
.app {
    background-color: gray;
    padding: 10px;
}
</style>

Suspense组件与动态引入可以让setup返回一个Promise

<template>
    <div class="son">
        <h4>我是Son组件</h4>
        {{ num }}
    </div>
</template>

<script>
import { ref } from 'vue'
export default {
    async setup() {
        let sum = ref(0)
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve({ sum })
            }, 3000)
        })
        return await p
    },
}
</script>
<style scoped>
.son {
    background-color: skyblue;
    padding: 10px;
}
</style>

总结

作用:等待异步组件时渲染一些额外内容,让应用有更好的用户体验

使用步骤:

  • 异步引入组件
  • 使用Suspense包裹组件,并配置好default与fallback

image.png

其他改变