首页 > 编程笔记 > JavaScript笔记
Vue prop属性:父组件向子组件传递数据
在 Vue 中,组件是当作自定义元素来使用的,而元素一般是有属性的,同样组件也可以有属性。
在使用组件时,给元素设置属性,组件内部如何接收呢?首先需要在组件代码中注册一些自定义的属性,称为 prop,这些 prop 是在组件的 props 选项中定义的;之后,在使用组件时,就可以把这些 prop 的名字作为元素的属性名来使用,通过属性向组件传递数据,这些数据将作为组件实例的属性被使用。
图 1 使用prop属性向子组件传递数据
HTML 中的 attribute 名是不区分大小写的,所以浏览器会把所有大写字符解释为小写字符,prop 属性也适用这种规则。当使用 DOM 中的模板时,dateTitle(驼峰命名法)的 prop 名需要使用其等价的 date-title(短横线分隔命名)命名。
上面的示例中,使用 prop 属性向子组件传递了字符串值,还可以传递数字。这只是它的一个简单用法。通常情况下,可以使用 v-bind 来传递动态的值,传递数组和对象时也需要使用 v-bind 指令。
修改上面的示例,在 Vue 实例中定义 title 属性,以传递到子组件中去。示例代码如下。
图 2 传递title属性到子组件
在上面的示例中,在 Vue 实例中向子组件中传递数据,通常情况下多用于组件向组件传递数据。下面的示例创建了两个组件,在页面中渲染其中一个组件,而在这个组件中使用另一个组件,并传递 title 属性。
图 3 组件之间传递数据
如果组件需要传递多个值,则可以定义多个 prop 属性。
图 4 传递多个值
从上面的示例可以看到,代码以字符串数组形式列出多个 prop 属性:
另外,每次父级组件发生变更时,子组件中所有的 prop 属性都将会刷新为最新的值。这意味着不应该在一个子组件内部改变 prop 属性。如果这样做,Vue 会在浏览器的控制台中发出警告。
有两种情况可能需要改变组件的 prop 属性。第一种情况是定义一个 prop 属性,以方便父组件传递初始值,在子组件内将这个 prop 作为一个本地的 prop 数据来使用。遇到这种情况,解决办法是在本地的 data 选项中定义一个属性,然后将 prop 属性值作为其初始值,后续操作只访问这个 data 属性。示例代码如下:
在 JavaScript 中,对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 属性来说,在子组件中改变这个对象或数组本身将会影响父组件的状态。
因此,Vue.js 提供了prop属性的验证规则,在定义 props 选项时,使用一个带验证需求的对象来代替之前使用的字符串数组(props: ['name','price','city'])。代码如下:
上面代码验证的 type 可以是下面原生构造函数中的一个:
示例代码如下:
图 5 非prop的属性
从上面的示例可以看出,input-con 组件没有定义任何 prop,根元素是 <input>,在 DOM 模板中使用 <input-con> 元素时设置了 type 属性,这个属性将被添加到 input-con 组件的根元素 input 上,渲染结果为 <input type="text">。
另外,在 input-con 组件的模板中还使用了 class 属性 bg1,同时在 DOM 模板中也设置了 class 属性 bg2,这种情况下,两个 class 属性的值会被合并,最终渲染的结果为 <input class="bg1 bg2" type="text">。
要注意的是,只有 class 和 style 属性的值会合并,对于其他属性而言,从外部提供给组件的值会替换掉组件内容设置好的值。假设 input-con 组件的模板是 <input type="text">,如果父组件传入 type="password",就会替换掉 type="text",最后渲染结果就会变成 <input type="password">。
例如修改上面的示例:
图 6 外部组件的值替换掉组件设置好的值
如果不希望组件的根元素继承外部设置的属性,可以在组件的选项中设置 inheritAttrs: false。例如修改上面的示例代码:
在使用组件时,给元素设置属性,组件内部如何接收呢?首先需要在组件代码中注册一些自定义的属性,称为 prop,这些 prop 是在组件的 props 选项中定义的;之后,在使用组件时,就可以把这些 prop 的名字作为元素的属性名来使用,通过属性向组件传递数据,这些数据将作为组件实例的属性被使用。
prop的基本用法
下面看一个示例,使用 prop 属性向子组件传递数据,这里传递“庭院深深深几许,云窗雾阁春迟。”,在子组件的 props 选项中接收 prop 属性,然后使用插值语法在模板中渲染 prop 属性。<div id="app"> <blog-content date-title="庭院深深深几许,云窗雾阁春迟。"></blog-content> </div> <!--引入Vue文件--> <script src="https://unpkg.com/vue@next"></script> <script> //创建一个应用程序实例 const vm= Vue.createApp({}); vm.component('blog-content', { props: ['dateTitle'], //date-title就像data定义的数据属性一样 template: '<h3>{{ dateTitle }}</h3>', //在该组件中可以使用“this.dateTitle”这种形式调用prop属性 created(){ console.log(this.dateTitle); } }); //在指定的DOM元素上装载应用程序实例的根组件 vm.mount('#app'); </script>在 Chrome 浏览器中运行程序,效果如下图所示。
图 1 使用prop属性向子组件传递数据
HTML 中的 attribute 名是不区分大小写的,所以浏览器会把所有大写字符解释为小写字符,prop 属性也适用这种规则。当使用 DOM 中的模板时,dateTitle(驼峰命名法)的 prop 名需要使用其等价的 date-title(短横线分隔命名)命名。
上面的示例中,使用 prop 属性向子组件传递了字符串值,还可以传递数字。这只是它的一个简单用法。通常情况下,可以使用 v-bind 来传递动态的值,传递数组和对象时也需要使用 v-bind 指令。
修改上面的示例,在 Vue 实例中定义 title 属性,以传递到子组件中去。示例代码如下。
<div id="app"> <blog-content v-bind:date-title="content"></blog-content> </div> <!--引入Vue文件--> <script src="https://unpkg.com/vue@next"></script> <script> //创建一个应用程序实例 const vm= Vue.createApp({ //该函数返回数据对象 data(){ return{ content:"玉瘦檀轻无限恨,南楼羌管休吹。" } } }); vm.component('blog-content', { props: ['dateTitle'], template: '<h3>{{ dateTitle }}</h3>', }); //在指定的DOM元素上装载应用程序实例的根组件 vm.mount('#app'); </script>在 Chrome 浏览器中运行程序,效果如下图所示。
图 2 传递title属性到子组件
在上面的示例中,在 Vue 实例中向子组件中传递数据,通常情况下多用于组件向组件传递数据。下面的示例创建了两个组件,在页面中渲染其中一个组件,而在这个组件中使用另一个组件,并传递 title 属性。
<div id="app"> <!--使用blog-content组件--> <blog-content></blog-content> </div> <!--引入Vue文件--> <script src="https://unpkg.com/vue@next"></script> <script> //创建一个应用程序实例 const vm= Vue.createApp({ }); vm.component('blog-content', { // 使用blog-title组件,并传递content template: '<div><blog-title v-bind:date-title="title"></blog-title> </div>', data:function(){ return{ title:"明朝准拟南轩望,洗出庐山万丈青。" } } }); vm.component('blog-title', { props: ['dateTitle'], template: '<h3>{{ dateTitle }}</h3>', }); //在指定的DOM元素上装载应用程序实例的根组件 vm.mount('#app'); </script>在 Chrome 浏览器中运行程序,效果如下图所示。
图 3 组件之间传递数据
如果组件需要传递多个值,则可以定义多个 prop 属性。
<div id="app"> <!--使用blog-content组件--> <blog-content></blog-content> </div> <!--引入Vue文件--> <script src="https://unpkg.com/vue@next"></script> <script> //创建一个应用程序实例 const vm= Vue.createApp({ }); vm.component('blog-content', { // 使用blog-title组件,并传递content template: '<div><blog-title :name="name" :price="price" :num="num"> </blog-title></div>', data:function(){ return{ name:"苹果", price:"6.88元", num:"2800公斤" } } }); vm.component('blog-title', { props: ['name','price','num'], template: '<ul><li>{{name}}</li><li>{{price}}</li><li>{{num}}</li></ul> ', }); //在指定的DOM元素上装载应用程序实例的根组件 vm.mount('#app'); </script>在 Chrome 浏览器中运行程序,效果如下图所示。
图 4 传递多个值
从上面的示例可以看到,代码以字符串数组形式列出多个 prop 属性:
props: ['name','price','num'],但是,通常希望每个 prop 属性都有指定的值类型。这时,可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型,例如:
props: { name: String, price: String, num: String, }
单向数据流
所有的 prop 属性传递数据都是单向的。父组件的 prop 属性的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的数据,从而导致应用的数据流向难以理解。另外,每次父级组件发生变更时,子组件中所有的 prop 属性都将会刷新为最新的值。这意味着不应该在一个子组件内部改变 prop 属性。如果这样做,Vue 会在浏览器的控制台中发出警告。
有两种情况可能需要改变组件的 prop 属性。第一种情况是定义一个 prop 属性,以方便父组件传递初始值,在子组件内将这个 prop 作为一个本地的 prop 数据来使用。遇到这种情况,解决办法是在本地的 data 选项中定义一个属性,然后将 prop 属性值作为其初始值,后续操作只访问这个 data 属性。示例代码如下:
props: ['initDate'], data: function () { return { title: this.initDate } }第二种情况是 prop 属性接收数据后需要转换后使用。这种情况可以使用计算属性来解决。示例代码如下:
props: ['size'], computed: { nowSize:function(){ return this.size.trim().toLowerCase() } }后续的内容直接访问计算属性 nowSize 即可。
在 JavaScript 中,对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 属性来说,在子组件中改变这个对象或数组本身将会影响父组件的状态。
prop验证
当开发一个可复用的组件时,父组件希望通过 prop 属性传递的数据类型符合要求。例如,组件定义一个 prop 属性是一个对象类型,结果父组件传递的是一个字符串的值,这明显不合适。因此,Vue.js 提供了prop属性的验证规则,在定义 props 选项时,使用一个带验证需求的对象来代替之前使用的字符串数组(props: ['name','price','city'])。代码如下:
vm.component('my-component', { props: { // 基础的类型检查 ('null'和'undefined' 会通过任何类型验证) name: String, // 多个可能的类型 price: [String, Number], // 必填的字符串 city: { type: String, required: true }, // 带有默认值的数字 prop1: { type: Number, default: 100 }, // 带有默认值的对象 prop2: { type: Object, // 对象或数组默认值必须从一个工厂函数获取 default: function () { return { message: 'hello' } } }, // 自定义验证函数 prop3: { validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } })为组件的 prop 指定验证要求后,如果有一个需求没有被满足,则 Vue 会在浏览器控制台中发出警告。
上面代码验证的 type 可以是下面原生构造函数中的一个:
String Number Boolean Array Object Date Function Symbol另外,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:
function Person (firstName, lastName) { this.firstName = firstName this.lastName = lastName }可以通过以下代码验证name的值是不是通过new Person创建的。
vm.component('blog-content', { props: { name: Person } })
非prop的属性
在使用组件的时候,父组件可能会向子组件传入未定义 prop 的属性值,这样也是可以的。组件可以接收任意的属性,而这些外部设置的属性会被添加到子组件的根元素上。示例代码如下:
<style> .bg1{ background: #C1FFE4; } .bg2{ width: 120px; } </style> <div id="app"> <!--使用blog-content组件--> <input-con class="bg2" type="text"></input-con> </div> <!--引入Vue文件--> <script src="https://unpkg.com/vue@next"></script> <script> //创建一个应用程序实例 const vm= Vue.createApp({ }); vm.component('input-con', { template: '<input class="bg1">', }); //在指定的DOM元素上装载应用程序实例的根组件 vm.mount('#app'); </script>在 Chrome 浏览器中运行程序,输入“九曲黄河万里沙”,打开控制台,效果如下图所示。
图 5 非prop的属性
从上面的示例可以看出,input-con 组件没有定义任何 prop,根元素是 <input>,在 DOM 模板中使用 <input-con> 元素时设置了 type 属性,这个属性将被添加到 input-con 组件的根元素 input 上,渲染结果为 <input type="text">。
另外,在 input-con 组件的模板中还使用了 class 属性 bg1,同时在 DOM 模板中也设置了 class 属性 bg2,这种情况下,两个 class 属性的值会被合并,最终渲染的结果为 <input class="bg1 bg2" type="text">。
要注意的是,只有 class 和 style 属性的值会合并,对于其他属性而言,从外部提供给组件的值会替换掉组件内容设置好的值。假设 input-con 组件的模板是 <input type="text">,如果父组件传入 type="password",就会替换掉 type="text",最后渲染结果就会变成 <input type="password">。
例如修改上面的示例:
<div id="app"> <!--使用blog-content组件--> <input-con class="bg2" type=" password "></input-con> </div> <!--引入Vue文件--> <script src="https://unpkg.com/vue@next"></script> <script> //创建一个应用程序实例 const vm= Vue.createApp({ }); vm.component('input-con', { template: '<input class="bg1" type="text">', }); //在指定的DOM元素上装载应用程序实例的根组件 vm.mount('#app'); </script>在 Chrome 浏览器中运行程序,然后输入“12345678”,可以发现 input 的类型为“password”,效果如图 6 所示。
图 6 外部组件的值替换掉组件设置好的值
如果不希望组件的根元素继承外部设置的属性,可以在组件的选项中设置 inheritAttrs: false。例如修改上面的示例代码:
Vue.component('input-con', { template: '<input class="bg1" type="text">', inheritAttrs: false, });再次运行项目,可以发现父组件传递的 type="password" 子组件并没有继承。