Vue

vue 组件化的入门详解

2019-02-01  本文已影响11人  大脸猫的前端之路

组件

组件(Component)是Vue.js最核心的功能,也是整个框架设计最精彩的地方,当然也是最难掌握的。组件可以扩展HTML元素,封装可重用的代码。为什么使用组件?当然是为了提高代码的重用性;

组件的用法

组件是需要注册才可以使用的,注册有全局注册和局部注册两种方式。全局注册后,任何Vue实例都可以使用,

Vue.component('my-component', {
// 选项
})

// my-component就是注册的组件自定义标签名称,推荐使用小写加减号分割的形式命名。要在父实例中使用这个组件,必须要在实例创建前注册,之后就可以用<my-component></my-component>的形式来使用组件了。

<div id="app">
    <my-component></my-component>
</div>
<script>
Vue.component('my-component', {
//选项
});
var app = new Vue({
    el: '#app'
})
</script>

此时的页面还是空白的,因为我们注册的组件没有任何内容,在组件选项中添加template就可以显示内容了。此时注意template的DOM结构必须被一个元素包含。

Vue.component('my-component', {
    template: '<div>这里是组件的内容</div>'
});

局部注册的组件只有在该实例作用域下有效。组件中也可以使用components选项来注册,使组件可以嵌套,示例如下:

<div id="app">
    <my-component></my-component>
</div>
<script>
var child = {
    template: '<div>局部注册组件的内容</div>'
}
var app = new Vue({
    el: '#app',
    components: {
        'my-component': child
    }
})
</script>

Vue组件的模板在某些情况下会受到HTML的限制,比如table标签内只允许使用tr、th等表格元素,所以在table内直接使用组件是无效的。在这种情况下,可以使用特殊的is属性来挂载组件,示例如下:

<table>
    <tbody is="my-component"></tbody>
</table>

tbody在渲染是会被替换为组件的内容,常用的限制元素如 ul、select等同理。

父组件向子组件传值

子组件通过props来声明需要从父组件接收的数据,props的值可以有两种,一种是字符串数组,一种是对象

1. 字符串数组用法

<div id="app">
    <my-component message="来自父组件的数据"></my-component>
</div>
<script>
Vue.component('my-component', {
    props: ['message'],
    template: '<div>{{ message }}</div>'
});
var app = new Vue({
    el: '#app'
})
</script>

渲染后的结果为

<div id="app">
    <div>来自父组件的数据</div>
</div>

props中声明的数据与组件中data函数return的数据主要区别是:props的来自父级,而data中的是组件自己的数据,作用域是组件本身,这两种数据都可以在模板template及计算属性computed和方法methods中使用。

在props中声明过的属性,不需要在data中重复声明,可直接使用

由于HTML特性不区分大小写,当使用DOM模板时,驼峰命名(camelCase)的props名称要转为短横分隔命名(kebab-case);

<div id="app">
    <my-component warning-text="提示信息"></my-component>
</div>
<script>
Vue.component('my-component', {
    props: ['warningText'],
    template: '<div>{{ message }}</div>'
});
var app = new Vue({
    el: '#app'
})
</script>

如果来自父级的数据并不是直接写死的,而是动态数据,这时可以使用指令v-bind来动态绑定props的值,当父组件的数据变化时,也会传递给子组件。

<div id="app">
    <input type="text" v-model="parentMsg">
    <my-component :message="parentMsg"></my-component>
</div>
<script>
Vue.component('my-component', {
    props: ['message'],
    template: '<div>{{ message }}</div>'
});
var app = new Vue({
    el: '#app',
    data() {
        return {
            parentMsg: ''
        }
    }
})
</script>

为了尽可能将父子组件解耦,避免子组件无意中修改了父组件的状态,Vue 2.x 之后通过props父组件向子组件传递数据是单向的。但有两种可改变prop的情况;

1. 子组件中重新声明变量,在组件初始化时会获取来自父组件的数据,之后只用修改子组件的变量。

<div id="app">
     <my-component :init-count="1"></my-component>
</div>
<script>
Vue.component('my-component', {
    props: ['initCount'],
    template: '<div>{{ count }}</div>',
    data() {
        return {
            count: this.initCount
        }
    }
});
var app = new Vue({
    el: '#app'
})
</script>

2. prop作为需要被转变的原始值传入时,可使用计算属性

