Vue3 Composition API

Composition API可以更方便的抽取共通逻辑,但是不要过于在意逻辑代码复用,以功能提取代码也是一种思路。

setup

setup是组合Composition API中的入口函数,也是第一个要使用的函数。

setup只在初始化时执行一次,所有的Composition API函数都在此使用。

setup是在beforeCreate生命周期之前执行的,由此可以推断出setup执行的时候,组件对象还没有创建,组件实例对象this还不可用,此时thisundefined,不能通过this来访问data/computed/methods/props

返回对象中的属性会与data函数返回对象的属性合并成为组建对象的属性,返回对象中的方法会与methods中的方法合并成为组件对象的方法,如果有重名,setup优先。因为在setupthis不可用,methods中可以访问setup提供的属性和方法,但在setup方法中不能访问datamethods里面的内容。

注意
setup不能是一个async函数,因为async返回值是promise,不再是return的对象,模板中就不可以使用return中返回对象的数据了。

setup的参数(props,context)

props:是一个对象,里面有父级组件向子级组件传递的数据,并且是在子级组件中使用props接收到的所有的属性。

context:上下文对象,可以通过es6语法解构setup(props,{attrs,slots,emit})

  • attrs:获取当前组件标签上所有没有通过props接收的属性的对象,相当于this.$attrs
  • slots:包含所有传入的插槽内容的对象,相当于this.$slots
  • emit:用来分发自定义事件的函数,相当于this.$emit

演示propsattrs

//父组件
<template>
    <h2>父组件</h2>
    <Child :msg="msg" msg2="lalala" />
</template>

<script lang="ts">
    import { defineComponent, ref } from 'vue';
    import Child from './Child.vue';

    export default defineComponent({
        components: {
            Child,
        },
        setup() {
            const msg = ref('hahaha');
            return { msg };
        },
    });
</script>

//子组件
<template>
    <h2>子组件</h2>
    <h3>msg:{{ msg }}</h3>
</template>

<script lang="ts">
    import { defineComponent } from 'vue';

    export default defineComponent({
        props: {
            msg: {
                type: String,
                default: '',
            },
        },
        setup(props, { attrs }) {
            console.log('props', props); //props Proxy {msg: 'hahaha'}
            console.log('attrs', attrs); //attrs Proxy {msg2: 'lalala'}
            return {};
        },
    });
</script>

演示emit

//父组件
<template>
    <h2>父组件</h2>
    <Child @show="show" />
</template>

<script lang="ts">
    import { defineComponent } from 'vue';
    import Child from './Child.vue';

    export default defineComponent({
        components: {
            Child,
        },
        setup() {
            const show = (data) => {
                console.log(data);
            };
            return { show };
        },
    });
</script>

//子组件
<template>
    <h2>子组件</h2>
    <n-button @click="emitFn">事件分发</n-button>
</template>

<script lang="ts">
    import { defineComponent } from 'vue';
    export default defineComponent({
        emits: ['show'],
        setup(props, { emit }) {
            const emitFn = () => {
                emit('show', '我是子组件传过来的值');
            };

            return { emitFn };
        },
    });
</script>

ref

