props
用于声明一个组件的 props。
详细信息
在 Vue 中,所有的组件 props 都需要被显式声明。组件 props 可以通过两种方式声明:
- 使用字符串数组的简易形式。
- 使用对象的完整形式。该对象的每个属性键是对应 prop 的名称,值则是该 prop 应具有的类型的构造函数,或是更高级的选项。
在基于对象的语法中,每个 prop 可以进一步定义如下选项:
type:可以是下列原生构造函数之一:String
、Number
、Boolean
、Array
、Object
、Date
、Function
、Symbol
、任何自定义构造函数,或由上述内容组成的数组。在开发模式中,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
| defineProps({ title: String, likes: Number })
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({ propA: Number, propB: [String, Number], propC: { type: String, required: true }, propD: { type: Number, default: 100 }, propE: { type: Object, default(rawProps) { return { message: 'hello' } } }, propF: { validator(value) { 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>
<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