博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Vue 组件详解
阅读量:6266 次
发布时间:2019-06-22

本文共 11732 字,大约阅读时间需要 39 分钟。

参考书籍:《Vue.js 实战》

组件与复用

为什么使用组件

Vue 的组件就是提高复用性,让代码可复用。

组件用法

组件需要注册后才可以使用,注册有全局注册和局部注册两种方式。

  1. 全局注册后,任何 Vue 实例都可以使用。

    // 要在父实例中使用这个组件,必须要在实例创建前注册。Vue.component('my-component', {    template: '
    my component
    '});var app = new Vue({ el: '#app'});
  2. 在 Vue 实例中,使用 components 选项可以局部注册组件,注册后的组件只有在该实例作用域下有效。

    var Child = {    template: '
    my component
    '};var app = new Vue({ el: '#app', components: { 'my-component': Child }});

渲染后的结果是:

my component

Vue 组件的模板在某些情况下会收到 HTML 的限制,比如 <table> 内规定只允许是 <tr><td> 等这些表格元素,所以在 <table> 内直接使用组件是无效的,这种情况下可以使用特殊的

is 属性来挂载组件。

Vue.component('my-component', {    template: '
my component
'});var app = new Vue({ el: '#app'});

渲染后的结果是:

my component

在组件中使用 data 时,必须是函数,然后将数据 return 出去。

Vue.component('my-component', {    template: '
my component
', data() { return { message: 'message' } }});

使用 props 传递数据

基本用法

组件不仅仅是要把模板的内容进行复用,更重要的是组件间要进行通信。通常父组件的模板中包含子组件,父组件要正向地向子组件传递数据或参数,子组件接收到后根据参数的不同来渲染不同的内容或执行操作。这个正向传递数据的过程就是通过 props 来实现的。

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

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

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

Vue.component('my-component', {    props: ['myMessage'],    template: '
{
{ myMessage}}
'});var app = new Vue({ el: '#app', data: { parentMessage: '' }});

渲染后的结果是:

dataMes

这里用 v-model 绑定了父级的数据 parentMessage,当通过输入框任意输入时,子组件接收到的 props 也会实时响应,并更新组件模板。

单向数据流

Vue 通过 props 传递数据是单向的,也就是父组件数据变化时会传递给子组件,但是反过来不行。之所以这样设计,是尽可能将父子组件解耦,避免子组件无意中修改了父组件的状态。

业务中会经常遇到两种需要改变 prop 的情况。

  1. 一种是父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改。

    Vue.component('my-component', {    props: ['initCount'],    template: '
    {
    { count }}
    ', data() { return { count: this.initCount } }});var app = new Vue({ el: '#app'});

    组件中声明了数据 count,它在组件初始化时会获取来自父组件的 initCount,之后就与之无关了,只用维护 count,这样就可以避免直接操作 initCount

  2. 另一种情况就是 prop 作为需要被转变的原始值传入。

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

    因为用 CSS 传递宽度要带单位(px),但是每次都写太麻烦了,而且数值计算一般是不带单位的,所以统一在组件内使用计算属性就可以了。

数据验证

Vue.component('my-component', {    props: {        propA: Number,        propB: [String, Number],        propC: {            type: Boolean,            default: true        },        propD: {            type: Number,            required: true        },        // 如果是数组或对象,默认值必须是一个函数来返回        propE: {            type: Array,            default() {                return [];            }        },        propF: {            validator(value) {                return value > 10;            }        }    }});

组件通信

组件关系可分为父子组件通信、兄弟组件通信和跨级组件通信。

自定义事件

当子组件需要向父组件传递数据时,就要用到自定义事件。

子组件用 $emit() 来触发事件,父组件用 $on 来监听子组件的事件。

父组件也可以直接在子组件的自定义标签上使用 v-on 来监听子组件触发的自定义事件。

总数:{

{ total }}

Vue.component('my-component', {    template: `        
`, data() { return { counter: 0 } }, methods: { handleIncrease() { this.counter++; this.$emit('increase', this.counter); }, handleReduce() { this.counter--; this.$emit('reduce', this.counter); } }});var app = new Vue({ el: '#app', data: { total: 0 }, methods: { handleGetTotal(total) { this.total = total; } }});

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

除了用 v-on 在组件上监听自定义事件外,也可以监听 DOM 事件,这时可以用 .native 修饰符表示监听的是一个原生事件,监听的是该组件的根元素。

使用 v-model

Vue 可以在自定义组件上使用 v-model 指令。

总数:{

{ total }}

Vue.component('my-component', {    template: '',    data() {        return {            counter: 0        }    },    methods: {        handleClick() {            this.counter++;            this.$emit('input', this.counter);        }    }});var app = new Vue({    el: '#app',    data: {        total: 0    }});

在使用组件的父级,并没有在 <my-component> 使用 @input="handler",而是直接用了 v-model 绑定的一个数据 total。这也可以称作是一个语法糖,因为上面的示例可以间接地用自定义事件来实现:

总数:{

{ total }}

// 省略组件代码var app = new Vue({    el: '#app',    data: {        total: 0    },    methods: {        handleGetTotal() {            this.total = total;        }    }});

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

总数:{

{ total }}

Vue.component('my-component', {    props: ['value'],    template: '',    methods: {        updateValue(event) {            this.$emit('input', event.target.value);        }    }});var app = new Vue({    el: '#app',    data: {        total: 0    },    methods: {        handleReduce() {            this.total--;        }    }});

实现这样一个具有双向绑定的 v-model 组件要满足下面两个条件:

  1. 接收一个 value 属性。
  2. 在有新的 value 时触发 input 事件。

非父子组件通信

非父子组件一般有两种,兄弟组件和跨多级组件。

在 Vue 中,推荐使用一个空的 Vue 实例作为中央事件总线(bus),也就是一个中介。

{
{ message }}
var bus = new Vue();Vue.component('component-a', {    template: '',    methods: {        handleEvent() {            bus.$emit('on-message', '来自组件 component-a 的内容');        }    }});var app = new Vue({    el: '#app',    data: {        message: ''    },    mounted() {        var _this = this;                // 在实例初始化时,监听来自 bus 实例的事件        bus.$on('on-message', function(msg) {            _this.message = msg;        });    }});

首先创建一个名为 bus 的空 Vue 实例,然后定义全局组件 component-a,最后创建 Vue 实例 app。在 app 初始化时,监听了来自 bus 的事件 on-message,而在组件 component-a 中,点击按钮会通过 bus 把事件 on-message 发出去,此时 app 就会接收到来自 bus 的事件,进而在回调里完成自己的业务逻辑。

这种方法巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟和跨级。如果深入使用,可以扩展 bus 实例,给它添加 datamethodscomputed 等选项,这些都是可以共用的,在业务中,尤其是协同开发时非常有用,因为经常需要共享一些通用的信息,比如用户登录的昵称、性别、邮箱和授权等。只需子安初始化时让 bus 获取一次,任何时间、任何组件就可以从中直接使用,在单页面富应用(SPA)中会很实用。

除了中央事件总线 bus 外,还有两种方法可以实现组件间通信:父链和子组件索引。

父链

在子组件中,使用 this.$parent 可以直接访问该组件的父实例或组件,父组件也可以通过 this.$children 访问它所有的子组件,而且可以递归向上或向下无限访问,直到根实例或最内层的组件。

{
{ message }}
Vue.component('component-a', {    template: '',    methods: {        handleEvent() {            // 访问到父链后,可以做任何操作,比如直接修改数据            this.$parent.message = '来自组件 component-a 的内容'        }    }});var app = new Vue({    el: '#app',    data: {        message: ''    }});

尽管 Vue 允许这样操作,但在业务中,子组件应该尽可能避免依赖父组件的数据,更不应该去主动修改它的数据,因为这样使得父子组件耦合,只看父组件,很难理解父组件的状态,因为它可能被任意组件修改,理想情况下,只有组件自己能修改它的状态。父子组件最好还是通过 props$emit() 来通信

子组件索引

当子组件较多时,通过 this.$children 来一一遍历出我们需要的一个组件实例时比较困难的,尤其是组件动态渲染时,它们的序列是不固定的。Vue 提供了子组件索引的方法,用特殊的属性 ref 来为子组件指定一个索引名称。

Vue.component('component-a', {    template: '
子组件
', data() { return { message: '子组件内容' } }});var app = new Vue({ el: '#app', methods: { handleRef() { // 通过 $refs 来访问指定的实例 var msg = this.$refs.comA.message; console.log(msg); } }});

提示:$refs 只在组件渲染完成后才填充,并且它是非响应式的。它仅仅作为一个直接访问子组件的应急方案,应当避免在模板或计算属性中使用 $refs

使用 slot 分发内容

什么是 slot

下面是一个常规的网站布局组件化后的机构:

当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到 slot,这个过程叫做内容分发(transclusion)。以 <app> 为例,它有两个特点:

  1. <app> 组件不知道它的挂载点会有什么内容。挂载点的内容由 <app> 的父组件决定。
  2. <app> 组件很可能有它自己的模板。

props 传递数据、events 触发事件和 slot 内容分发就构成了 Vue 组件的 3 个 API来源,再复杂的组件也是由这 3 部分构成的。

作用域

父组件模板的内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。

Vue.component('child-component', {    template: '
子组件
'});var app = new Vue({ el: '#app', data: { showChild: true }});

这里的状态 showChild 绑定的是父组件的数据,如果想在子组件上绑定,应该是:

Vue.component('child-component', {    template: '
子组件
', data() { return { showChild: true } }});var app = new Vue({ el: '#app'});

因此,slot 分发的内容,作用域是在父组件上的。

slot 用法

单个 slot

在子组件内使用特殊的 <slot> 元素就可以为这个子组件开启一个 slot(插槽),在父组件模板里,插入在子组件标签内的所有内容将替代子组件的 <slot> 标签及它的内容。

分发的内容

更多分发的内容

Vue.component('child-component', {    template: `        

如果父组件没有插入内容,我将作为默认出现

`});var app = new Vue({ el: '#app'});

上例渲染后的结果是:

分发的内容

更多分发的内容

注意:子组件 <slot> 内的备用内容,它的作用域是子组件本身。

具名 slot

<slot> 元素指定一个 name 后可以分发多个内容,具名 slot 可以与单个 slot 共存。

标题

正文内容

更多的正文内容

底部信息
Vue.component('child-component', {    template: `        
`});var app = new Vue({ el: '#app'});

上例渲染后的结果是:

标题

正文内容

更多的正文内容

注意:如果没有指定默认的匿名 slot,父组件内多余的内容片段都将被抛弃。

作用域插槽

作用域插槽是一种特殊的 slot,使用一个可以复用的模板替换已渲染元素。

Vue.component('child-component', {    template: `        
`});var app = new Vue({ el: '#app'});

观察子组件的模板,在 <slot> 元素上有一个类似 props 传递数据给组件的写法 msg="xxx",将数据传到了插槽。父组件中使用了 <template> 元素,而且拥有一个 scope="props" 的特性,这里的 props 只是一个临时变量,就像 v-for="item in items" 里面的 item 一样。template 内可以通过临时变量 props 访问来自子组件插槽的数据 msg

上例渲染后的结果是:

来自父组件的内容

来自子组件的内容

作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项。

Vue.component('my-list', {    props: {        books: {            type: Array,            default() {                return [];            }        }    },    template: `        
`});var app = new Vue({ el: '#app', data: { books: [ { name: '《book1》' }, { name: '《book2》' }, { name: '《book3》' } ] }});

子组件 my-list 接收一个来自父级的 prop 数组 books,并且将它在 namebookslot 上使用 v-for 指令循环,同时暴露一个变量 bookName

此例的用意主要是介绍作用域插槽的用法,并没有加入使用场景,而作用域插槽的使用场景既可以复用子组件的 slot,又可以是 slot 内容不一致。如果此例还在其他组件内使用,<li> 的内容渲染权是由使用者掌握的,而数据却可以通过临时变量(比如 props)从子组件内获取。

访问 slot

Vue 提供了用来访问被 slot 分发的内容的方法 $slots

标题

正文内容

更多的正文内容

底部信息
Vue.component('child-component', {    template: `        
`, mounted() { var header = this.$slots.header, main = this.$slots.default, footer = this.$slots.footer; console.log(footer); console.log(footer[0].elm.innerHTML); }});var app = new Vue({ el: '#app'});

通过 $slots 可以访问某个具名 slotthis.$slots.default 包括了所有没有被包含在具名 slot 中的节点。

$slots 在业务中几乎用不到,在用 render 函数创建组件时会比较有用,但主要还是用于独立组件开发中。

转载地址:http://vuvpa.baihongyu.com/

你可能感兴趣的文章
jacky自问自答-java并发编程
查看>>
Struts2+JSON数据
查看>>
zTree实现单独选中根节点中第一个节点
查看>>
Cocos2D-x设计模式发掘之中的一个:单例模式
查看>>
很强大的HTML+CSS+JS面试题(附带答案)
查看>>
用树莓派实现RGB LED的颜色控制——C语言版本号
查看>>
VC2012编译CEF3-转
查看>>
java 自己定义异常,记录日志简单说明!留着以后真接复制
查看>>
Android 使用AIDL实现进程间的通信
查看>>
机器学习(Machine Learning)&深度学习(Deep Learning)资料
查看>>
jquery的图片轮播 模板类型
查看>>
C# 获取文件名及扩展名
查看>>
Web安全学习计划
查看>>
输出有序数组的连续序列范围
查看>>
zinnia项目功能分析
查看>>
windows cmd for paramiko
查看>>
SQL经典面试题集锦
查看>>
View学习(一)-DecorView,measureSpec与LayoutParams
查看>>
色彩力量!21款你应该知道的优秀品牌设计
查看>>
SDUT 3503 有两个正整数,求N!的K进制的位数
查看>>