vue-组件使用教程

组件API包括三部分: prop / 事件 / 插槽

  • prop: 允许外部环境传递数据到组件
  • 事件: 允许组件内部触发外部环境的方法
  • 插槽: 允许外部环境将额外的内容组合在组件中

字面量语法 vs 动态语法

使用字面语法方式传递值,是字符串,而使用动态语法传递的时候,会当作JavaScript表达式计算,如下例子:

<!-- 传递字符串 '1' -->
<comp some-prop="1"></comp>

<!-- 传递数字 1 -->
<comp :some-prop="1"></comp>

单向数据流

prop是单向绑定的,当父组件的属性变化时,将传导给子组件,但是反过来不行,这是为了防止组件无意间修改了父组件的状态,避免应用的数据流变得难以控制和理解。

但是在如下的场景中,我们常常需要进行修改,该如何(变相)实现:

  • prop作为初始值传入后,子组件想把它当作局部数据来用;
  • prop作为原始数据后,由子组件处理成其他数据输出。

解决方案如下:

  1. 定义一个局部变量,并用prop的值来初始化:
components: {
    'component-1': {
        props: ['initialNum'],
        data() {
            return {
                localNum: this.initialNum
            }
        },
        template: `<div>
                                <button @click="addOne()">{{ localNum }}</button>
                            </div>`,
        methods: {
            addOne: function () {
                this.localNum += 1;
            }
        }
    }
}
  1. 定义一个计算属性,处理prop的值并返回:
components: {
    'component-1': {
        props: ['initialNum'],
        template: `<div>
                                <p>{{ addOne }}</>
                            </div>`,
        computed: {
            addOne: function () {
                return this.initialNum + 1;
            }
        }
    }
}

注意:JavaScript中 对象数组 是引用类型,指向 同一个内存空间,如果prop是一个对象或数组,在子组件内部改变,会影响父组件的状态,此时会报错。

属性设置和校验

为组件的prop指定验证规则、默认值等信息,可以用对象的形式来定义prop,不能用字符串数组形式。下面列举几种设置组件prop的方式:

// 字符串数组形式
props: ['prop-1', 'prop-2'],

// 基础类型检测, null 指允许任何类型
props: {
    'prop-1': Number
}

// 可以是多种类型
props: {
    'prop-1': [Number, String]
}

// 必传,且是字符串
props: {
    'prop-1': {
        type: String,
        required: true
    }
}

// 有默认值
props: {
    'prop-1': {
        type: String,
        default: ''
    }
}

// 数组、对象的默认值应由一个工厂函数返回
props: {
    'prop-1': {
        type: Object,
        default: function () {
            return {}
        }
    }
}

// 自定义验证函数
props: {
    'prop-1': {
        validator: function (value) {
            return value > 10
        }
    }
}

type 可以是下面原生构造器: String / Number / Boolean / Function / Object / Array / Symbol
type 也可以是一个自定义构造函数,使用 instanceof 检测

当prop验证失败,vue会抛出警告(本地开发环境下)。注意prop会在组件实例创建之前进行校验,所以在default或validator函数里,在data/methods/computed等实例属性还无法使用。

自定义事件

上面讲述了父组件如何传递数据到子组件中,那么子组件如何跟父组件之间进行通信呢?这里介绍的是自定义事件来实现这个场景需求。

使用 v-on 绑定自定义事件

每个vue实例都实现了事件接口,即:

  • 使用 $on(eventName) 监听事件
  • 使用 $emit(eventName) 触发事件

可以看到这里的事件系统,貌似和浏览器的 EventTarget 很相似,但是注意这里的 $on$emitaddEventListenerdispatchEvent 不是别名的简称。

父组件可以在使用子组件的地方,直接用 v-on来监听子组件触发的事件,注意这里不能使用 $on 来监听,而必须使用 v-on 来绑定(可以使用语法糖 @ 来代替)。

$emit 触发事件时,也可以传递参数,形如: $emit("自定义的方法名", arg1, arg2 ...)
例子如下:

<template>
    <div>
        <p># 测试子组件传递数据到父组件</p>
        <p>父组件的数字: {{ number }}</p>
        <button-counter :number="number" @increment="incrementParentCounter"></button-counter>
    </div>
