深入理解Vue:探究组合式API的奥秘(续集)

34 阅读5分钟

自定义组件使用 v-model

封装input 组件实现双向数据绑定

    // 父组件
    <template>
        <h1>App 组件 ---- {{ value }}</h1>
        <children v-model:value="value" />
    </template>

    <script setup>
    import children from './components/children.vue'
    import { ref } from 'vue'

     const value = ref('张三')
    </script>
    
    // 子组件
    <template>
        <input type="text" :value="value" @input="changeEl">
    </template>

    <script setup>
        import { defineProps, defineEmits } from 'vue'
        defineProps(['value'])

        const emit = defineEmits(['update:value'])

        function changeEl(e) {
            emit('update:value', e.target.value)
        }
    </script>

计算属性(computed)

在Vue 3的组合式API中,你可以使用computed函数来创建计算属性。computed函数接受一个函数作为参数,该函数返回计算属性的值。计算属性可以依赖于响应式数据,并且只有在依赖的响应式数据发生变化时才会重新计算。

    <template>
        <h1>APP 组件</h1>
        <hr />
        <h1>原价: {{ price }} <button @click="changePrice">修改原价</button></h1>
        <h1>会员价: {{ vipPrice }} <button @click="changeVipPrice">修改会员价</button></h1>
        <h1>超级会员价: {{ sVipPrice }}</h1>
    </template>

    <script setup>
    import { ref, computed } from "vue";

    const price = ref(1000);

    // 最常见的 计算属性写法, 但是这种写法 不允许修改 计算属性的值
    // const vipPrice = computed(() => {
    //     return price.value / 2;
    // });
    const vipPrice = computed({
        get () {
            return price.value / 2
        },
        set (val) {
            // console.log('你想修改 计算属性, 修改的值: ', val)
            price.value = val * 2
        }
    })

    const sVipPrice = computed(() => {
        return price.value * 0.1;
    });

    // 修改原价
    function changePrice() {
        price.value = 100;
    }

    // 修改 vip 价格
    function changeVipPrice () {
        // console.log(vipPrice)
        vipPrice.value = 1000
    }
    </script>

侦听器(watch)

在Vue 3的组合式API中,watch是一个函数,用于监视数据的变化并执行相应的操作。它可以替代Vue 2中的this.$watch方法。

watch函数接受两个参数:要监视的数据源和回调函数。当数据源发生变化时,回调函数将被触发。此外,还可以传递一些选项参数,如deep用于深度监视对象内部的变化,immediate用于立即执行回调函数等。

在使用watch时,可以监视响应式数据、计算属性或ref对象的变化,以及在监视回调函数中执行相应的逻辑,比如发送请求、更新UI等操作。

    <template>
        <h1>APP 组件</h1>
        <hr />
        <h1>原价: {{ price }} <button @click="price = 500">修改原价</button></h1>
        <h1>会员价: {{ vipPrice }}</h1>

        <button @click="appName = '李四'">修改名字: {{ appName }}</button>
        <hr />
        <ul>
            <li>id: {{ obj.id }}</li>
            <li>age: {{ obj.age }} <button @click="obj.age++">增加年龄</button></li>
        </ul>
    </template>

    <script setup>
    import { ref, watch, reactive } from "vue";

    const price = ref(1000);
    const vipPrice = ref(0);

    const appName = ref("张三");

    const obj = reactive({
        id: "QF001",
        age: 18,
    });

    // watch('侦听的值', '一个回调函数, 会在侦听的值发生变化的时候执行', '一个对象, 对当前侦听器的一些配置项')
    // watch(price, () => {
    //     // console.log('此时 price 发生变化')
    //     vipPrice.value = price.value / 2
    // }, { immediate: true })

    /**
     *  选项式开发中, 侦听器本身只能侦听一个值, 如果需要侦听多个值, 我们需要很多额外的操作
     *      1. 在计算属性中, 返回一个对象, 对象内部是你需要监听的多个值
     *      2. 在侦听器中添加一个对这个计算属性的监听, 并且需要开启 深层监听
     *
     *  但是组合式开发中, 如果 watch 需要侦听多个值, 我们只需要将第一个参数更换为一个数组
     *      数组内, 书写你需要侦听的 数据
     */
    watch([price, appName], () => {
        console.log("当前侦听器会在 price/appName 的值发生变化的时候执行");

        vipPrice.value = price.value / 2;
    });

    watch(obj, () => {
        console.log("当前obj侦听器执行~~");
    });
    </script>