<div id="app">
    <my-component :width="100"></my-component>
</div>
<script>
Vue.component('my-component', {
    props: ['width'],
    template: '<div :style="style">组件内容</div>',
    computed: {
        style() {
            return {
                width: this.width + 'px'
            }
        }
    }
});
var app = new Vue({
    el: '#app'
})
</script>

注意: 在JavaScript中,对象和数据是引用类型,指向同一个内存空间,所以props是对象和数组时,在子组件内改变是会影响父组件的。

  1. prop的对象用法 当prop需要验证时,就需要对象写法;
Vue.component('my-component', {
    props: {
        propA: {
            type: [String, Number], // 必须是字符串或数字类型
            default: '',           // 默认值为空字符串
            required: true,        // 必传
            validator(value) {     // 自定义一个验证函数
                return value > 10;
            }
        }
    }
});

验证的type类型可以是String、Number、Boolean、Object、Array、Function

组件通信

父子组件通信

我们已经知道父组件向子组件通信,通过props传递数据子组件用$emit来触发事件,那父子组件、兄弟组件、跨级组件又是如何通讯的呢?请接着往下看:

<div id="app">
    <p>总数:{{ total }}</p>
    <my-component @increse="handleIncrese"></my-component>
</div>
<script>
Vue.component('my-component', {
    props: ['width'],
    template: '\
        <div>\
            <Button  @click="increse">+1</Button>\
        <div>',
    data() {
        return {
            counter: 0
        }
    },
    methods: {
        increse() {
            this.counter++;
            this.$emit('increse', this.counter);
        }
    }
});
var app = new Vue({
    el: '#app',
    data() {
        return {
            total: 0
        }
    },
    methods: {
        handleIncrese(count) {
            this.total = count;
        }
    }
})
</script>

上面的示例中,在改变组件的data counter后,通过emit()再把它传递给父组件,父组件用v-on:recrese(示例使用的是语法糖),emit()方法的第一个参数是自定义事件名称,后面的参数是要传递的数据,可以不填或填多个。

非父子组件通信

Vue.js 2.x中,推荐使用空的vue实例作为中央事件总线(bus),

先看下下面的示例,试着运行下,会有什么样的结果呢?

<div id="app">
    <my-component :msg-a="otherMsg"></my-component>
    <other-component :msg-b="message"></other-component>
</div>
<script>
var bus = new Vue();
Vue.component('my-component', {
    props: ['msgA'],
    template: '\
        <div>\
        <p>A: {{msgA}}</p>\
            <Button @click="handleEvent">A兄弟组件</Button>\
        <div>',
    methods: {
        handleEvent() {
            bus.$emit('on-message', '来自A组件的内容');
        }
    }
});
Vue.component('other-component', {
    props: ['msgB'],
    template: '\
        <div>\
       <p> B:{{ msgB }}</p>\
            <Button @click="handleEvent1">B兄度组件</Button>\
        <div>',
    methods: {
        handleEvent1() {
            bus.$emit('other-message', '来自B组件内容');
        }
    }
});
var app = new Vue({
    el: '#app',
    data() {
        return {
            message: '',
            otherMsg: ''
        }
    },
    mounted() {
        // 在实例初始化时监听来自bus的事件
        bus.$on('on-message', (msg) => {
            this.message = msg;
        });
        bus.$on('other-message', (msg) => {
            this.otherMsg = msg;
        });
    }
})
</script>

示例中首先创建了一个名为bus的空Vue实例,里面没有任何内容,然后全局定义了组件components,在app实例化时,在生命周期mounted钩子函数里监听来自bus的事件,可将组件A的内容传至组件B,组件B的内容传至组件A,从而巧妙的实现了任何组件间的通信。包括:父子、兄弟、跨级组件之间。

子组件索引

Vue提供了特殊的属性ref来为子组件指定一个索引名称,在父组件内通过this.$refs来访问指定名称的子组件

<child-component ref="child"></child-component>

refs只在组件渲染完后后才填充,并且是非响应式的,应当避免在模板或计算属性中使用refs。

大家可以试着补全下面的代码,分别打印胡两个ref的内容看看会是什么?

<div id="app">
    <p ref="p"></p>
    <child-component ref="child></child-component> 
</div>
上一篇下一篇

猜你喜欢

热点阅读