正在进入ing...

Vue3.2+setup 组件传值备忘

发布时间:2022-12-03 浏览量: 744 文章分类: 前端相关

在Vue3.2中,增加了很方便、简洁的语法糖setup,不过我也一直没仔细用过,每次都是网上查一下就直接上了。趁着有时间,好好研究一下。

对比那些我就不说了,网上大把。我直接放个简单对比就好。

  • Beforer

原来的写法需要先export default暴漏,并在里面定义setup,在需要通过return方法返回才可以。    

<script lang="ts">
import { ref } from "vue";
export default {
    setup() {
        const count = ref(1);
        return {
            count,
        };
    },
};
</script>
  • After

现在的方法可以理解为简化了方法,变的更简洁易懂了。

```vue

```

没错,按照上面的方法就可以了,瞬间爽了很多,代码也更清晰了。但是组件之间传值的方式也改变了。这里也就引入今天的正题,我自己也当一波备忘。

结构定义

为了方便记录,也不搞的非常复杂,所以我统一一下。 **后面的传递规范都是以这个为基准

App.vue是父组件、./components/children.vue是子组件。**

defineProps父传子

  • Before
// App.vue
<script setup lang="ts">
import { ref } from "vue";
import Children from "@/components/children.vue";
</script>

<template>
    <Children />
</template>
// Children.vue

<script lang="ts">
export default {
    props: {
        msg: {
            type: String,
            required: true,
            default: "Hello",
        },
    },
};
</script>

<template>
    <h1>{{ msg }}</h1>
</template>

这个是老方法

  • After

现在在用加了setup语法糖的方法来对 Children.vuescript进行改造。

普通声明

App.vue不变

// Children.vue

<script setup lang="ts">
defineProps({
    msg: {
        type: String,
        required: true,
        default: "Hello",
    },
});
</script>

<template>
    <h1>{{ msg }}</h1>
</template>

可以看到defineProps也不需要引入,直接就可以使用了。

TS类型声明

App.vue不变

// Children.vue


<script setup lang="ts">
const props = withDefaults(
    defineProps<{
        msg: string;
    }>(),
    {
        msg: "hello",
    }
);
</script>

<template>
    <h1>{{ msg }}</h1>
</template>

上面的代码看着其实更复杂,感觉也更乱了。这里补充说明一下,其实你如果不需要设定默认值什么的,只要像下面这样写也是可以的。

defineProps<{
    msg: string;
}>();

但是这样就没办法对类型进行限制和要求,所以我理解就是官方有增加了一个"补丁"withDefaults(同样不需要引用),在传递2个参数,第一个就是defineProps,第二个就是类型限制规则。

defineEmits 子传父

接下来先对组件的内容都进行一些改造。让App.vue只要展示子组件,然后在App.vue里面暴漏一个方法给到Children.vue,并能让Children.vue传递数据给到App.vue,并展示。

普通声明

// App.vue

<script setup lang="ts">
import Children from "@/components/Children.vue";
// 定义一个父组件点击事件,接受一个参数,然后在页面弹窗
const fatherClick = (msg: string) => {
    alert(msg);
};
</script>
<template>
    // 页面什么都不展示,只展示子组件,并通过@parentClick传递父组件的方法fatherClick给到子组件
    <Children @parentClick="fatherClick" />
</template>
// Children.vue


<script setup lang="ts">
// 定义一个emits方法来接收父组件传递的方法,也就是parentClick
const emits = defineEmits(["parentClick"]);

const clickEmits = () => {
    // 调用上面的emits 传来的方法 parentClick,并传递字符串给父组件
    emits("parentClick", "我是子组件传的");
};
</script>

<template>
    <button @click="clickEmits">子传父</button>
</template>

TS声明

App.vue不变

// Children.vue

<script setup lang="ts">
const emits = defineEmits<{
    (evevtName: "parentClick", dataMsg: string): void;
}>();

const clickEmits = () => {
    emits("parentClick", "123");
};
</script>

<template>
    <button @click="clickEmits">子传父</button>
</template>

仔细看上面的代码,也只是对defineEmits进行了变化,通过<>进行了类型验证类型中他接收两个参数一个是事件名称,另外就是事件的类型,后面的void代表组件返回为空。

