setup语法糖组件传值

  1. 1. props
    1. 1.1. Props 声明
    2. 1.2. Prop 名字格式
    3. 1.3. 使用一个对象绑定多个 prop
    4. 1.4. 单向数据流
  2. 2. 父向子:
    1. 2.1. 父组件:
    2. 2.2. 子组件:
  3. 3. Vue3全局组件通信之EventBus

props

用于声明一个组件的 props。

详细信息

在 Vue 中,所有的组件 props 都需要被显式声明。组件 props 可以通过两种方式声明:

  • 使用字符串数组的简易形式。
  • 使用对象的完整形式。该对象的每个属性键是对应 prop 的名称,值则是该 prop 应具有的类型的构造函数,或是更高级的选项。

在基于对象的语法中,每个 prop 可以进一步定义如下选项:

  • type:可以是下列原生构造函数之一:StringNumberBooleanArrayObjectDateFunctionSymbol、任何自定义构造函数,或由上述内容组成的数组。在开发模式中,Vue 会检查一个 prop 的值是否匹配其声明的类型,如果不匹配则会抛出警告。详见 Prop 校验

    还要注意,一个 Boolean 类型的 prop 会影响它在开发或生产模式下的值转换行为。详见 Boolean 类型转换。

  • default:为该 prop 指定一个当其没有被传入或值为 undefined 时的默认值。对象或数组的默认值必须从一个工厂函数返回。工厂函数也接收原始 prop 对象作为参数。

  • required:定义该 prop 是否必需传入。在非生产环境中,如果 required 值为真值且 prop 未被传入,一个控制台警告将会被抛出。

  • validator:将 prop 值作为唯一参数传入的自定义验证函数。在开发模式下,如果该函数返回一个假值 (即验证失败),一个控制台警告将会被抛出。

对象声明,带有验证:

Props 声明

在使用 <script setup> 的单文件组件中,props 可以使用 defineProps() 宏来声明:

1
2
3
4
5
<script setup>
const props = defineProps(['foo'])

console.log(props.foo)
</script>

在没有使用 <script setup> 的组件中,prop 可以使用 props 选项来声明:

1
2
3
4
5
6
7
export default {
props: ['foo'],
setup(props) {
// setup() 接收 props 作为第一个参数
console.log(props.foo)
}
}

注意传递给 defineProps() 的参数和提供给 props 选项的值是相同的,两种声明方式背后其实使用的都是 prop 选项。

除了使用字符串数组来声明 prop 外,还可以使用对象的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用 <script setup>
defineProps({
title: String,
likes: Number
})

// 非 <script setup>
export default {
props: {
title: String,
likes: Number
}
}

对于以对象形式声明中的每个属性,key 是 prop 的名称,而值则是该 prop 预期类型的构造函数。比如,如果要求一个 prop 的值是 number 类型,则可使用 Number 构造函数作为其声明的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export default defineComponent({
components: {
YinIcon,
},

props: {
title: String,
playList: Array,
path: String,
},

setup(props) {
const { proxy } = getCurrentInstance();
const { routerManager } = mixin();

const { path } = toRefs(props);

function goAblum(item) {
proxy.$store.commit("setSongDetails", item);
routerManager(path.value, { path: `/${path.value}/${item.id}` });
}

return {
BOFANG: Icon.BOFANG,
goAblum,
attachImageUrl: HttpManager.attachImageUrl,
};
},
});

Prop 名字格式

如果一个 prop 的名字很长,应使用 camelCase 形式,因为它们是合法的 JavaScript 标识符,可以直接在模板的表达式中使用,也可以避免在作为属性 key 名时必须加上引号。

1
2
3
defineProps({
greetingMessage: String
})
1
<span>{{ greetingMessage }}</span>

虽然理论上你也可以在向子组件传递 props 时使用 camelCase 形式 ,但实际上为了和 HTML attribute 对齐,我们通常会将其写为 kebab-case 形式:

1
<MyComponent greeting-message="hello" />

使用一个对象绑定多个 prop

如果你想要将一个对象的所有属性都当作 props 传入,你可以使用[没有参数的 v-bind,即只使用 v-bind 而非 :prop-name。例如,这里有一个 post 对象:

1
2
3
4
const post = {
id: 1,
title: 'My Journey with Vue'
}
1
<BlogPost v-bind="post" />

而这实际上等价于:

1
<BlogPost :id="post.id" :title="post.title" />

单向数据流

所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。

另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你**不应该**在子组件中去更改一个 prop。
若你这么做了,Vue 会在控制台上向你抛出警告 ## Prop 校验

Vue 组件可以更细致地声明对传入的 props 的校验要求。比如我们上面已经看到过的类型声明,如果传入的值不满足类型要求,Vue 会在浏览器控制台中抛出警告来提醒使用者。这在开发给其他开发者使用的组件时非常有用。

要声明对 props 的校验,你可以向 defineProps() 宏提供一个带有 props 校验选项的对象,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})