</template>

<script>
    export default {
        name: 'ComponentT1',
        data() {
            return {
                number: 1
            }
        },
        components: {
            'button-counter': {
                props: ['number'],
                data() {
                    return {
                        child_number: this.number
                    }
                },
                template: `<div>
                                        <button @click="incrementChildCounter()">点击修改子组件内容: {{ child_number }}</button>
                                    </div>`,
                methods: {
                    incrementChildCounter: function () {
                        let step = 2;
                        this.child_number += step;
                        this.$emit('increment', step);
                    }
                }
            }
        },
        methods: {
            incrementParentCounter: function (data) {
                this.number += data;
            }
        }
    }
</script>

使用自定义事件的表单输入组件

自定义事件可以用来创建自定义的表单输入组件,使用 v-model 来进行数据双向绑定。

<input v-model="something" />

与以下的代码是等价的

<input
    v-bind:value="something"
    v-on:input="something = $event.target.value" />

父组件中使用时,第二个语句其实是第一个语句的缩写而已:

<custom-input v-model="something"></custom-input>

<!-- 相当于如下的简写 -->

<custom-input 
    v-bind:value="something"
    v-on:input="something = arguments[0]">
</custom-input>

所以要让组件的 v-model 生效,它应该满足以下两点:

  • 接受一个 value 的 prop
  • 在有新的值时,触发 input 事件,并将新值作为参数

如下例子:

<template>
    <div>
        <custom-input v-model="something"></custom-input>
        <br />
        <div>
            <p>something: </p>
            <div>{{ something }}</div>
        </div>
    </div>
</template>

<script>
    import Vue from 'vue'

    Vue.component('custom-input', {
        props: ['something'],
        template: `<input type="text"
                                v-bind:value="something"
                                v-on:input="updateValue($event.target.value)"
                            />`
        methods: {
            updateValue: function (value) {
                this.$emit("input", value);
            }
        }
    })

    export default {
        name: 'ComponentT2',
        data() {
            return {
                something: ''
            }
        }
    }
</script>

注意上面提到组件 v-model 默认是使用 value prop 和 input 事件。那么在使用单选框、复选框等情况下,就不能直接使用v-model了,下面介绍通过在组件中,使用 model 选项来避免这种冲突。

<custom-checkbox v-model="cfg" value="some value"></custom-checkbox>

<!-- 上面语句等价于下面语句,但是需要显式的声明 checked 这个 prop,并且说明 model 绑定了change事件 -->

<custom-checkbox
    :checked="cfg"
    @change="val => {cfg = val}"
    value="some value">
</custom-checkbox>

完整代码如下;

