Vue2.6已经更新了关于内容插槽和作用域插槽的API和用法,为了不误导大家,我把插槽的内容删除了。详情请看官网

2018-07-19更新:

  1. 更新作用域插槽的属性: scope -> slot-scope;
  2. 添加了对象解构。

今天看了下Vue官网上关于组件的教程,感觉内容还挺多,现在把组件中基本的知识梳理一下。


组件的基本使用

注册组件

注册组件就是利用Vue.component()方法,先传入一个自定义组件的名字,然后传入这个组件的配置。

 Vue.component('mycomponent',{
template: `<div>这是一个自定义组件</div>`,
data () {
return {
message: 'hello world'
}
}
})

如上方式,就已经创建了一个自定义组件,然后就可以在Vue实例挂在的DOM元素中使用它。

 <div id="app">
<mycomponent></mycomponent>
<my-component></my-component>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
},
components: {
'my-component': {
template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>`,
}
}
})
</script>

直接使用Vue.component()创建的组件,所有的Vue实例都可以使用。还可以在某个Vue实例中注册只有自己能使用的组件。

var app = new Vue({
el: '#app',
data: {
},
components: {
'my-component': {
template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>`,
}
}
})

模板的要求

注意:组件的模板只能有一个根元素。下面的情况是不允许的。

