组件

组件就是可复用的单元模块。

组件的data部分是一个函数,返回值是一个对象,在这个对象中传递要传递的值。

Vue.component('组件名称', {组件选项})
  • 组件名称遵循自定义组件命名规范:全小写、连字符(虽然驼峰式一般也没问题)
  • 组件选项与 new Vue 选项配置基本一致(也有一些细节的不同)

全局组件通过Vue对象进行创建,局部组件通过在组件的内部属性components创建。

new Vue({
  ...,
  components: {
  	'组件名称': {组件选项}	
	}
})

data

在非 new Vue 的组件中,data 必须为函数,函数返回值必须是一个对象,作为组件的最终 data

components: {
    'xk-circle': {
            data() {
            return {pi: 3.14}
        },
    }
}

props

组件中内部私有数据存储中组件 data 中,通过外部传入的数据,则通过 props 选项接收

  • 如果传入的 props 值为一个表达式,则必须使用 v-bind
  • 组件中的 dataprops 数据都可以通过组件实例进行直接访问
  • data 中的 keyprops 中的 key 不能冲突

组件间的通信

image-20201104080703036

父组件向子组件传值

父组件向子组件传值使用props即可。

子组件通过props属性接收外部传来的值(即父组件传来的值)

Vue.component('menu-item', {
    props: ['title'],
    data: function () {
        return {
            msg: '子组件'
        }
    },
    template: '<div>{{msg}}---{{title}}</div>'
})
var vm = new Vue({
    el: '#app',
    data: {
        pmsg: "父组件",
        ptitle: '来自父组件的值'
    }
});

传值通过标签属性传值:

<!-- 不使用v-bind绑定,则传递字符串 -->
<menu-item title='来自父组件的值123'></menu-item>
<!-- 使用v-bind绑定,则传递表达式 -->
<menu-item :title='ptitle'></menu-item>
<!-- 不传递任何值 -->
<menu-item></menu-item>

image-20201104081344935

子组件向父组件传值

子组件无法直接向父组件传值(修改父组件的值),因为props是单向数据流。因此父组件向子组件传值需要通过事件触发同值父组件,父组件定义事件来修改值。

  1. 定义子组件

    Vue.component('menu-item', {
        props: ['parr'],
        template: `
    <div>
    <ul>
    <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
    </ul>
    <button @click='$emit("enlarge-text",5)'>扩大父组件中字体大小</button>
    </div>
    `
    });

    为子组件的按钮监听一个点击事件,当点击时触发自定义事件并传入5

  2. 调用组件

    <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>

    为组件绑定自定义事件事件,当事件发生时触发父级修改值的函数,并将事件对象传入

  3. 在父级中定义修改值的函数handle

    var vm = new Vue({
        el: '#app',
        data: {
            pmsg: '父组件中内容',
            parr: ['apple', 'orange', 'banana'],
            fontSize: 10
        },
        methods: {
            handle: function (val) {
                // 扩大字体大小
                this.fontSize += val;
            }
        }
    });

组件间传值

  1. 开辟一个新的VUE实例

    var hub = new Vue()
  2. 定义子组件A

    var sona = {
        template: `<div>
    <p>组件A --- {{ msg }}</p>
    <button @click='change'>按钮</button></div > `,
        data: function () {
            return {
                msg: '这是组件A的消息',
            }
        },
        methods: {
            change: function () {
                hub.$emit('aevent', '来自A修改的消息')
            }
        },
    }

    为子组件A添加绑定一个事件,当点击按钮时,触发自定义方法change。在change方法中通过触发事件aevent并将值传递过去来修改接下来子组件B的信息

  3. 定义子组件B

    var sonb = {
        template: `<div>组件B---{{msg}}</div>`,
        data: function () {
            return {
                msg: '这是组件B的消息',
            }
        },
        mounted: function () {
            hub.$on('aevent', (val) => {
                this.msg = val
            })
        },
    }

    在子组件B中通过mounted监听aevent事件的触发。从而修改子组件B的值。

props 验证

组件的 props 就是组件的参数,为了确保传入的数据在可控的合理范围内,我们需要对传入的 props 的值类型进行必要的验证

Vue.component('my-component', {
    props: {
        // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
        propA: Number,
        // 多个可能的类型
        propB: [String, Number],
        // 必填的字符串
        propC: {
            type: String,
            required: true
        },
        // 带有默认值的数字
        propD: {
            type: Number,
            default: 100
        },
        // 带有默认值的对象
        propE: {
            type: Object,
            // 对象或数组默认值必须从一个工厂函数获取
            default: function () {
                return { message: 'hello' }
            }
        },
        // 自定义验证函数
        propF: {
            validator: function (value) {
                // 这个值必须匹配下列字符串中的一个
                return ['success', 'warning', 'danger'].indexOf(value) !== -1
            }
        }
    }
})

非prop 特性

一个非 prop 特性是指传向一个组件,但是该组件并没有相应 prop 定义的特性,这些 props 会被自动添加到组件的根元素上

组件的双向数据绑定

v-model

