美文网首页vue
vue-组件使用教程

vue-组件使用教程

作者: jshan | 来源:发表于2018-12-20 14:57 被阅读0次

    组件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详解(六)

    相关文章

      网友评论

        本文标题:vue-组件使用教程

        本文链接:https://www.haomeiwen.com/subject/vtmykqtx.html