template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>
<button>hello</button>`,

组件中的data必须是函数

可以看出,注册组件时传入的配置和创建Vue实例差不多,但也有不同,其中一个就是data属性必须是一个函数。
这是因为如果像Vue实例那样,传入一个对象,由于JS中对象类型的变量实际上保存的是对象的引用,所以当存在多个这样的组件时,会共享数据,导致一个组件中数据的改变会引起其他组件数据的改变。

而使用一个返回对象的函数,每次使用组件都会创建一个新的对象,这样就不会出现共享数据的问题来了。

关于DOM模板的解析

当使用 DOM 作为模版时 (例如,将 el 选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模板内容。尤其像这些元素 <ul><ol><table><select> 限制了能被它包裹的元素,而一些像 <option> 这样的元素只能出现在某些其它元素内部。

在自定义组件中使用这些受限制的元素时会导致一些问题,例如:

<table>
<my-row>...</my-row>
</table>

自定义组件 <my-row> 被认为是无效的内容,因此在渲染的时候会导致错误。这时应使用特殊的 is 属性:

<table>
<tr is="my-row"></tr>
</table>

也就是说,标准HTML中,一些元素中只能放置特定的子元素,另一些元素只能存在于特定的父元素中。比如table中不能放置divtr的父元素不能div等。所以,当使用自定义标签时,标签名还是那些标签的名字,但是可以在标签的is属性中填写自定义组件的名字。

应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:

  • <script type="text/x-template">
  • JavaScript 内联模版字符串
  • .vue 组件

其中,前两个模板都不是Vue官方推荐的,所以一般情况下,只有单文件组件.vue可以忽略这种情况。

组件的属性和事件

在html中使用元素,会有一些属性,如class,id,还可以绑定事件,自定义组件也是可以的。当在一个组件中,使用了其他自定义组件时,就会利用子组件的属性和事件来和父组件进行数据交流。
<img src="/assets/images/props-events.png"/>

如上如所示,父子组件之间的通信就是 props down,events up,父组件通过 属性props向下传递数据给子组件,子组件通过 事件events 给父组件发送消息。
比如,子组件需要某个数据,就在内部定义一个prop属性,然后父组件就像给html元素指定特性值一样,把自己的data属性传递给子组件的这个属性。
而当子组件内部发生了什么事情的时候,就通过自定义事件来把这个事情涉及到的数据暴露出来,供父组件处理。

<my-component v-bind:foo="baz" v-on:event-a="doThis(arg1,...arg2)"></my-component>

如上代码,

  • foo<my-component>组件内部定义的一个prop属性,baz是父组件的一个data属性,
  • event-a是子组件定义的一个事件,doThis是父组件的一个方法

过程就是这样:

  • 父组件把baz数据通过prop传递给子组件的foo
  • 子组件内部得到foo的值,就可以进行相应的操作;
  • 当子组件内部发生了一些变化,希望父组件能知道时,就利用代码触发event-a事件,把一些数据发送出去
  • 父组件把这个事件处理器绑定为doThis方法,子组件发送的数据,就作为doThis方法的参数被传进来
  • 然后父组件就可以根据这些数据,进行相应的操作

属性Props

Vue组件通过props属性来声明一个自己的属性,然后父组件就可以往里面传递数据。

Vue.component('mycomponent',{
template: '<div>这是一个自定义组件,父组件传给我的内容是:{{myMessage}}</div>',
props: ['myMessage'],
data () {
return {
message: 'hello world'
}
}
})

然后调用该组件

<div id="app">
<mycomponent my-message="hello"></mycomponent>
</div>

注意,由于HTML特性是不区分大小写的,所以传递属性值时,myMessage应该转换成 kebab-case (短横线隔开式)my-message="hello"

v-bind绑定属性值

这里说一下v-bind绑定属性值的一个特性:一般情况下,使用v-bind给元素特性(attribute)传递值时,Vue会将""中的内容当做一个表达式。
比如:

<div attr="message">hello</div>

上面这样,div元素的attr特性值就是message

而这样

<div v-bind:attr="message">hello</div>

这里的message应该是Vue实例的data的一个属性,这样div元素的attr特性值就是message这个属性的值。

之所以说是一般情况,是因为classstyle特性并不是这样。用v-bind:classclass传入正常的类名,效果是一样的,因为对于这两个特性,Vue采用了合并而不是替换的原则。

动态绑定特性值

根据上面,想要把父组件的属性绑定到子组件,应该使用v-bind,这样,父组件中数据改变时能反映到子组件。
注意,根据父组件传递给子组件的属性类型的不同,当在子组件中更改这个属性时,会有以下两种情况:

  • 当父组件传递的属性是引用类型时,在子组件中更改相应的属性会导致父组件相应属性的更改。

       <div id="app2">
    <div>这是父组件的parentArray:{{parentArray}}</div>
    <my-component :child-array="parentArray"></my-component>
    </div>
    <script>
    Vue.component('my-component', {
    template: `
    <div>这是接收了父组件传递值的子组件的childArray: {{childArray}} <br>
    <button type="button" @click="changeArray">
    点击我改变父元素的parentArray</button>
    </div>`,
    props: ['childArray'],
    data () {
    return {
    counter: 1
    }
    },
    methods: {
    changeArray () {
    this.childArray.push(this.counter++)
    }
    }
    })
    new Vue({
    el: '#app2',
    data: {
    parentArray: []
    }
    })
    </script>
  • 当父组件传递值为基本类型时,在子组件中更改这个属性会报错。正确的做法是,在父组件中绑定属性值时,加上.sync修饰符。

    <my-component :child-array.sync="parentArray"></my-component>

    然后在子组件中改变相应的属性

        methods: {
    changeArray () {
    this.counter++
    this.$emit('update:childArray', this.counter)
    }
    }

子组件希望对传入的prop进行操作

一般来说,是不建议在子组件中对父组件中传递来的属性进行操作的。如果真的有这种需求,可以这样:

  1. 父组件传递了一个基本类型值,那么可以在子组件中创建一个新的属性,并以传递进来的值进行初始化,之后就可以操作这个新的属性了

    props: ['initialCounter'],
    data: function () {
    return { counter: this.initialCounter }
    }
  2. 父组件传递了一个引用类型值,为了避免更改父组件中相应的数据,最好是对引用类型进行复制。复杂的情况,肯定应该是深复制。

给子组件传递正确类型的值

同样是上面的原因,静态的给子组件的特性传递值,它都会把他当做一个字符串。

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

子组件中,特性的值是字符串 "1" 而不是 number 1。如果想传递正确的数值,应该使用v-bind传递,这样就能把传递的值当做一个表达式来处理,而不是字符串。

<!-- 传递实际的 number 1 -->
<comp v-bind:some-prop="1"></comp>

Prop验证

我们可以给组件的props属性添加验证,当传入的数据不符合要求时,Vue会发出警告。

Vue.component('example', {
props: {
// 基础类型检测 (`null` 意思是任何类型都可以)
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 value > 10
}
}
}
})

type 可以是下面原生构造器:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol

type 也可以是一个自定义构造器函数,使用 instanceof 检测。

 // 自定义Person构造器
function Person(name, age) {
this.name = name
this.age = age
}
Vue.component('my-component', {
template: `<div>名字: {{ person-prop.name }}, 年龄: {{ person-prop.age }} </div>`,
props: {
person-prop: {
type: Person // 指定类型
}
}
})
new Vue({
el: '#app2',
data: {
person: 2 // 传入Number类型会报错
}
})

非Prop类型的属性

也可以像在html标签中添加data-开头的自定义属性一样,给自定义组件添加任意的属性。而不仅限于data-*形式,这样做的话,Vue会把这个属性放在自定义组件的根元素上。一个自定义组件的模板只能有一个根元素。

覆盖非Prop属性

如果父组件向子组件的非prop属性传递了值,那么这个值会覆盖子组件模板中的特性。

<div id="app3">
<my-component2 att="helloParent"></my-component2>
</div>
<script>
Vue.component('my-component2', {
template: `<div att="helloChild">子组件原有的特性被覆盖了</div>`
})
new Vue({
el: '#app3'
})
</script>

上面渲染的结果是,divatt属性是helloParent
注意:前面已经提到过,覆盖原则对于classstyle不适用,而是采用了合并(merge)的原则。

<div id="app3">
<my-component2 att="helloParent" class="class2" style="color: red;"></my-component2>
</div>
<script>
Vue.component('my-component2', {
template: `<div att="helloChild" class="class1" style="background: yellow;">子组件原有的特性被覆盖了</div>`
})
new Vue({
el: '#app3'
})
</script>

上面的渲染结果是,div的类名是class1 class2,行内样式是color:red; background:yellow;

自定义事件

通过prop属性,父组件可以向子组件传递数据,而子组件的自定义事件就是用来将内部的数据报告给父组件的。

<div id="app3">
<my-component2 v-on:myclick="onClick"></my-component2>
</div>
<script>
Vue.component('my-component2', {
template: `<div>
<button type="button" @click="childClick">点击我触发自定义事件</button>
</div>`,
methods: {
childClick () {
this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据2')
}
}
})
new Vue({
el: '#app3',
methods: {
onClick () {
console.log(arguments)
}
}
})
</script>

如上所示,共分为以下步骤:

  1. 子组件在自己的方法中将自定义事件以及需要发出的数据通过以下代码发送出去

    this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据2')
    • 第一个参数是自定义事件的名字
    • 后面的参数是依次想要发送出去的数据
  2. 父组件利用v-on为事件绑定处理器

    <my-component2 v-on:myclick="onClick"></my-component2>

    这样,在Vue实例的methods方法中就可以调用传进来的参数了

注意: 在使用v-on绑定事件处理方法时,不应该传进任何参数,而是直接写v-on:myclick="onClick",不然,子组件暴露出来的数据就无法获取到了

绑定原生事件

如果想在某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on

<my-component v-on:click.native="doTheThing"></my-component>

探究v-model

v-model可以对表单控件实现数据的双向绑定,它的原理就是利用了绑定属性和事件来实现的。比如input控件。不使用v-model,可以这样实现数据的双向绑定:

<div id="app4">
<input type="text" v-bind:value="text" v-on:input="changeValue($event.target.value)">
{{text}}
</div>
<script>
new Vue({
el: '#app4',
data: {
text: '444'
},
methods: {
changeValue (value) {
this.text = value
}
}
})
</script>

上面的代码同样实现了数据的双向绑定。其本质就是:

  • inputvalue特性绑定到Vue实例的属性text上,text改变,input中的内容也会改变
  • 然后把表单的input事件处理函数设置为Vue实例的一个方法,这个方法会根据输入参数改变Vue中text`的值
  • 相应的,在input中输入内容时,触发了input事件,把event.target.value传给这个方法,最后就实现了改变绑定的数据的效果。