为组件定义v-model属性实现双向数据绑定,但并不推荐这种方法。因为这种方式进行的双向数据绑定并不明确到底绑定的是什么数据。

  1. 定义组件

    Vue.component('xk-radio', {
        props: ['value', 'checkedValue'],
        model: {
            // 要想实现双绑定的属性,比如 checkedValue
            prop: 'checkedValue',
            // 触发 prop 修改的事件
            event: 'click'
        },
        template: `
    <div
    :class="{
    'kkb-radio': true,
    'checked': value === checkedValue
    }"
    @click="check"
    >
    {{value}}
    </div>
    `,
        methods: {
            check() {
                // 触发一个事件,通过事件去调用父级绑定的函数
                this.$emit('click', this.value);
                // 这里click与 model设置的event是一致
                // 会自动的把 this.value 更新到  model 中 props 指定的 属性上
            }
        }
    });
  2. 接下来调用组件

    <xk-radio value="javascript" v-model="val"></xk-radio>
    <xk-radio value="css" v-model="val"></xk-radio>

.sync

.sync修饰符同样可以做到双向数据绑定,相比于v-model更能直观看到到底绑定的是什么数据。

  1. 定义子组件

    Vue.component('xk-radio', {
        props: ['value', 'checkedValue'],
        data() {
            return {
            }
        },
        template: `
    <div
    :class="{
    'xk-radio': true,
    'checked': value === checkedValue
    }"
    @click="check"
    >
    {{value}}
    </div>
    `,
        methods: {
            check() {
                this.$emit('update:checkedValue', this.value);
            }
        }
    });

    为组件元素添加点击事件,点击时触发check()方法。在这方法中在通过$emit触发事件实现更新。

    这里事件名称要使用 update 加上 prop 名称 的格式。第二个参数即要更新的值。

  2. 组件调用

    <xk-radio value="css" :checked-value.sync="val"></xk-radio>

插槽

插槽即组件中传递的值,会自动填充到组件中定义的<slot></slot>位置。其中组件的<slot></slot>位置也可以传入值,其代表默认内容,也就是没有传值时的内容。

<body>
    <div id="app">
        <alert-box>有bug发生</alert-box>
        <alert-box></alert-box>
    </div>
    <script type="text/javascript" src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component('alert-box', {
            template: `
<div>
<strong>Error:<strong>
<slot>默认内容</slot>    
        </div>
`
        })
        var vm = new Vue({
            el: '#app',

        });

    </script>
</body>

image-20201104090906019

具名插槽

具名插槽即为插槽定义name属性,当填充时通过name属性确定填充的位置。

  1. 组件定义

    Vue.component('base-layout', {
        template: `
    <div>
    <header>
    <slot name='header'></slot>    
    </header>  
    <main>
    <slot></slot>    
    </main>  
    <footer>
    <slot name='footer'></slot>    
    </footer>  
    </div>
    `
    })
  2. 组件调用

    组件调用填充插槽必须通过template标签。在template标签上定义属性表示将整个template里的内容填充到该名称的插槽内。

    <base-layout>
        <template>
            <p v-slot="header">标题</p> <!-- 只有这个标签会填充到名称为header插槽内-->
            <p>哈哈哈</p> <!-- 会填充到默认插槽内-->
        </template>
        <template>
            <p>主要内容</p>
        </template>
        <template v-slot="footer">
            <!-- 此标签下的内容会全部填充到名称为footer插槽内-->
            <p>尾部</p>
            <p>尾部哈哈哈哈哈</p> <!-- 会填充到默认插槽内-->
        </template>
    </base-layout>

    image-20201104091507833

作用域插槽

对比前面两种插槽,也可以叫它带数据的插槽。作用域插槽要求在slot上面绑定数据。

  1. 定义组件

    Vue.component('fruit-list', {
        props: ['list'],
        template: `
    <div>
    <li :key='item.id' v-for='item in list'>
    <slot :info='item'>{{item.name}}</slot>
    </li>
    </div>
    `
    });

    为插槽绑定了以名为info值为item的数据。

  2. 调用时获取到数据

    <fruit-list :list='list'>
        <template v-slot='slotProps'>
            <strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong>
            <span v-else>{{slotProps.info.name}}</span>
            <span>{{slotProps}}</span>
        </template>
    </fruit-list>

    此时可以在template标签上通过v-slot命令定义一个变量名,这个变量名的值为定义组件时传过来的值。

其他

非 prop 特性

一个非 prop 特性是指传向一个组件,但是该组件并没有相应 prop 定义的特性,这些 props 会被自动添加到组件的根元素上

替换/合并已有的特性

默认情况下,非prop 特性的属性会覆盖组件根元素上同名的内容,但是针对 styleclass 有特殊的处理,它们会合并(同名样式还是会覆盖)

禁用特性继承

如果你不希望组件的根元素继承特性,你可以在组件的选项中设置 inheritAttrs: false,我们可以通过组件的 this.$attrs 来获取这些属性

注意 inheritAttrs: false 选项不会影响 styleclass 的绑定