首页 > 编程笔记

Vue父组件给子组件传值(超级详细)

在 Vue.js 中,可以在定义子组件中定义多个 prop 属性,用来接收父组件传过来的数据。也就是说,父组件可以通过子组件的 prop 属性,给子组件传递值。

定义一个 ViewCount 组件,显示通过 propCount 传入的值。在 vm 根实例组件中,注册并且使用 ViewCount 组件,显示单击按钮累计的单击次数,代码如下:
<div id="app">
    <button v-on:click="clickMe">单击我</button>
    <!-- 使用ViewCount组件显示单击次数 -->
    <view-count :prop-count="count"></view-count>
</div>

<template id="viewCountTemplate">
    <div>{{ propCount }}</div>
</template>

<script type="text/javascript">

    const ViewCount = {
        props: ['propCount'],
        template: '#viewCountTemplate'
    }

    const vm = new Vue({
        el: '#app',
        components: {
            'view-count': ViewCount
        },
        data: {
            count: 0
        },
        methods: {
            clickMe() {
                this.count++;
            }
        }
    });

</script>
传值的语法如下:
<子组件名称:prop 属性名称="表达式"></子组件名称>
或者:
<子组件名称 v-bind:prop 属性名称="表达式"></子组件名称>

prop的大小写

HTML 中的 attribute 名对大小写不敏感,所以浏览器会把所有大写字符解释为小写字符。这意味着当开发人员使用 DOM 中的模板时,用 camelCase(驼峰命名法)命名的 prop 名需要使用其等价的 kebab-case(短横线分隔命名法)命名,代码如下:
Vue.component('sub-component', {
    // 在 JavaScript 中使用的是 camelCase
    props: ['postTitle'],
    template: '<h3>{{ postTitle }}</h3>'
});

// 在 HTML 中使用的是 kebab-case
<sub-component :post-title="hello"></sub-component>

prop的数据类型

prop 除了支持数字和 string 类型外,还支持其他类型,代码如下:
props: {
    title: String,
    likes: Number,
    isPublished: Boolean,
    commentIds: Array,
    author: Object,
    callback: Function,
    contactsPromise: Promise
}
这不仅为组件提供了使用参考文档,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户。使用方式和说明的样例代码如下。

1) 传入一个数字

<!-- 即便‘42’是静态的,仍然需要v-bind来告诉Vue.js -->
<!-- 这是一个JavaScript表达式而不是一个字符串. -->
<sub-component v-bind:likes="42"></sub-component>

<!-- 用一个变量进行动态赋值. -->
<sub-component v-bind:likes="post.likes"></sub-component>

2) 传入一个布尔值

<!-- 包含该prop没有值的情况在内,都意味着true -->
<sub-component is-published></sub-component>

<!-- 即便'false'是静态的,仍然需要'v-bind'来告诉Vue.js -->
<!-- 这是一个JavaScript表达式而不是一个字符串. -->
<sub-component v-bind:is-published="false"></sub-component>

<!-- 用一个变量进行动态赋值. -->
<sub-component v-bind:is-published="post.isPublished"></sub-component>

3) 传入一个数组

<!-- 即便数组是静态的,我们仍然需要'v-bind'来告诉Vue.js -->
<!-- 这是一个JavaScript表达式而不是一个字符串. -->
<sub-component v-bind:comment-ids="[234, 266, 273]"></sub-component>

<!-- 用一个变量进行动态赋值. -->
<sub-component v-bind:comment-ids="post.commentIds"></sub-component>

4) 传入一个对象

<!-- 即便对象是静态的,仍然需要'v-bind'来告诉Vue.js -->
<!-- 这是一个JavaScript表达式而不是一个字符串. -->
<sub-component v-bind:author="{ name: 'Veronica', company: 'Meridian Dynamics' }"></sub-component>

<!-- 用一个变量进行动态赋值. -->
<sub-component v-bind:author="post.author"></sub-component>

5) 传入一个对象的所有property