v-model就是上面这种方式的语法糖,也就是把上面的写法封装了一下,方便我们使用。

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

理解了v-model的内幕,也就可以把这个效果用在自定义表单组件上了。
来实现一个简单的只能输入hello的表单输入组件。

<div id="app5">
<my-component3 v-model="hello"></my-component3>
<div>{{hello}}</div>
</div>
<script>
Vue.component('my-component3', {
template: `<input ref="input" type="text" :value="value" @input="checkInput($event.target.value)">`,
props: ['value'],
methods: {
checkInput (value) {
var hello = 'hello'
if (!hello.includes(value)) {
this.$emit('input', hello)
this.$refs.input.value = hello
} else {
this.$emit('input', value)
}
}
}
})
new Vue({
el: '#app5',
data: {
hello: ''
}
})
</script>

定制组件的v-model

默认情况下,一个组件的 v-model 会使用 value 属性和 input 事件,但是诸如单选框、复选框之类的输入类型可能把 value 属性用作了别的目的。model 选项可以回避这样的冲突:

Vue.component('my-checkbox', {
model: {
prop: 'checked', // 将输入的特性改为checked
event: 'change' // 触发的自定义事件类型为change
},
props: {
checked: Boolean,
// this allows using the `value` prop for a different purpose
value: String
}
})