watchEffter

    <template>
        <h1>APP 组件</h1>
        <hr />
        <h1>原价: {{ price }} <button @click="price = 500">修改原价</button></h1>
        <h1>会员价: {{ vipPrice }}</h1>
    </template>

    <script setup>
    import { ref, watch, watchEffect } from "vue";

    const price = ref(1000);
    const vipPrice = ref(0);


    // 当前侦听器会自动追踪内部书写的响应式数据(不包含赋值号左边的), 并且页面打开的时候会立即执行
    watchEffect(() => {
        vipPrice.value = price.value / 2;
    })

    // 侦听 price 响应式对象, 会在 页面一打开的时候和 price 发生变化的时候执行
    // watch(
    //     price,
    //     () => {
    //         console.log("当前侦听器会在 price/appName 的值发生变化的时候执行");
    //         vipPrice.value = price.value / 2;
    //     },
    //     {
    //         immediate: true,
    //     }
    // );
    </script>

组合式API 的生命周期

    <template>
        <div></div>
    </template>
    <script setup>
    import {
        onBeforeMount,
        onMounted,
        onBeforeUpdate,
        onUpdated,
        onBeforeUnmount,
        onUnmounted,
    } from "vue";

    /**
     * 在挂载开始之前被调用:相关的 render 函数首次被调用。
     */
    onBeforeMount(() => {});

    /**
     * 组件挂载时调用
     */
    onMounted(() => {});

    /**
     *  数据更新时调用,发生在虚拟 DOM 打补丁之前。
     * 这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
     */
    onBeforeUpdate(() => {});

    /**
     * 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子
     */
    onUpdated(() => {});

    /**
     * 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的
     */
    onBeforeUnmount(() => {});

    /**
     *  卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,
     * 所有事件侦听器都被移除,所有子组件实例被卸载
     */
    onUnmounted(() => {});
    </script>

组合式API 的路由

基本使用

    <template>
        <div>
            <div>我是第一个页面</div>
            <button @click="link2">跳转到第二个页面</button>
        </div>
    </template>
    <script setup>
    // 从vue-router引入 useRouter这个钩子
    import { useRouter } from "vue-router";

    // 初始化这个钩子并赋值给router
    const router = useRouter();

    // 跳转到页面2
    const link2 = () => {
        // 使用我们刚声明的router跳转
        router.push("/v11");
    };

页面传参

传值方式和 vue2.x 的路由一样,也是 query 和 params,但是接收参数的形式有所不同

    // 传参页
    <template>
        <div>
            <div>我是第一个页面</div>
            <button @click="link2">跳转到第二个页面</button>
        </div>
    </template>
    <script setup>
    // 从vue-router引入 useRouter这个钩子
    import { useRouter } from "vue-router";

    // 初始化这个钩子并赋值给router
    const router = useRouter();

    // 跳转到页面2
    const link2 = () => {
        // 跳转你的时候传一个id为1
        router.push("/v11?id=1");
    };
    </script>
    // 接收页
    <template>
        <div>我是第二个页面</div>
    </template>
    <script setup>
    import { useRoute } from "vue-router";

    // 初始化useRoute并赋值给route
    const route = useRoute();

    // 获取query的参数
    console.log(route.query);
    </script>

组合式 API 获取 元素

    <template>
        <!-- 2. 通过 ref 属性, 将刚才创建的响应式数据绑定给 某一个标签/组件  
        (绑定的时候不需要添加 冒号) -->
        <h1 ref="myh1">我是首页的内容</h1>

        <!--
            组合式 API 中 获取 标签/组件
                1. 通过 ref 方法 创建一个响应式数据
                2. 通过 ref 属性, 将刚才创建的响应式数据绑定给 某一个标签/组件 
                (绑定的时候不需要添加 冒号)
                3. 在某一个合适的时机, 你需要访问标签的时候, 可以直接通过 响应式数据访问到
                标签
        -->
        <button @click="getH1">获取标签</button>
    </template>

    <script setup>
    import { ref } from 'vue'

    // 1. 通过 ref 方法 创建一个响应式数据
    const myh1 = ref(null)

    function getH1() {
        // 3. 在某一个合适的时机, 你需要访问标签的时候, 可以直接通过 响应式数据访问到标签
        console.log('获取 h1 标签: ', myh1.value)
    }
    </script>