如果开发人员要将一个对象的所有 property 都作为 prop 传入,则可以使用不带参数的 v-bind(取代 v-bind:prop-name)。将 user 对象的所有属性传递到组件中,代码如下:
<template id="subUserId">
    <div>
        <p>name: {{ userAttr.name }}</p>
        <p>age: {{ userAttr.age }}</p>
    </div>
</template>

<div id="app">
    <!-- 传入用户对象 -->
    <user-component :user="user"></user-component>
</div>

<script>
    Vue.component('user-component', {
        props: ['userAttr'],
        template: '#subUserId'
    });

    const vm = new Vue({
        el: "#app",
        data: {
            user: {
                name: '张三',
                age: 12
            }
        }
    });
</script>

prop单向数据流

所有的 prop 都使其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致应用的数据流向难以理解。

另外,每次父级组件发生变更时,子组件中所有的 prop 都将被刷新为最新的值。这意味着开发人员不应该在一个子组件的内部改变 prop。如果这样做了,Vue.js 则会在浏览器的控制台中发出警告。

这里有两种常见的试图变更一个prop的情形:

1) 使用 prop attribute 将一个初始值传递给子组件的本地属性,子组件直接操作本地属性。

2) 在子组件中定义计算属性,基于 prop attribute 传入的值进行计算处理。

代码如下:
<!-- 定义模板 -->
<template id="templateIn">
    <div>
        <!-- 初始值方式 -->
        <p>
            <span>传入的 counter: {{ counter }}</span><br>
            sub Counter: <input v-model="subCounter" /><br>
            <span>子组件更新 counter: {{ subCounter }}</span>
        </p>
       
        <!-- 计算属性方式 -->
        <p>
            <span>传入的 size: {{ size }}</span><br>
            subSize: <input v-model="computeSize" /><br>
            <span>子组件更新 size: {{ computeSize }}</span>
        </p>
    </div>
</template>

<div id="app">
    counter: <input v-model="counter" /><br>
    size: <input v-model="size" />
    <sub-test :counter="counter" :size="size"></sub-test>
</div>

<script>
    const subVue = {
        props: ['counter', 'size'],  // 定义prop属性接收父组件的值
        data: function() {
            return {
                subCounter: this.counter  // 给本地属性赋值
            };
        },
        computed: {
            computeSize: {
                get: function() {
                    return 'result -> ' + this.size;
                },
                set: function(newValue) {
                    // 不能响应到父组件,会抛出异常
                    this.size = newValue.slice('result -> '.length);
                }
            }
        },
        template: '#templateIn'
    };

    const vm = new Vue({
        el: "#app",
        data: {
            counter: 12,
            size: 10
        },
        components: {
            'sub-test': subVue
        }
    });
</script>

prop属性验证

开发人员还可以为组件的 prop 指定验证要求。如果有一个需求没有被满足,则 Vue.js 会在浏览器控制台中警告。这在开发一个会被别人用到的组件时尤其有帮助。

为了定制 prop 的验证方式,开发人员可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组,代码如下:
<!-- 定义模板 -->
<template id="templateIn">
    <div>
        <span>{{ attr1 }}</span><br>
        <span>{{ attr2 }}</span><br>
        <span>{{ attr3 }}</span><br>
        <span>{{ attr4 }}</span><br>
        <span>{{ attr5.message }}</span><br>
        <span>{{ attr6 }}</span><br>
    </div>
</template>

<div id="app">
    <son-component
        :attr1="a1"
        :attr2="a2"
        :attr3="a3"
        :attr4="a4"
        :attr5="a5"
        :attr6="a6">
    </son-component>
</div>

<script>
    const SonComponent = {
        props: {
            attr1: Number,
            attr2: [String, Number],
            attr3: {
                type: String,
                required: true
            },
            attr4: {
                type: Number,
                default: 10
            },
            attr5: {
                type: Object,
                default: function() {
                    return { message: 'hello' };
                }
            },
            attr6: {
                type: String,
                validator: function(value) {
                    // 这个值必须匹配下列字符串中的一个
                    return ["success", "warning", "danger"].indexOf(value) !== -1;
                }
            }
        },
        template: '#templateIn'
    };

    const vm = new Vue({
        el: "#app",
        components: {
            SonComponent
        },
        data: {
            a1: 1,
            a2: 'hello',
            a3: 'world',
            a4: 11,
            a5: { message: "hai" },
            a6: 'success'
        }
    });