<template>
    <div>
        <custom-checkbox v-model="cfg" value="some value"></custom-checkbox>
        <div>
            <p>父组件中的变量 cfg: ></p>
            <div>{{ cfg }}</div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "CopmonentT3",
        data() {
            return {
                cfg: false
            }
        },
        components: {
            'custom-checkbox': {
                props: {
                    cfg: Boolean,
                    value: String
                },
                data() {
                    return {
                        isChecked: this.cfg
                    }
                },
                template: `<input type="checkbox" @change="changeCheck(ischecked)" />,
                model: {
                    prop: "cfg",
                    event: "change"
                },
                methods: {
                    changeCheck: function (state) {
                        this.isChecked = !state;
                        this.$emit("change", this.isChecked);
                    }
                }

            }
        }
    }
</script>

非父子组件的通信

有时候,非父子关系的两个组件之间也需要通信,在简单的情况下,可以使用一个空的 Vue 实例作为事件总线。
然后一个组件负责触发,另外一个组件在声明周期mounted的时候,创建一个监听事件,例子如下:

<template>
    <div>
        <component-1 v-on:id-selected="getdate"></component-1>
        <component-2></component-2>
    </div>
</template>

<script>
    import Vue from 'vue'

    var bus = new Vue();

    export default {
        name: 'ComponentT4',
        data() {
            return {}
        },
        components: {
            'component-1': {
                data() {
                    return {}
                },
                template: `<button class="comp1" @click="comfuna">组件1</button>`,
                methods: {
                    comfuna() {
                        bus.$emit('id-selected', 1);
                        this.$emit('id-selected', 1);
                    }
                }
            },
            'component-2': {
                data() {
                    return {}
                },
                template: `<div class="comp2">组件2</div>`,
                mounted() {
                    bus.$on('id-selected', function (id) {
                        console.info('在组件2中得到的值: ', id);    
                    })
                }
            }
        },
        methods: {
            getdate(value) {
                console.info("父组件中得到的值: ", value);
            }
        }
    }
</script>

插槽slot分发内容

为了组合父组件的内容与子组件自己的模版,我们可以使用 slot 元素作为原始内容的插槽。
具体我们可以使用的有:

  • 单个插槽
  • 具名插槽
  • 作用于插槽

下面使用代码的方式来使用插槽,例子如下:

<template>
    <div>
        <component-scope msg="我是来自父组件的内容">
            <template slot-scope="props" slot="test1">
                <p>此内容来自于父组件</p>
                <p>但是,text内容来自于子组件: {{ props.text }}, value内容来自于子组件: {{ props.value }}</p>
            </template>
        </component-scope>
    </div>
</template>

<script>
    export default {
        name: 'ComponentT5',
        data() {
            return {}
        },
        components: {
            'component-scope': {
                props: ['msg'],
                data() {
                    return {}
                },
                template: `<div>
                                        <slot name="test1" text="我是来自子组件的text" value="我是来自子组件的value">我是test1</slot>
                                        <slot name="test2">我是test2</slot>
                                        <span>我是子组件,获取到来自父组件的内容有: {{ msg }}</span>
                                    </div>`
            }
        }
    }

</script>

在父组件中,具有特殊特性 slot-scope 的元素,表示它是作用域插槽的模版,slot-scope 的值将被用作一个临时变量名,此变量接收从子组件传递过来的 prop 对象,例如上面的 text / value

动态组件

动态组件的功能是可以动态的切换多个组件,场景比如是tab中切换时,切换到自己想要的子组件中。
使用方法:通过 <component> 元素,并对其 is 特性进行动态绑定,你可以在同一个挂载点动态切换多个组件:

<template>
    <div>
        <select v-model="chose_item">
            <option>component-1</option>
            <option>component-2</option>
            <option>component-3</options>
        </select>

        <div>
            <component :is="chose_item">
            </component>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'ComponentT6',
        data() {
            return {
                chose_item: ''
            }
        },
        components: {
            'component-1': {
                template: `<div>我是component-1模版内容</div>`
            },
            'component-2': {
                template: `<div>我是component-2模版内容</div>`
            },
            'component-3': {
                template: `<div>我是component-3模版内容</div>`
            }
        }
    }
</script>

上面例子中,当切换 chose_item 值时,下面 component 中会动态切换显示指定的子组件。

上面例子中,组件切换时,都是重新渲染的,那如果想要保留组件的状态,避免重新渲染,可以使用 keep-alive 来解决这个问题,用法如下:

<keep-alive>
    <component :is="chose_item">
    </component>
</keep-alive>

子组件的引用

如果想在javasc中直接访问子组件,可以使用 ref 为子组件指定一个引用 ID,例如如下:

<template>
    <div id="parent">
        <user-profie ref="profile"></user-profile>
    </div>
</template>

<script>
    var parent = new Vue({el: '#parent'});

    // 访问子组件实例
    var child = parent.$refs.profile;
</script>

其他

参考: vue从入门到进阶:组件Component详解(六)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 什么是组件? 组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装...
    youins阅读 9,441评论 0 13
  • 组件(Component)是Vue.js最核心的功能,也是整个架构设计最精彩的地方,当然也是最难掌握的。...
    六个周阅读 5,576评论 0 32
  • vue概述 在官方文档中,有一句话对Vue的定位说的很明确:Vue.js 的核心是一个允许采用简洁的模板语法来声明...
    li4065阅读 7,185评论 0 25
  • 组件注册 组件名 在注册一个组件的时候,我们始终需要给它一个名字。 该组件名就是Vue.component的第一个...
    oWSQo阅读 396评论 0 1
  • 此文基于官方文档,里面部分例子有改动,加上了一些自己的理解 什么是组件? 组件(Component)是 Vue.j...
    陆志均阅读 3,799评论 5 14