这样设置的话,

<my-checkbox v-model="foo" value="some value"></my-checkbox>

上面的代码就等同于

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

实际使用时,与之前不同的地方是:

  1. 把子组件中接收外部数据的prop属性改为checked
  2. 向父组件发出事件时,事件类型应改为change
Vue.component('my-component3', {
template: `<input ref="input" type="text" :value="checked" @input="checkInput($event.target.value)">`,
props: ['checked'], // 属性名改变
model: {
prop: 'checked',
event: 'change'
},
methods: {
checkInput (value) {
var hello = 'hello'
if (!hello.includes(value)) {
this.$emit('change', hello) // 事件类型改变
this.$refs.input.value = hello
} else {
this.$emit('change', value) // 事件类型改变
}
}
}
})

动态组件

通过使用保留的 <component> 元素,动态地绑定到它的 is 特性,可以让多个组件使用同一个挂载点,并动态切换:

 <div id="app6">
<select v-model="currentComponent">
<option value="home">home</option>
<option value="post">post</option>
<option value="about">about</option>
</select>
<component :is="currentComponent"></component>
</div>
<script>
new Vue({
el: '#app6',
data: {
currentComponent: 'home'
},
components: {
home: {
template: `<header>这是home组件</header>`
},
post: {
template: `<header>这是post组件</header>`
},
about: {
template: `<header>这是about组件</header>`
}
}
})
</script>

也可以直接绑定到组件对象上:

var Home = {
template: `<header>这是home组件</header>`
}
new Vue({
el: '#app6',
data: {
currentComponent: Home
}
})

保留切换出去的组件,避免重新渲染

如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:

<keep-alive>
<component :is="currentComponent">
<!-- 非活动组件将被缓存! -->
</component>
</keep-alive>