</script>
当 prop 验证失败时,Vue.js 将会发出一个控制台警告。

实例的属性是在对象创建之前进行验证的,所以实例的属性(如 data 和 computed)在 default 和 validator 函数中不可用。

prop 支持的类型包括:String、Number、Boolean、Array、Object、Date、Function、Symbol,同时支持自定义的构造函数,能使用 instanceof 进行确认,代码如下:
<!-- 定义模板 -->
<template id="template1">
    <div>
        {{ personAttr.firstName }} {{ personAttr.lastName }}
    </div>
</template>

<div id="app">
    <son-component :person-attr="person"></son-component>
</div>

<script>
    function Person(first, last) {
        this.firstName = first;
        this.lastName = last;
    }

    const SonComponent = {
        template: '#template1',
        props: {
            personAttr: {
                type: Person,
                validator: function(value) {
                    return value instanceof Person;
                }
            }
        }
    };

    const vm = new Vue({
        el: "#app",
        components: {
            SonComponent
        },
        data: {
            person: new Person("san", "zhang")
        }
    });
</script>

非prop的attribute

组件可以接受任意的 attribute,而这些 attribute 会被添加到这个组件的根元素上。

显式定义的 prop 适用于向一个子组件传入信息,这也是 Vue.js 中推荐的做法,即向子组件传值的方式,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的 attribute,而这些 attribute 会被添加到这个组件的根元素上。

如下例子中的 son-component 组件的 notprop 属性,没有在 son-component 的 props 属性中定义,但是会被直接渲染到子组件的根元素(div元素)中,代码如下:
<!-- 定义模板 -->
<template id="template1">
    <div class="subClass" name="subName">子组件</div>
</template>

<div id="app">
    <son-component
        :notprop="notPropValue"
        :class="clsValue"
        :name="nameValue">
    </son-component>
</div>

<script>
    const SonComponent = {
        template: '#template1'
    };

    const vm = new Vue({
        el: "#app",
        data: {
            notPropValue: "hello",
            clsValue: "parentClass",
            nameValue: "parentName"
        },
        components: {
            SonComponent
        }
    });

    vm.$data.notPropValue = "hai";
</script>
渲染代码如下:
<div notprop="hai" class="parentclass subclass" name="parentName">子组件</div>
div 中的 notprop="hai" 是从 son-component:notprop="notPropValue"/son-component 传递过去的。

1) 替换/合并已有的attribute

如果在子组件中也定义了非 prop 的 attribute,同时在使用组件的时候也定义了该 attribute,这时候最后的值,存在替换/合并问题。class 和 style 的值会被合并,其他属性值会被替换。

如上面的代码中,在 son-component 组件中定义了 class="subClass"name="subName",同时在组件使用的时候定义了:class="clsValue"和:name="nameValue",最后渲染的结果是 class="parentClass subClass"和name="parentName"。class 属性的值被合并了,而 name 属性的值只有一个:外面的值被替换了组件里面的 name 值。

2) 禁用Attribute继承

如果开发人员不希望组件的根元素继承 attribute,则可以在组件的选项中设置 inheritAttrs:false

因为继承的 attribute 只能作用到根元素上,如果需要将 attribute 继承到子组件的非根元素上,则可以使用 v-bind="$attrs" 将 attribute 绑定到子元素的非根元素上,代码如下:
<div id="app">
    <son-component test="Value" required placeholder="请输入姓名"></son-component>
</div>

<script>
Vue.component('SonComponent', {
    inheritAttrs: false,
    template: '<div><input type="text" v-bind="$attrs"/></div>'
});

const vm = new Vue({
    el: '#app'
});
</script>

注意,class 和 style 属性不在作用范围。

推荐阅读