Vue2里的props怎么用?从声明到传值避坑全解析
不少刚开始学Vue2的同学,一碰到props就犯愁——到底咋声明?父组件咋传数据?传的时候类型不对咋办?还有单向数据流是啥意思?别慌,这篇把props从基础到进阶的知识点拆成问答,帮你把每个环节吃透~
props到底是干啥的?
props是Vue里父组件给子组件传数据的核心方式,你可以理解成“子组件对外暴露的‘接口’”,父组件通过这个接口给子组件塞数据,让同一个子组件在不同场景下显示不同内容。
举个实际例子:你做了个「商品卡片组件」,首页、分类页都要用它,这时候父组件(首页)把商品名称、价格这些数据通过props传给子组件(商品卡片),子组件只负责渲染,这样一个组件就能在不同页面复用,数据由父组件控制,是不是很灵活?
怎么在子组件里声明props?
有两种声明风格:简单数组式和对象验证式,场景不同用法也不同~
数组式(快速声明,无验证)
如果只是简单传值,不需要验证类型、必填这些,直接用数组列出来要接收的prop名称:
Vue.component('Child', { props: ['name', 'age', 'isVip'] })
这种写法轻快,但缺点是没法做任何验证,适合小项目或临时组件。
对象式(带验证,严谨开发必用)
当你需要控制prop的类型、是否必填、默认值、自定义验证逻辑时,得用对象格式,每个prop对应一个配置项,常见配置有这几个:
- type:指定prop的类型,比如
String
、Number
、Boolean
、Array
、Object
,甚至自定义构造函数(比如自己写的class); - required:布尔值,标记这个prop是否必须由父组件传入;
- default:当父组件没传这个prop时,用啥默认值(注意:对象/数组的
default
要写成函数返回值,后面讲原因); - validator:自定义验证函数,传入prop的值,返回布尔值判断是否合法。
举个完整例子,做一个「用户信息组件」,要求name
必填(字符串)、age
选填(数字,默认18)、tags
必须是数组且长度≥1:
Vue.component('UserCard', { props: { name: { type: String, required: true // 父组件必须传name }, age: { type: Number, default: 18 // 没传就用18 }, tags: { type: Array, // 数组/对象的default要返回函数,避免所有实例共享同一个引用 default: () => [], validator: (value) => { // 验证tags长度 return value.length >= 1 } } } })
这样写后,父组件传值不对时,开发环境会直接在控制台报错提醒,相当于给代码加了“安全锁”~
父组件怎么给子组件传props?
父传子的方式分静态传递和动态传递,还有传对象/数组的技巧,一个个说:
静态传递(传固定值)
如果传给子组件的是“死数据”(比如固定字符串、数字),直接在标签上写属性:
<Child name="张三" age="18" />
注意哦!这种写法下,值的类型是字符串!哪怕你写age="18"
,子组件里如果声明type
是Number
,这里传的还是字符串'18'
,所以静态传递只适合字符串类型,或者子组件不验证类型的场景。
动态传递(传变量/表达式)
如果要传父组件的变量、布尔值、数组、对象,或者控制类型,得用v-bind(简写)绑定JS表达式:
<!-- 传变量 --> <Child :name="parentName" :age="parentAge" /> <!-- 传布尔值(加:`才是布尔true,否则是字符串'true') --> <Child :isVip="true" /> <!-- 传表达式 --> <Child :score="Math.random() * 100" />
用绑定后,值的类型由JS表达式决定(比如parentAge
是数字,传过去就是数字;Math.random()
返回数字,传过去也是数字),这样才能和子组件的type
验证对应上~
批量传对象/数组
如果父组件有个对象,里面包含子组件需要的多个props,不用一个个传,直接用v-bind="对象"批量传递:
// 父组件data里的对象 data() { return { userInfo: { name: '李四', age: 22, isVip: false } } }
<!-- 子组件需要name、age、isVip三个props --> <Child v-bind="userInfo" /> <!-- 等价于分别传: <Child :name="userInfo.name" :age="userInfo.age" :isVip="userInfo.isVip" /> -->
这种写法在对象属性多的时候超省心,代码也更简洁~
props的验证规则怎么玩?
前面提了type
、required
、default
、validator
这些配置,这里展开讲讲细节,避免踩坑:
type可以玩出花
type
不止支持内置类型(String
/Number
等),还能:
- 多个类型:比如
type: [String, Number]
,表示这个prop可以是字符串或数字; - 自定义构造函数:比如你写了个
class Person
,type: Person
,父组件传new Person()
进来才会验证通过;
举个自定义构造函数的例子:
// 定义Person类 class Person { constructor(name, age) { this.name = name this.age = age } } // 子组件props验证 props: { user: { type: Person, // 只有传Person实例才合法 required: true } } // 父组件传值 <Child :user="new Person('王五', 25)" />
default的“坑”:对象/数组必须用函数
如果prop的类型是Object
或Array
,直接写default: {name: '默认'}
会出问题——所有子组件实例会共享同一个对象/数组引用!比如一个子组件改了这个对象的属性,其他子组件的默认值也会被改掉,这是引用类型的特性导致的。
所以正确写法是用函数返回默认值,每个实例都能拿到独立的副本:
props: { settings: { type: Object, // 错误写法:default: { theme: 'light' } // 正确写法: default: () => ({ theme: 'light' }) }, hobbies: { type: Array, default: () => [] } }
validator:自定义“安检员”
validator
是个函数,接收当前prop的值作为参数,返回true/false
表示是否合法,比如验证手机号格式:
props: { phone: { type: String, validator: (value) => { // 正则判断是否是11位手机号 return /^1\d{10}$/.test(value) } } }
如果父组件传了个不符合规则的手机号,开发环境控制台会直接报错,相当于给数据加了“格式过滤器”~
props的单向数据流是啥?为啥不能直接改props?
Vue里props遵循“单向数据流”规则:父组件传值给子组件后,子组件不能直接修改props的值,为啥要这么设计?
想象一下:如果子组件能直接改props,父组件里的数据源就会被偷偷修改,整个项目的数据流向会变得混乱,调试时根本不知道谁改了数据,所以Vue强制让数据“父→子”单向流动,保证数据可预测。
那子组件要改props的值咋办?分两种场景:
只是“用”props的值,但需要局部修改
把props的值复制到子组件的data
里,后续改data
里的副本:
Vue.component('Child', { props: ['count'], data() { return { localCount: this.count // 复制props到data } }, methods: { increment() { this.localCount++ // 改的是localCount,不影响父组件 } } })
需要把修改同步回父组件
子组件通过$emit
触发父组件的事件,让父组件自己修改数据源,从而更新子组件的props,比如子组件有个“+1”按钮,要让父组件的count
加1:
// 子组件 Vue.component('Child', { props: ['count'], methods: { handleClick() { this.$emit('update-count', this.count + 1) // 触发事件,传新值 } } }) // 父组件 <template> <div> <Child :count="parentCount" @update-count="parentCount = $event" /> </div> </template> <script> export default { data() { return { parentCount: 0 } } } </script>
这样父组件主动修改自己的parentCount
,子组件的props会自动更新(因为单向数据流,父变了子跟着变),既遵守规则又实现了交互~
props和data、computed有啥区别?
很多同学刚学的时候会混淆这三个,其实它们的“身份”和作用域完全不同:
特性 | props | data | computed |
---|---|---|---|
数据来源 | 父组件传入(外部) | 组件内部自己定义(内部) | 基于props、data等计算衍生(内部) |
可修改性 | 不能直接改(单向流) | 可以自由修改(内部状态) | 一般是只读(由依赖决定) |
典型场景 | 子组件接收父组件配置 | 组件内部的临时状态(比如表单输入) | 复杂逻辑的封装(比如购物车总价计算) |
举个生活例子:你点外卖,props像“商家给你的餐品”(外部给的),data像“你自己加的辣椒醋”(内部调的),computed像“算出这顿饭总共花多少钱”(基于餐品和调料计算的结果)~
传值时类型不匹配咋办?
最常见的坑是父组件传值类型和子组件type
声明不一致,比如父传字符串"18"
,子组件要Number
类型的age
,这时候得注意传值方式:
- 如果是静态传递(没加),比如
<Child age="18" />
,子组件拿到的是字符串'18'
,哪怕type
声明Number
也没用; - 如果是动态传递(加),比如
<Child :age="18" />
,子组件拿到的是数字18
,这时候type
验证才能生效。
所以记住:想传非字符串类型,必须用v-bind
绑定JS表达式!比如传布尔值true
,得写:isVip="true"
,不然传的是字符串'true'
;传数组/对象同理,必须用绑定。
子组件能主动通知父组件更新props吗?
前面讲单向数据流时提过,子组件不能直接改props,但可以通过$emit
事件让父组件自己改,流程是:
- 子组件触发
$emit('事件名', 新值)
- 父组件监听这个事件,修改自己的数据源(比如
data
里的变量) - 因为父组件的数据源变了,子组件的props会自动更新(单向流的“父变子变”)
举个实时搜索的例子:子组件是搜索输入框,输入内容后要让父组件的searchKey
更新,从而重新请求数据,代码如下:
// 子组件(搜索框) Vue.component('SearchInput', { props: ['value'], // 父组件传的当前搜索关键词 methods: { handleInput(e) { const newKey = e.target.value this.$emit('input', newKey) // 触发input事件,传新关键词 } }, template: `<input :value="value" @input="handleInput" />` }) // 父组件 <template> <div> <SearchInput :value="searchKey" @input="searchKey = $event" /> <button @click="fetchData">搜索</button> </div> </template> <script> export default { data() { return { searchKey: '' } }, methods: { fetchData() { // 用searchKey发请求... } } } </script>
这样子组件输入时,通过$emit
通知父组件改searchKey
,父组件的searchKey
变了,子组件的value
(props)也会跟着变,完美绕开“直接改props”的坑~
props的驼峰命名和短横线命名咋处理?
HTML标签的属性名是不区分大小写的,所以Vue里有个“驼峰→短横线”的转换规则:
- 子组件props声明用驼峰命名(比如
userName
); - 父组件模板中传值时,要用短横线命名(比如
user-name
); - 但如果是用JS动态绑定(
:userName
),因为在JS上下文里,驼峰是合法的,所以也能传。
举个例子:
// 子组件声明 props: ['userName'] // 父组件模板里的两种传法: <!-- 静态传,必须用短横线 --> <Child user-name="赵六" /> <!-- 动态传,驼峰或短横线都能识别,但建议和子组件声明一致 --> <Child :userName="name" /> <Child :user-name="name" />
简单说:模板里静态写属性名用短横线,动态绑定()时驼峰/短横线都行,但为了统一,建议和子组件props的命名风格一致~
props验证不通过会怎样?
在开发环境下,Vue会帮你“挑错”:如果prop的类型不对、required
没传、validator
返回false
,控制台会弹出警告,直接告诉你哪里传错了,相当于有个“代码检查助手”;
但到了生产环境(打包后),为了性能,Vue会把props验证的代码删掉,所以验证只在开发时起作用,用来提前发现错误,避免线上bug~
看到这,你对Vue2的props应该从“一头雾水”变成“心里有数”了吧?记住核心逻辑:props是父传子的桥梁,声明时做好验证,传值时注意类型和绑定方式,修改时遵守单向数据流规则,多写几个组件练手,这些知识点自然就吃透啦~如果还有疑问,评论区随时喊我~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。