Vue3.2+setup 组件传值备忘
在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.vue
的script
进行改造。
普通声明
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.x
的setup
语法糖中定义的变量默认是不会暴漏出去的,如果需要暴漏组件内部属性给父组件调用,就需要用到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
中的msg
、num
、handle()
3个变量对外暴漏,就引用了defineExpose({ msg, num, handle });
在App.vue
中child
了来进行获取,要注意 在<Children ref="child" />
注册到页面的时候增加了ref
来告诉编译器。同时在onMounted
中,我们也可以通过child.value
来从父组件修改子组件的值、或者调用方法。