文章目录
- 1. Props 声明
- 1.1 props用字符串数组来声明
- Blog.vue
- BlogPost.vue
- 1.2 props使用对象来声明
- Blog.vue
- BlogPost.vue
- 2. 传递 prop 的细节
- 2.1 Prop 名字格式
- 2.1 静态Prop & 动态 Prop
- 静态prop
- 动态prop
- 示例
- Blog.vue
- BlogPost.vue
- 2.3 传递不同的值类型
- Number
- Boolean
- Array
- Object
- 2.4 使用一个对象绑定多个 prop
- Blog.vue
- BlogPost.vue
- 3. 单向数据流(重要)
- 3.1 更改对象 / 数组类型的 props
- Blog.vue
- BlogPost.vue
- 4. Prop 校验
- 4.1 运行时类型检查
- 5. Boolean 类型转换
- Blog.vue
- BlogPost.vue
1. Props 声明
- 一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props,哪些是透传 attribute
- props 需要使用 props 选项来定义
1.1 props用字符串数组来声明
看上去跟vue2没什么区别,就是先引入组件,然后注册组件,然后就使用组件。接着绑定属性。
Blog.vue
<template>
<div>
<h1>Blog组件</h1>
<blog-post title="学习vue3"/>
</div>
</template>
<script >
import BlogPost from './BlogPost.vue'
export default {
name: 'Blog',
components:{
BlogPost
}
}
</script>
<style lang="scss"></style>
BlogPost.vue
<template>
<h3>{{ title }}</h3>
</template>
<script>
export default {
name: 'BlogPost',
props: ['title']
}
</script>
<style lang="scss"></style>
1.2 props使用对象来声明
- key 是 prop 的名称,而值则是该 prop 预期类型的构造函数
- 如果在使用时,传递了错误的类型,会在浏览器控制台中抛出警告
Blog.vue
<template>
<div>
<h1>Blog组件</h1>
<blog-post title="学习vue3" :likes="3"/>
</div>
</template>
<script >
import BlogPost from './BlogPost.vue'
export default {
name: 'Blog',
components:{
BlogPost
}
}
</script>
<style lang="scss"></style>
BlogPost.vue
<template>
<h3>{{ title }}</h3>
<span>{{ likes }}</span>
</template>
<script>
export default {
name: 'BlogPost',
props: {
title: String,
likes: Number
}
}
</script>
<style lang="scss"></style>
2. 传递 prop 的细节
2.1 Prop 名字格式
- 定义prop的名字时 ,建议使用
camelCase 形式
(驼峰命名,首字母小写) - 向子组件传递 props 时,可以使用 camelCase 形式,但通常使用
kebab-case形式
(短横线形式) - 定义组件名时,建议使用
PascalCase形式
(驼峰命名,但首字母大写)
2.1 静态Prop & 动态 Prop
静态prop
<BlogPost title="My journey with Vue" />
动态prop
可以将组件中定义的属性值(响应式数据)传给子组件,当这些属性值发生变化时,子组件也会更新重新渲染
<!-- 根据一个变量的值动态传入 -->
<BlogPost :title="post.title" />
<!-- 根据一个更复杂表达式的值动态传入 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />
示例
Blog.vue
<template>
<div>
<h1>Blog组件</h1>
<button @click="addLike">addLike</button>
<blog-post title="学习vue3" :likes="likes"/>
</div>
</template>
<script >
import BlogPost from './BlogPost.vue'
export default {
name: 'Blog',
components:{
BlogPost
},
data() {
return {
likes: 0
}
},
methods: {
addLike() {
this.likes ++
}
}
}
</script>
<style lang="scss">
</style>
BlogPost.vue
<template>
<h3>{{ title }}</h3>
<span>{{ likes }}</span>
</template>
<script>
export default {
name: 'BlogPost',
props: {
title: String,
likes: Number
}
}
</script>
<style lang="scss"></style>
2.3 传递不同的值类型
任何类型的值
都可以作为 props 的值
被传递。- 使用
v-bind绑定的值 将视为 js表达式
Number
<!-- 虽然 `42` 是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :likes="42" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :likes="post.likes" />
Boolean
注意会隐式转换
,和 使用 v-bind绑定的值 将视为 js表达式
<!-- 仅写上 prop 但不传值,会隐式转换为 `true` -->
<BlogPost is-published />
<!-- 虽然 `false` 是静态的值,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :is-published="false" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :is-published="post.isPublished" />
Array
<!-- 虽然这个数组是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :comment-ids="[234, 266, 273]" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :comment-ids="post.commentIds" />
Object
<!-- 虽然这个对象字面量是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :author="{name: 'Veronica', company: 'Veridian Dynamics'}" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :author="post.author" />
2.4 使用一个对象绑定多个 prop
可以使用没有参数的 v-bind,即只使用 v-bind 而非 :prop-name。这样就可以将一个对象的所有属性都当作 props 传入
Blog.vue
<template>
<div>
<h1>Blog组件</h1>
<blog-post v-bind="post"/>
<blog-post :title="post.title" :likes="post.likes"/>
</div>
</template>
<script >
import BlogPost from './BlogPost.vue'
export default {
name: 'Blog',
components:{
BlogPost
},
data() {
return {
post: {
likes: 3,
title: '学习vue3'
}
}
}
}
</script>
<style lang="scss"></style>
BlogPost.vue
<template>
<h3>{{ title }}</h3>
<span>{{ likes }}</span>
</template>
<script>
export default {
name: 'BlogPost',
props: {
title: String,
likes: Number
}
}
</script>
<style lang="scss"></style>
3. 单向数据流(重要)
-
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化
,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。 -
每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着
不应该在子组件中去更改一个 prop
。若你这么做了,Vue 会在控制台上向你抛出警告export default { props: ['foo'], created() { // ❌ 警告!prop 是只读的! this.foo = 'bar' } }
-
子组件想修改prop,可以使用 “变通的手段来更改prop”(不是真正的修改)
-
prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是
新定义一个局部数据属性,从 props 上获取初始值
即可:export default { props: ['initialCounter'], data() { return { // 计数器只是将 this.initialCounter 作为初始值 // 像下面这样做就使 prop 和后续更新无关了 counter: this.initialCounter } } }
-
需要对传入的 prop 值做进一步的转换。在这种情况中,最好是
基于该 prop 值定义一个计算属性
:export default { props: ['size'], computed: { // 该 prop 变更时计算属性也会自动更新 normalizedSize() { return this.size.trim().toLowerCase() } } }
-
3.1 更改对象 / 数组类型的 props
当对 象 或 数组 作为 props 被传入时,虽然子组件无法更改 props 绑定
,但仍然可以更改对象或数组内部的值
。
-
在最佳实践中,你应该尽可能避免这样的更改,除非父子组件在设计上本来就需要紧密耦合
-
在大多数场景下,
子组件应该抛出一个事件来通知父组件做出改变
Blog.vue
<template>
<div>
<h1>Blog组件</h1>
<blog-post :post="post" @addLiking="addLiking"/>
</div>
</template>
<script >
import BlogPost from './BlogPost.vue'
export default {
name: 'Blog',
components:{
BlogPost
},
data() {
return {
post: {
likes: 3,
title: '学习vue3',
tags: ['java','spring','vue','ps']
},
}
},
methods: {
addLiking(){
this.post.likes += 2
}
}
}
</script>
<style lang="scss"></style>
BlogPost.vue
- 父组件通过props传递过来post
- 子组件中直接修改通过props传过来的post(对象)里面内部的属性
- 子组件中直接修改通过props传过来的post.tags(数组)里面内部的属性
<template>
<h3>{{ post.title }}</h3>
<span>{{ post.likes }}</span>
<br/>
<span v-for="tag,idx in post.tags" :key="idx">{{ tag }}、</span>
<br/>
<button @click="addLike">addLike + 1</button>
<button @click="emitLike">emitLike + 2</button>
<br/>
<button @click="post.tags[3] = 'redis'">changeArr</button>
</template>
<script>
export default {
name: 'BlogPost',
props: {
post: Object
},
methods: {
addLike() {
this.post.likes++
},
emitLike() {
this.$emit('addLiking')
}
}
}
</script>
<style lang="scss"></style>
4. Prop 校验
可以向 props 选项提供一个带有 props 校验选项的对象。
- 所有 prop 默认都是可选的,
除非声明了 required: true
- 除 Boolean 外的
未传递的可选 prop 将会有一个默认值 undefined
。Boolean 类型的未传递 prop 将被转换为 false
!!!(但是,可以通过default来修改,比如:default:undefined。)。
- 如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default 值。
export default {
props: {
// 基础类型检查
//(给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或者数组应当用工厂函数返回。
// 工厂函数会收到组件所接收的原始 props
// 作为参数
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'
}
}
}
}
4.1 运行时类型检查
校验选项中的 type 可以是下列这些原生构造函数
,也可以是自定义的类或构造函数
,Vue 将会通过 instanceof 来检查类型是否匹配。
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
5. Boolean 类型转换
声明为 Boolean 类型的 props 有特别的类型转换规则
Blog.vue
- 虽然diabled属性被声明为了Boolean类型,但是,如果传一个字符串过去,仍然就是字符串。
<template>
<div>
<h1>Blog组件</h1>
<blog-post disabled/> <br/>
<blog-post disabled/> <br/>
<blog-post disabled="false"/> <br/>
<blog-post disabled="false1"/> <br/>
<blog-post :disabled="false"/>
</div>
</template>
<script >
import BlogPost from './BlogPost.vue'
export default {
name: 'Blog',
components:{
BlogPost
},
data() {
return {}
}
}
</script>
<style lang="scss"></style>
BlogPost.vue
<template>
{{ disabled }}
</template>
<script>
export default {
name: 'BlogPost',
props: {
disabled:Boolean
}
}
</script>
<style lang="scss"></style>