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中不能放置div,tr的父元素不能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这个属性的值。

之所以说是一般情况,是因为class和style特性并不是这样。用v-bind:class和class传入正常的类名,效果是一样的,因为对于这两个特性,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>

上面渲染的结果是,div的att属性是helloParent。
注意:前面已经提到过,覆盖原则对于class和style不适用,而是采用了合并(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>

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

  • 把input的value特性绑定到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. 12月12日内容总结——Django之数据增删改查、Django请求生命周期流程图、Django路由层(路由匹配、转换器、正则匹配)、反向解析

    目录 一.可视化界面之数据增删改查 二.django请求生命周期流程图 三.django路由层 1.路由匹配 2.转换器 3.正则匹配 不同版本的区别 正则匹配斜杠导致的区别 4.正则匹配的无名有名分 ...

  2. drf-认证、权限、频率、过滤、排序、分页

    1.认证组件 1.1 局部认证 1.首先写两个接口,一个查询单个一个查询所有,我们利用视图扩展类和视图子类写在一个视图类上: views.py: from rest_framework.viewset ...

  3. day07-SpringMVC底层机制简单实现-03

    SpringMVC底层机制简单实现-03 https://github.com/liyuelian/springmvc-demo.git 7.任务6-完成控制器方法获取参数-@RequestParam ...

  4. 使用Hook拦截sendto函数解决虚拟局域网部分游戏联机找不到房间的问题——以文明6为例

    正文 重要提醒(2023-02-13):本文部分内容存在bug,目前正在调试修改,会在一段时间之后更新 重要提醒(2023-02-14):目前已修复主要bug,会在一段时间之后更新,本文计划重写大部分 ...

  5. ajax的原理是什么?如何实现?

    一.是什么 AJAX全称(Async Javascript and XML) 即异步的JavaScript 和XML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,与服务器 ...

  6. CPU AMX 详解

    CPU AMX 详解 CPU AMX 详解 概述 算力如何 问题定义 AVX如何解决矩阵乘问题 AMX如何解决矩阵乘问题 如何实现的 计算部分 数据部分 路才开始 概述 2016 年开始,随着 NV ...

  7. CSS3--点击弹出菜单动画

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name ...

  8. 内容分发网络 CDN

    介绍 CDN 内容分发网络(英语:Content Delivery Network 或 Content Distribution Network,缩写:CDN)是建立并覆盖在承载网上,由不同区域的服务 ...

  9. B树,B-树,B+树和B*树

    B树和B-树(平衡多路查找树)规则:1.排序方法:所有结点关键字按递增次序,并遵循左小右大的原则.2.子结点数:非叶子结点的子结点数>1且<=M且M>=2,空树除外(M阶代表一个树的 ...

  10. sudo漏洞解决方案--源码转rpm包(spec文件编写)

    RPM 知识储备 将源码包打包成rpm包,一般有两种情况 在找包中,能够在网上找到 ".src.rpm" 结尾的包,并且是根据漏洞需要升级的版本的包 在找包中,没有找到 " ...