作用
定义一个响应式的数据(一般用来定义一个基本类型的响应式数据UndefinedNullBooleanNumberString

Vue2中我们通过this.$refs来获取dom节点,Vue3中我们通过ref来获取节点

首先需要在标签上添加ref='xxx',然后在setup中定义一个初始值为nullref类型,名字要和标签的ref属性一致。

代码演示

<template> <n-input type="text" ref="inputRef" /> </template>

<script lang="ts">
    import { defineComponent, onMounted, ref } from 'vue';

    export default defineComponent({
        setup() {
            const inputRef = ref<HTMLElement | null>(null);
            onMounted(() => {
                inputRef.value && inputRef.value.focus();
            });
            return { inputRef };
        },
    });
</script>

reactive

作用

定义多个数据的响应式,接收一个普通对象然后返回该普通对象的响应式代理对象(proxy),响应式转换是“深层的”:会影响对象内部所有嵌套的属性,所有的数据都是响应式的。

<template>
    <h3>姓名:{{ user.name }}</h3>
    <h3>年龄:{{ user.age }}</h3>
    <h3>wife:{{ user.wife }}</h3>
    <n-button @click="updateUser">更新</n-button>
</template>

<script lang="ts">
    import { defineComponent, reactive } from 'vue';

    export default defineComponent({
        setup() {
            const user = reactive({
                name: 'hw',
                age: 20,
                wife: {
                    name: 'cl',
                    age: 18,
                    books: ['小王子', '一生一世美人骨', '杨绛传'],
                },
            });
            const updateUser = () => {
                user.name = 'xxx';
                user.age += 2;
                user.wife.books[0] = '了不起的盖茨比';
            };
            return {
                user,
                updateUser,
            };
        },
    });
</script>

computed

<template>
    <div> </div>
</template>

<script lang="ts">
    import { defineComponent, computed, reactive } from 'vue';

    export default defineComponent({
        setup() {
            const user = reactive({
                firstName: 'cl',
                lastName: 'chelsy',
            });
            //计算属性的函数中如果只传入一个回调函数,表示的是get操作
            const fullName = computed(() => {
                return user.firstName + user.lastName;
            });
            //计算属性的函数中可以传入一个对象,可以包含set和get函数,进行读取和修改的操作
            const fullName2 = computed({
                get() {
                    return user.firstName + '_' + user.lastName;
                },
                set(val: string) {
                    const name = val.split('_');
                    user.firstName = name[0];
                    user.lastName = name[1];
                },
            });
            return { user, fullName, fullName2 };
        },
    });
</script>

watch

watch的参数:

  • 要监听的数据
  • 回调函数
  • 配置

作用

监听指定的一个或多个响应式数据,一旦数据发生变化,就自动执行监听回调。默认初始时不执行回调,但可以通过配置immediatetrue,来指定初始时立即执行一次,通过配置deeptrue,来指定深度监听。

侦听来源类型
watch的第一个参数可以是不同形式的来源:它可以是一个ref(包括计算属性)、一个响应式对象、一个getter函数、或多个来源组成的数组:

<template>
    <div> </div>
</template>

<script lang="ts">
    import { defineComponent, reactive, ref, watch } from 'vue';

    export default defineComponent({
        setup() {
            const x = ref(0);
            const y = ref(0);
            //单个ref
            watch(x, (newX) => {
                console.log(`x is ${newX}`);
            });

            //getter函数
            watch(
                () => x.value + y.value,
                (sum) => {
                    console.log(`sum of x + y is: ${sum}`);
                }
            );
            //多个来源组成的数组
            watch([x, () => y.value], (newX, newY) => {
                console.log(`x is ${newX} and y is ${newY}`);
            });

            //注意,不能侦听响应式对象的 property,例如:

            const obj = reactive({
                count: 0,
            });

            /* watch(obj.count, (count) => {
                    console.log(`count is: ${count}`);
                }); */
            //而是用 getter 函数:
            watch(
                () => obj.count,
                (count) => {
                    console.log(`count is: ${count}`);
                }
            );

            return {};
        },
    });
</script>

watchEffect

watch()是懒执行的:仅在侦听源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。举个例子,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。我们可以这样写:

import { defineComponent, ref, watch } from 'vue';

export default defineComponent({
    setup() {
        const url = ref('https://xxx');
        const data = ref(null);
        async function fetchData() {
            const res = await fetch(url.value);
            data.value = await res.json();
        }
        fetchData();
        watch(url, fetchData);
        return {};
    },
});

watchEffect会立即执行一遍回调函数,如果这时函数产生了副作用,Vue会自动追踪副作用的依赖关系,自动分析响应源。上面的例子可以重写为:

<script lang="ts">
    import { defineComponent, ref, watchEffect } from 'vue';

    export default defineComponent({
        setup() {
            const url = ref('https://xxx');
            const data = ref(null);
            async function fetchData() {
                const res = await fetch(url.value);
                data.value = await res.json();
            }
            /* fetchData();
            watch(url, fetchData); */
            watchEffect(async () => {
                const res = await fetch(url.value);
                data.value = await res.json();
            });
            return {};
        },
    });
</script>

这个例子中,回调会立即执行。在执行期间,它会自动追踪url.value作为依赖(近似于计算属性)。每当url.value变化时,回调会再次执行。

watch vs. watchEffect

watchwatchEffect都能响应式地执行有副作用的回调。他们之间的主要区别是追踪响应式依赖的方式:

  • watch只追踪明确侦听的源。它不会追踪任何在回调中访问到的东西。另外,仅在响应源确实改变时才会触发回调。watch会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
  • watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式property。这更方便,而且代码往往更简洁,但其响应性依赖关系不那么明确。

生命周期对比

在这里插入图片描述

注意:3.0中的生命周期钩子要比2.x中相同生命周期的钩子要快

toRefs

作用

把一个响应式对象转换成普通对象,该普通对象的每个属性都是一个ref

应用

我们使用reactive创建的对象,如果想在模板中使用,就必须得使用xxx.xxx的形式,如果大量用到的话还是很麻烦的,但是使用es6解构以后,会失去响应式,那么toRefs的作用就体现在这,利用toRefs可以将一个响应式 reactive 对象的所有原始属性转换为响应式的ref属性。

<script lang="ts">
    import { defineComponent, reactive, toRefs } from 'vue';

    export default defineComponent({
        setup() {
            const state = reactive({
                name: 'cl',
            });
            const state2 = toRefs(state);
            setInterval(() => {
                state.name += '===';
            }, 1000);
            return {
                // ...state, //cl
                //通过toRefs返回的对象,解构出来的属性也是响应式的
                ...state2, //cl====...
            };
        },
    });
</script>

provide 与 inject

作用

实现跨层组件(祖孙)间通信

代码演示

//父组件
<template>
    <n-h1>父组件</n-h1>
    <p>当前颜色: {{ color }}</p>
    <n-button @click="color = 'red'"></n-button>
    <n-button @click="color = 'yellow'"></n-button>
    <n-button @click="color = 'blue'"></n-button>
    <Son />
</template>

<script lang="ts">
    import { ref, provide } from 'vue';
    import Son from './Son.vue';

    export default {
        components: { Son },
        setup() {
            const color = ref('red');
            provide('color', color);
            return {
                color,
            };
        },
    };
</script>
//子组件
<template>
    <n-h2>子组件</n-h2>
    <GrandSon />
</template>

<script lang="ts">
    import GrandSon from './GrandSon.vue';

    export default {
        components: { GrandSon },
        setup() {
            return {};
        },
    };
</script>
//孙子组件
<template>
    <n-h3 :style="{ color }">孙子组件: {{ color }}</n-h3>
</template>

<script lang="ts">
    import { inject } from 'vue';
    export default {
        setup() {
            const color = inject('color');
            return {
                color,
            };
        },
    };
</script>

跨组件通讯mitt.js

vue2中怎么实现跨组件通讯呢?很多人第一想法就是event bus。但是vue3移除了$on$once$off导致不能使用这个方法。但是vue官方给大家推荐了mitt.js,它的原理就是event bus

代码演示

  1. 安装:npm i mitt -s
  2. 封装一个hook
import mitt from 'mitt'
const emitter = mitt();

export default emitter;
//父组件
<template>
    <n-h2>父组件</n-h2>
    <Child1 />
    <Child2 />
</template>

<script lang="ts" setup>
    import Child1 from './Child1.vue';
    import Child2 from './Child2.vue';
</script>
//子组件1
<template>
    我是子组件1
    <n-h1>{{ msg }}</n-h1>
</template>

<script lang="ts">
    import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
    import emitter from './mitt';

    export default defineComponent({
        setup() {
            //初始化
            const msg = ref('hello');
            const changeMsg = () => {
                msg.value = 'world';
            };
            //监听事件,更新数据
            onMounted(() => {
                emitter.on('change-msg', changeMsg);
            });
            onUnmounted(() => {
                emitter.off('change-msg', changeMsg);
            });
            return { msg, changeMsg };
        },
    });
</script>
//子组件2
<template>
    我是子组件2
    <n-button @click="changeMsg">点击修改msg</n-button>
</template>

<script lang="ts">
    import { defineComponent } from 'vue';
    import emitter from './mitt.js';

    export default defineComponent({
        setup() {
            const changeMsg = () => {
                emitter.emit('change-msg');
            };
            return { changeMsg };
        },
    });
</script>

script setup语法糖

Vue3官方提供了script setup语法糖,只需要在script标签中添加setup,组件只需引入不用注册,属性和方法也不用返回,setup函数也不需要,甚至export default都不用写了,不仅是数据,计算属性和方法,甚至是自定义指令也可以在我们的template中自动获得。

setup script语法糖提供了五个新的API来供我们使用:

  • defineProps() & defineEmits()
<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup code
</script>
  • defineExpose()
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>
  • useSlots() & useAttrs()
<script setup>
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>

例子:

<template>
    <n-h2>父组件{{ son }}</n-h2>
    <Child ref="son" />
</template>

<script lang="ts" setup>
    import { ref } from 'vue';
    import Child from './Child.vue';
    const son = ref(null);
</script>

//子组件
<template> 子组件{{ msg }} </template>

<script lang="ts" setup>
    // import { defineComponent, ref } from 'vue';

    // export default defineComponent({
    //     setup() {
    //         const msg = ref('hello');
    //         return {
    //             msg,
    //         };
    //     },
    // });

    import { ref, defineExpose } from 'vue';
    const msg = ref('hello');
    defineExpose({
        msg,
    });
</script>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值