如何理解Vue中的组件?的更多相关文章

  1. 深刻理解Vue中的组件

    转载:https://segmentfault.com/a/1190000010527064 --20更新: Vue2.6已经更新了关于内容插槽和作用域插槽的API和用法,为了不误导大家,我把插槽的内 ...

  2. Vue中keep-alive组件的理解

    对keep-alive组件的理解 当在组件之间切换的时候,有时会想保持这些组件的状态,以避免反复重渲染导致的性能等问题,使用<keep-alive>包裹动态组件时,会缓存不活动的组件实例, ...

  3. 理解vue中的scope的使用

    理解vue中的scope的使用 我们都知道vue slot插槽可以传递任何属性或html元素,但是在调用组件的页面中我们可以使用 template scope="props"来获取 ...

  4. 理解Vue中的Render渲染函数

    理解Vue中的Render渲染函数 VUE一般使用template来创建HTML,然后在有的时候,我们需要使用javascript来创建html,这时候我们需要使用render函数.比如如下我想要实现 ...

  5. Vue 中的组件

    VUE中的组件 一个自定义的标签,vue就会把他看成一个组件,vue可以给这些标签赋予一定意义:一个页面就是一个组件 好处: 1.提高开发效率 2.方便重复使用 3.便于协同开发 4.更容易被管理和维 ...

  6. 简述vue中父子组件是怎样相互传递值的(基础向)

    前言 首先,你需要知道vue中父组件和子组件分别指的是什么?   父组件:vue的根实例——用new Vue()构造函数创建的vue实例(实例会有一个挂载点,挂载点里的所有内容可理解为父组件的内容) ...

  7. vue中自定义组件(插件)

    vue中自定义组件(插件) 原创 2017年01月04日 22:46:43 标签: 插件 在vue项目中,可以自定义组件像vue-resource一样使用Vue.use()方法来使用,具体实现方法: ...

  8. Vue中父子组件执行的先后顺序

    Vera   Vue中父子组件执行的先后顺序探讨(转载) 前几天,朋友向我提出了一个关于Vue中父子组件执行的先后顺序问题,相信很多朋友在学习的过程中也会遇到这个问题,所以我就在此提出我自己的一些小看 ...

  9. Vue中父子组件执行的先后顺序探讨

    前几天,朋友向我提出了一个关于Vue中父子组件执行的先后顺序问题,相信很多朋友在学习的过程中也会遇到这个问题,所以我就在此提出我自己的一些小看法. 问题如下:请问下图中父子组件执行的先后顺序? 首先, ...

  10. Vue中父组件向子组件传值

    Vue中父组件向子组件传值 相关Html: <!DOCTYPE html> <html lang="en"> <head> <meta c ...

随机推荐

  1. JavaScript 评测代码运行速度

    一.使用 performance.now() API 在 JavaScript 中,可以使用 performance.now() API 来评测代码的运行速度.该 API 返回当前页面的高精度时间戳, ...

  2. springcloud11 spring cloud config

    1.spring cloud config是什么 官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RE ...

  3. 2021级《JAVA语言程序设计》上机考试试题

    勉强写完了 Card package Bean; public class Card { private String CardId; private String CardData; private ...

  4. 基于minikube快速搭建kubernetes单节点环境

    一.说明 本文主要介绍在 Centos7 环境下基于 Minikube 来快速部署 Kubernetes 单节点集群环境,并在浏览器上访问部署在 k8s 上的 dashboard 服务. 二.Mini ...

  5. 搭建Git服务器教程(整理自腾讯云开发者实验室)

    搭建Git服务器教程(整理自腾讯云开发者实验室) 下载安装 Git Git 是一款免费.开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目. 此实验以 CentOS 7.2 x64 的系统 ...

  6. 自动化测试方案对比:Katalon vs Python

    公司要用katalon做自动化,我提议用更加主流的python的方案,即pytest+selenium.下面是做一个大概的对比,以引导团队尽可能的选择可靠的方案. 接下来我直接把我当时讲的ppt贴上来 ...

  7. 自己从零写操作系统GrapeOS系列教程——4.GrapeOS开发环境介绍

    1. 开发环境简介 为了减少开发过程中不必要的麻烦,希望大家的开发环境尽量与我的保持一致. 我的开发环境如下: Windows10电脑一台 Visual Studio Code(最好是最新版) Vir ...

  8. Python连接Hadoop-impala方法

    from impala.dbapi import connectfrom impala.util import as_pandas连接方式 conn1= connect(host='xxx.xxx.x ...

  9. LeetCode-398 随机数索引

    来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/random-pick-index 题目描述 给定一个可能含有重复元素的整数数组,要求随机输出给定 ...

  10. C - Functions again CodeForces - 789C

    C - Functions again CodeForces - 789C 这道题考查了对Map的运用 #include<iostream> #include<cstdio> ...