TIP

defineProps() 宏中的参数不可以访问 script 中定义的其他变量,因为在编译时整个表达式都会被移到外部的函数中。

当一个 prop 被声明为允许多种类型时,例如:

1
2
3
defineProps({
disabled: [Boolean, Number]
})

一些补充细节:

  • 所有 prop 默认都是可选的,除非声明了 required: true
  • Boolean 外的未传递的可选 prop 将会有一个默认值 undefined
  • Boolean 类型的未传递 prop 将被转换为 false。你应该为它设置一个 default 值来确保行为符合预期。
  • 如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default 值。

父向子:

父组件:

Homeview.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<SingerNav></SingerNav>
<!-- <el-container style="margin-top: 30px; justify-content: space-around">
<div class="home"></div>
<div>
<el-row :gutter="20">
<el-col v-for="item in state.song" :span="6">
<el-card>
<img :src="item.al.picUrl" class="image" />
<div style="padding: 14px">
<span>《{{ item.name }}》</span>
</div>
</el-card>
</el-col>
</el-row>
</div>
</el-container> -->

<PlayerList :song="state.song"></PlayerList>
</template>

<script setup>
import axios from "axios";
import SingerNav from "../components/nav/SingerNav";
import { reactive, ref } from "vue";
import PlayerList from "../components/PlayerList";
import AboutView from "../views/AboutView";

let data = ref("hello");

const state = reactive({
song: [],
});

const url = `http://localhost:3000/artist/top/song?id=6452`;
axios.get(url).then((response) => {
console.log(response);
state.song = response.data.songs;
});
</script>

子组件:

PlayerList.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div class="play-list"></div>
<el-container style="margin-top: 30px; justify-content: space-around">
<div class="home"></div>
<div>
<el-row :gutter="20">
<el-col v-for="item in song" :span="6">
<el-card>
<img :src="item.al.picUrl" class="image" />
<div style="padding: 14px">
<span>《{{ item.name }}》</span>
</div>
</el-card>
</el-col>
</el-row>
</div>
</el-container>
</template>
<script setup>
import { ref, toRefs, toRef } from "vue";
name: "PlayerList";
// 接收来自父类的传值
const props = defineProps({
song: [],
});
const { song } = toRefs(props); // 转换响应式数据
</script>

不使用toRefs父组件改变 data 的值,子组件 无法响应 data 的变化。

因为 ref 是对传入数据的拷贝但 toRef 是对传入数据的引用,

Vue3全局组件通信之EventBus

全局组件通信是指,两个任意的组件,不管是否有关联(e.g. 父子、爷孙)的组件,都可以直接进行交流的通信方案。

https://www.jianshu.com/p/d8d55d8f0c48?u_atoken=5e86a83a-ffc6-4ad3-98f8-43e1323a96c0&u_asession=013Wg47ifu2ppQC18G8-YrZKRLo1EMTYJ393KInUTYZSLRcaSVSXkzAaSLdIOIzouoX0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K_5SWLIGwoQfWc2ywKEYeo3UPWO0ljqS-0m6uUj231Ub2BkFo3NEHBv0PZUm6pbxQU&u_asig=05B84LN60qDaOs1YORDHf9wugx9snzDfdiys9CRyip3FcBeS4TEY8rxBo0Gv7bEXA1mj3i1zk4fYYixn31kDfhS2NDGvhBP9-XAp36NYAoZ4UMOwsf_bKglwHzsQLkfymy-WjpjNJ4SZbDD2XudEN6VCwnlooXFag0MHwcNNa72P79JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzdffBxuN6hoqtC_e0JdS4TAaZZ7121Zl4eRtOE_tqOnHU1_gr7b-5Q11Fu-gS_hPv-3h9VXwMyh6PgyDIVSG1W-wc6kUNTGbD58hpmtPSg0q1Lvc-etxreWHqYi8cEYrnfpkNooKeSTweV2h7ZLbVklS5_kwGzRBd9innlpI-NG6mWspDxyAEEo4kbsryBKb9Q&u_aref=NyeYSjObejyjcNLyKc0yekt3CMU%3D