useSlots&useAttrs

这玩意是插槽,我其实用的不多(其实也就是element-plus的时候有的需要用,别的地方我都没用过),不过既然都琢磨了,就一起看了。

子组件引用父组件插槽 (useSlots)

// App.vue

<script setup lang="ts">
import Children from "@/components/Children.vue";
</script>
<template>
    <Children>
        <template #header> 我是父组件插槽 </template>
    </Children>
</template>
// Children.vue

<script setup lang="ts">
import { useSlots } from "vue";
const slots = useSlots();
</script>

<template>
    <h1>子组件应用父组件的插槽</h1>
    <slot name="header" />
</template>

App.vue中,在Children标签中,定义了一个template,并使用#xxx来给插槽命名。

Children.vue中,先引入了useSlots方法,然后在通过<slot name=父组件插槽名>来进行使用。

这里还有一个问题,就是我在ts里面明明定义的是slots,但是在模板中用的就是slot,的原因是如果在children.vue中打印slots就能看见,他吧父组件的插槽挂载在他下面了,所以页面的slot+名称才会可以使用的。所以简单理解也就是可以获取插槽,方便页面的其他方法使用。

父组件传递给子组件属性(useAttrs)props除外

// App.vue


<script setup lang="ts">
import Children from "@/components/Children.vue";
</script>
<template>
    // 注意这里,在引入子组件的时候,传递了不同类型的几个参数
    <Children a="1" b="2" msg="hello" class="toChild" style="color: red">
        <template #header>
            <h1>我是父组件插槽</h1>
        </template>
    </Children>
</template>
// Children.vue


<script setup lang="ts">
import { onMounted, useSlots, useAttrs } from "vue";
const slots = useSlots();
// 引入了useAttrs
const attrs = useAttrs();

onMounted(() => {
    // 打印接收到的参数
    console.log(attrs);
    -->{
    "a": "1",
    "b": "2",
    "msg": "hello",
    "class": "toChild",
    "style": {
        "color": "red"
    }
}
});
</script>

<template>
    <h1>子组件应用父组件的插槽</h1>
    <slot name="header" />
</template>

这里可以关注 useAttrs,先从vue引入,在实例化。然后在onMounted中打印了一下,我看到了传递过来的几个参数,都能接收到了。

但是这里也要注意一下,他是除了props的

App.vue不变

// Children.vue


<script setup lang="ts">
import { onMounted, useSlots, useAttrs } from "vue";
const slots = useSlots();
const attrs = useAttrs();
// 定义一个props接收一个msg参数
defineProps({ msg: String });

onMounted(() => {
    // 因为我们App.vue不变,本身里面传递的一个参数msg和props的参数msg重复。所以下面输出就没有msg了
    console.log(attrs);
    -->{
    "a": "1",
    "b": "2",
    "class": "toChild",
    "style": {
        "color": "red"
    }
}

});
</script>

defineExpose

Vue3.xsetup语法糖中定义的变量默认是不会暴漏出去的,如果需要暴漏组件内部属性给父组件调用,就需要用到defineExpose({})

废话不多说,还是老例子,直接上代码吧。

// App.vue

<script setup lang="ts">
import Children from "@/components/Children.vue";
import { onMounted, ref } from "vue";

const child = ref<{
    msg: string;
    handle: () => void | null;
}>(null);

onMounted(() => {
    console.log(child.value?.msg);
    child.value.msg = "我修改了msg属性";
    child.value.handle();
});
</script>
<template>
    <Children ref="child" />
</template>
// Children.vue

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

const msg = ref("hello");
const num = ref(123);
const handle = () => {
    console.log("我要暴漏这个组件试试");
};
defineExpose({ msg, num, handle });
</script>

<template>
    <h1>子组件应用父组件的插槽</h1>
    {{ msg }}
</template>

首先是定义了Children.vue中的msgnumhandle()3个变量对外暴漏,就引用了defineExpose({ msg, num, handle });

App.vuechild了来进行获取,要注意 在<Children ref="child" /> 注册到页面的时候增加了ref来告诉编译器。同时在onMounted中,我们也可以通过child.value来从父组件修改子组件的值、或者调用方法。