组件

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

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

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

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

1
2
3
4
5
6
new Vue({
...,
components: {
'组件名称': {组件选项}
}
})

data

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

1
2
3
4
5
6
7
components: {
'xk-circle': {
data() {
return {pi: 3.14}
},
}
}

props

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

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

组件间的通信

image-20201104080703036

父组件向子组件传值

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component('menu-item', {
props: ['title'],
data: function () {
return {
msg: '子组件'
}
},
template: '<div>{{msg}}---{{title}}</div>'
})
var vm = new Vue({
el: '#app',
data: {
pmsg: "父组件",
ptitle: '来自父组件的值'
}
});

传值通过标签属性传值:

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

image-20201104081344935

子组件向父组件传值

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

  1. 定义子组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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. 调用组件

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var vm = new Vue({
    el: '#app',
    data: {
    pmsg: '父组件中内容',
    parr: ['apple', 'orange', 'banana'],
    fontSize: 10
    },
    methods: {
    handle: function (val) {
    // 扩大字体大小
    this.fontSize += val;
    }
    }
    });

组件间传值

  1. 开辟一个新的VUE实例

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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 的值类型进行必要的验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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. 定义组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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. 接下来调用组件

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

.sync

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

  1. 定义子组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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. 组件调用

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

插槽

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<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. 组件定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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里的内容填充到该名称的插槽内。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <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. 定义组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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. 调用时获取到数据

    1
    2
    3
    4
    5
    6
    7
    <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 的绑定