转载:https://segmentfault.com/a/1190000016409329

Vue.js 最核心的功能就是组件(Component),从组件的构建、注册到组件间通信,Vue .x 提供了更多方式,让我们更灵活地使用组件来实现不同需求。
一、构建组件
1.1 组件基础
一个组件由 template、data、computed、methods等选项组成。需要注意: template 的 DOM 结构必须有根元素
data 必须是函数,数据通过 return 返回出去
// 示例:定义一个组件 MyComponent
var MyComponent = {{
data: function () {
return {
// 数据
}
},
template: '<div>组件内容</div>'
}
由于 HTML 特性不区分大小写, 在使用kebab-case(小写短横线分隔命名) 定义组件时,引用也需要使用这个格式如 <my-component>来使用;在使用PascalCase(驼峰式命名) 定义组件时<my-component>和<MyComponent>这两种格式都可以引用。 1.2 单文件组件.vue
如果项目中使用打包编译工具 webpack,那引入 vue-loader 就可以使用 .vue后缀文件构建组件。
一个.vue单文件组件 (SFC) 示例: // MyComponent.vue 文件
<template>
<div>组件内容</div>
</template> <script>
export default {
data () {
return {
// 数据
}
}
}
</script> <style scoped>
div{
color: red
}
</style>
.vue文件使组件结构变得清晰,使用.vue还需要安装 vue-style-loader 等加载器并配置 webpack.config.js 来支持对 .vue 文件及 ES6 语法的解析。 进一步学习可参考文章:详解 SFC 与 vue-loader
二、注册组件
2.1 手动注册
组件定义完后,还需要注册才可以使用,注册分为全局和局部注册: // 全局注册,任何 Vue 实例都可引用
Vue.component('my-component', MyComponent) // 局部注册,在注册实例的作用域下有效
var MyComponent = { /* ... */ }
new Vue({
components: {
'my-component': MyComponent
}
}) // 局部注册,使用模块系统,组件定义在统一文件夹中
import MyComponent from './MyComponent.vue' export default {
components: {
MyComponent // ES6 语法,相当于 MyComponent: MyComponent
}
}
注意全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。 2.2 自动注册
对于通用模块使用枚举的注册方式代码会非常不方便,推荐使用自动化的全局注册。如果项目使用 webpack,就可以使用其中的require.context一次性引入组件文件夹下所有的组件: import Vue from 'vue'
import upperFirst from 'lodash/upperFirst' // 使用 lodash 进行字符串处理
import camelCase from 'lodash/camelCase' const requireComponent = require.context(
'./components', // 其组件目录的相对路径
false, // 是否查询其子目录
/Base[A-Z]\w+\.(vue|js)$/ // 匹配基础组件文件名的正则表达式
) requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName) // 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 剥去文件名开头的 `./` 和结尾的扩展名
fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
)
) // 全局注册组件
Vue.component(
componentName,
componentConfig.default || componentConfig
)
})
三、组件通信
3.1 父单向子的 props
Vue .x 以后父组件用props向子组件传递数据,这种传递是单向/正向的,反之不能。这种设计是为了避免子组件无意间修改父组件的状态。 子组件需要选项props声明从父组件接收的数据,props可以是字符串数组和对象,一个 .vue 单文件组件示例如下 // ChildComponent.vue
<template>
<div>
<b>子组件:</b>{{message}}
</div>
</template> <script>
export default {
name: "ChildComponent",
props: ['message']
}
</script>
父组件可直接传单个数据值,也可以可以使用指令v-bind动态绑定数据: // parentComponent.vue
<template>
<div>
<h1>父组件</h1>
<ChildComponent message="父组件向子组件传递的非动态值"></ChildComponent>
<input type="text" v-model="parentMassage"/>
<ChildComponent :message="parentMassage"></ChildComponent>
</div>
</template> <script>
import ChildComponent from '@/components/ChildComponent'
export default {
components: {
ChildComponent
},
data () {
return {
parentMassage: ''
}
}
}
</script>
配置路由后运行效果如下:
clipboard.png 3.2 子向父的 $emit
当子组件向父组件传递数据时,就要用到自定义事件。子组件中使用 $emit()触发自定义事件,父组件使用$on()监听,类似观察者模式。 子组件$emit()使用示例如下: // ChildComponent.vue
<template>
<div>
<b>子组件:</b><button @click="handleIncrease">传递数值给父组件</button>
</div>
</template> <script>
export default {
name: "ChildComponent",
methods: {
handleIncrease () {
this.$emit('increase',)
}
}
}
</script>
父组件监听自定义事件 increase,并做出响应的示例: // parentComponent.vue
<template>
<div>
<h1>父组件</h1>
<p>数值:{{total}}</p>
<ChildComponent @increase="getTotal"></ChildComponent>
</div>
</template> <script>
import ChildComponent from '@/components/ChildComponent'
export default {
components: {
ChildComponent
},
data () {
return {
total:
}
},
methods: {
getTotal (count) {
this.total = count
}
}
}
</script>
访问 parentComponent.vue 页面,点击按钮后子组件将数值传递给父组件:
clipboard.png 3.3 子孙的链与索引
组件的关系有很多时跨级的,这些组件的调用形成多个父链与子链。父组件可以通过this.$children访问它所有的子组件,可无限递归向下访问至最内层的组件,同理子组件可以通过this.$parent访问父组件,可无限递归向上访问直到根实例。 以下是子组件通过父链传值的部分示例代码: // parentComponent.vue
<template>
<div>
<p>{{message}}</p>
<ChildComponent></ChildComponent>
</div>
</template> // ChildComponent.vue
<template>
<div>
<b>子组件:</b><button @click="handleChange">通过父链直接修改数据</button>
</div>
</template> <script>
export default {
name: "ChildComponent",
methods: {
handleChange () {
this.$parent.message = '来自 ChildComponent 的内容'
}
}
}
</script>
显然点击父组件页面的按钮后会收到子组件传过来的 message。 在业务中应尽量避免使用父链或子链,因为这种数据依赖会使父子组件紧耦合,一个组件可能被其他组件任意修改显然是不好的,所以组件父子通信常用props和$emit。
3.4 中央事件总线 Bus
子孙的链式通信显然会使得组件紧耦合,同时兄弟组件间的通信该如何实现呢?这里介绍中央事件总线的方式,实际上就是用一个vue实例(Bus)作为媒介,需要通信的组件都引入 Bus,之后通过分别触发和监听 Bus 事件,进而实现组件之间的通信和参数传递。 首先建 Vue 实例作为总线: // Bus.js
import Vue from 'vue'
export default new Vue;
需要通信的组件都引入 Bus.js,使用 $emit发送信息: // ComponentA.vue
<template>
<div>
<b>组件A:</b><button @click="handleBus">传递数值给需要的组件</button>
</div>
</template> <script>
import Bus from './bus.js'
export default {
methods: {
handleBus () {
Bus.$emit('someBusMessage','来自ComponentA的数据')
}
}
}
</script>
需要组件A信息的就使用$on监听: // ComponentB.vue
<template>
<div>
<b>组件B:</b><button @click="handleBus">接收组件A的信息</button>
<p>{{message}}</p>
</div>
</template> <script>
import Bus from './bus.js'
export default {
data() {
return {
message: ''
}
},
created () {
let that = this // 保存当前对象的作用域this
Bus.$on('someBusMessage',function (data) {
that.message = data
})
},
beforeDestroy () {
// 手动销毁 $on 事件,防止多次触发
Bus.$off('someBusMessage', this.someBusMessage)
}
}
</script>
四、递归组件
组件可以在自己的 template 模板中调用自己,需要设置 name 选择。 // 递归组件 ComponentRecursion.vue
<template>
<div>
<p>递归组件</p>
<ComponentRecursion :count="count + 1" v-if="count < 3"></ComponentRecursion>
</div>
</template> <script>
export default {
name: "ComponentRecursion",
props: {
count: {
type: Number,
default:
}
}
}
</script>
如果递归组件没有 count 等限制数量,就会抛出错误(Uncaught RangeError: Maximum call stack size exceeded)。 父页面使用该递归组件,在 Chrome 中的 Vue Devtools 可以看到组件递归了三次:
clipboard.png 递归组件可以开发未知层级关系的独立组件,如级联选择器和树形控件等。
五、动态组件
如果将一个 Vue 组件命名为 Component 会报错(Do not use built-in or reserved HTML elements as component id: Component),因为 Vue 提供了特殊的元素 <component>来动态挂载不同的组件,并使用 is 特性来选择要挂载的组件。 以下是使用<component>动态挂载不同组件的示例: // parentComponent.vue
<template>
<div>
<h1>父组件</h1>
<component :is="currentView"></component>
<button @click = "changeToViewB">切换到B视图</button>
</div>
</template> <script>
import ComponentA from '@/components/ComponentA'
import ComponentB from '@/components/ComponentB'
export default {
components: {
ComponentA,
ComponentB
},
data() {
return {
currentView: ComponentA // 默认显示组件 A
}
},
methods: {
changeToViewB () {
this.currentView = ComponentB // 切换到组件 B
}
}
}
</script>
改变 this.currentView的值就可以自由切换 AB 组件:
clipboard.png 与之类似的是vue-router的实现原理,前端路由到不同的页面实际上就是加载不同的组件。
要继续加油呢,少年!

Vue.js 最核心的功能就是组件(Component),从组件的构建、注册到组件间通信,Vue 2.x 提供了更多方式,让我们更灵活地使用组件来实现不同需求。

一、构建组件

1.1 组件基础

一个组件由 template、data、computed、methods等选项组成。需要注意:

  • template 的 DOM 结构必须有根元素
  • data 必须是函数,数据通过 return 返回出去
// 示例:定义一个组件 MyComponent
var MyComponent = {{
data: function () {
return {
// 数据
}
},
template: '<div>组件内容</div>'
}

由于 HTML 特性不区分大小写, 在使用kebab-case(小写短横线分隔命名) 定义组件时,引用也需要使用这个格式如 <my-component>来使用;在使用PascalCase(驼峰式命名) 定义组件时<my-component><MyComponent>这两种格式都可以引用。

1.2 单文件组件.vue

如果项目中使用打包编译工具 webpack,那引入 vue-loader 就可以使用 .vue后缀文件构建组件。
一个.vue单文件组件 (SFC) 示例:

// MyComponent.vue 文件
<template>
<div>组件内容</div>
</template> <script>
export default {
data () {
return {
// 数据
}
}
}
</script> <style scoped>
div{
color: red
}
</style>

.vue文件使组件结构变得清晰,使用.vue还需要安装 vue-style-loader 等加载器并配置 webpack.config.js 来支持对 .vue 文件及 ES6 语法的解析。

进一步学习可参考文章:详解 SFC 与 vue-loader

二、注册组件

2.1 手动注册

组件定义完后,还需要注册才可以使用,注册分为全局和局部注册:

// 全局注册,任何 Vue 实例都可引用
Vue.component('my-component', MyComponent) // 局部注册,在注册实例的作用域下有效
var MyComponent = { /* ... */ }
new Vue({
components: {
'my-component': MyComponent
}
}) // 局部注册,使用模块系统,组件定义在统一文件夹中
import MyComponent from './MyComponent.vue' export default {
components: {
MyComponent // ES6 语法,相当于 MyComponent: MyComponent
}
}

注意全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。

2.2 自动注册

对于通用模块使用枚举的注册方式代码会非常不方便,推荐使用自动化的全局注册。如果项目使用 webpack,就可以使用其中的require.context一次性引入组件文件夹下所有的组件:

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst' // 使用 lodash 进行字符串处理
import camelCase from 'lodash/camelCase' const requireComponent = require.context(
'./components', // 其组件目录的相对路径
false, // 是否查询其子目录
/Base[A-Z]\w+\.(vue|js)$/ // 匹配基础组件文件名的正则表达式
) requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName) // 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 剥去文件名开头的 `./` 和结尾的扩展名
fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
)
) // 全局注册组件
Vue.component(
componentName,
componentConfig.default || componentConfig
)
})

三、组件通信

3.1 父单向子的 props

Vue 2.x 以后父组件用props向子组件传递数据,这种传递是单向/正向的,反之不能。这种设计是为了避免子组件无意间修改父组件的状态。

子组件需要选项props声明从父组件接收的数据,props可以是字符串数组对象,一个 .vue 单文件组件示例如下

// ChildComponent.vue
<template>
<div>
<b>子组件:</b>{{message}}
</div>
</template> <script>
export default {
name: "ChildComponent",
props: ['message']
}
</script>

父组件可直接传单个数据值,也可以可以使用指令v-bind动态绑定数据:

// parentComponent.vue
<template>
<div>
<h1>父组件</h1>
<ChildComponent message="父组件向子组件传递的非动态值"></ChildComponent>
<input type="text" v-model="parentMassage"/>
<ChildComponent :message="parentMassage"></ChildComponent>
</div>
</template> <script>
import ChildComponent from '@/components/ChildComponent'
export default {
components: {
ChildComponent
},
data () {
return {
parentMassage: ''
}
}
}
</script>

配置路由后运行效果如下:

3.2 子向父的 $emit

当子组件向父组件传递数据时,就要用到自定义事件。子组件中使用 $emit()触发自定义事件,父组件使用$on()监听,类似观察者模式。

子组件$emit()使用示例如下:

// ChildComponent.vue
<template>
<div>
<b>子组件:</b><button @click="handleIncrease">传递数值给父组件</button>
</div>
</template> <script>
export default {
name: "ChildComponent",
methods: {
handleIncrease () {
this.$emit('increase',5)
}
}
}
</script>

父组件监听自定义事件 increase,并做出响应的示例:

// parentComponent.vue
<template>
<div>
<h1>父组件</h1>
<p>数值:{{total}}</p>
<ChildComponent @increase="getTotal"></ChildComponent>
</div>
</template> <script>
import ChildComponent from '@/components/ChildComponent'
export default {
components: {
ChildComponent
},
data () {
return {
total: 0
}
},
methods: {
getTotal (count) {
this.total = count
}
}
}
</script>

访问 parentComponent.vue 页面,点击按钮后子组件将数值传递给父组件:

3.3 子孙的链与索引

组件的关系有很多时跨级的,这些组件的调用形成多个父链与子链。父组件可以通过this.$children访问它所有的子组件,可无限递归向下访问至最内层的组件,同理子组件可以通过this.$parent访问父组件,可无限递归向上访问直到根实例。

以下是子组件通过父链传值的部分示例代码:

// parentComponent.vue
<template>
<div>
<p>{{message}}</p>
<ChildComponent></ChildComponent>
</div>
</template> // ChildComponent.vue
<template>
<div>
<b>子组件:</b><button @click="handleChange">通过父链直接修改数据</button>
</div>
</template> <script>
export default {
name: "ChildComponent",
methods: {
handleChange () {
this.$parent.message = '来自 ChildComponent 的内容'
}
}
}
</script>

显然点击父组件页面的按钮后会收到子组件传过来的 message。

在业务中应尽量避免使用父链或子链,因为这种数据依赖会使父子组件紧耦合,一个组件可能被其他组件任意修改显然是不好的,所以组件父子通信常用props$emit

3.4 中央事件总线 Bus

子孙的链式通信显然会使得组件紧耦合,同时兄弟组件间的通信该如何实现呢?这里介绍中央事件总线的方式,实际上就是用一个vue实例(Bus)作为媒介,需要通信的组件都引入 Bus,之后通过分别触发和监听 Bus 事件,进而实现组件之间的通信和参数传递。

首先建 Vue 实例作为总线:

// Bus.js
import Vue from 'vue'
export default new Vue;

需要通信的组件都引入 Bus.js,使用 $emit发送信息:

// ComponentA.vue
<template>
<div>
<b>组件A:</b><button @click="handleBus">传递数值给需要的组件</button>
</div>
</template> <script>
import Bus from './bus.js'
export default {
methods: {
handleBus () {
Bus.$emit('someBusMessage','来自ComponentA的数据')
}
}
}
</script>

需要组件A信息的就使用$on监听:

// ComponentB.vue
<template>
<div>
<b>组件B:</b><button @click="handleBus">接收组件A的信息</button>
<p>{{message}}</p>
</div>
</template> <script>
import Bus from './bus.js'
export default {
data() {
return {
message: ''
}
},
created () {
let that = this // 保存当前对象的作用域this
Bus.$on('someBusMessage',function (data) {
that.message = data
})
},
beforeDestroy () {
// 手动销毁 $on 事件,防止多次触发
Bus.$off('someBusMessage', this.someBusMessage)
}
}
</script>

四、递归组件

组件可以在自己的 template 模板中调用自己,需要设置 name 选择。

// 递归组件 ComponentRecursion.vue
<template>
<div>
<p>递归组件</p>
<ComponentRecursion :count="count + 1" v-if="count < 3"></ComponentRecursion>
</div>
</template> <script>
export default {
name: "ComponentRecursion",
props: {
count: {
type: Number,
default: 1
}
}
}
</script>

如果递归组件没有 count 等限制数量,就会抛出错误(Uncaught RangeError: Maximum call stack size exceeded)。

父页面使用该递归组件,在 Chrome 中的 Vue Devtools 可以看到组件递归了三次:

递归组件可以开发未知层级关系的独立组件,如级联选择器和树形控件等。

五、动态组件

如果将一个 Vue 组件命名为 Component 会报错(Do not use built-in or reserved HTML elements as component id: Component),因为 Vue 提供了特殊的元素 <component>来动态挂载不同的组件,并使用 is 特性来选择要挂载的组件。

以下是使用<component>动态挂载不同组件的示例:

// parentComponent.vue
<template>
<div>
<h1>父组件</h1>
<component :is="currentView"></component>
<button @click = "changeToViewB">切换到B视图</button>
</div>
</template> <script>
import ComponentA from '@/components/ComponentA'
import ComponentB from '@/components/ComponentB'
export default {
components: {
ComponentA,
ComponentB
},
data() {
return {
currentView: ComponentA // 默认显示组件 A
}
},
methods: {
changeToViewB () {
this.currentView = ComponentB // 切换到组件 B
}
}
}
</script>

改变 this.currentView的值就可以自由切换 AB 组件:

与之类似的是vue-router的实现原理,前端路由到不同的页面实际上就是加载不同的组件。


要继续加油呢,少年!

【Vue】组件的基础与组件间通信的更多相关文章

  1. DRF框架(八)——drf-jwt手动签发与校验、搜索过滤组件、排序过滤组件、基础分页组件

    自定义drf-jwt手动签发和校验 签发token源码入口 前提:给一个局部禁用了所有 认证与权限 的视图类发送用户信息得到token,其实就是登录接口,不然进不了登录页面 获取提交的username ...

  2. java线程基础巩固---线程间通信快速入门,使用wait和notify进行线程间的数据通信

    之前已经对于线程同步相关的知识点进行了详细的学习,这次来学习一下线程间的通信相关的知识,话不多说直接用代码进行演练,以一个简陋的生产者消费者模型来初步了解下线程间通信是怎么一回事. 生产消费者第一版: ...

  3. Cocoapods组件化之搭建组件化项目框架

    一,概述 随着公司业务需求的不断迭代发展,工程的代码量和业务逻辑也越来越多,原始的开发模式和架构已经无法满足我们的业务发展速度了,这时我们就需要将原始项目进行一次重构大手术了.这时我们应该很清晰这次手 ...

  4. Vue – 基础学习(2):组件间 通信及参数传递

    Vue – 基础学习(2):组件间 通信及参数传递

  5. python 全栈开发,Day91(Vue实例的生命周期,组件间通信之中央事件总线bus,Vue Router,vue-cli 工具)

    昨日内容回顾 0. 组件注意事项!!! data属性必须是一个函数! 1. 注册全局组件 Vue.component('组件名',{ template: `` }) var app = new Vue ...

  6. vue之父子组件间通信实例讲解(props、$ref、$emit)

       组件间如何通信,也就成为了vue中重点知识了.这篇文章将会通过props.$ref和 $emit 这几个知识点,来讲解如何实现父子组件间通信. 组件是 vue.js 最强大的功能之一,而组件实例 ...

  7. 第四节:Vue表单标签和组件的基本用法,父子组件间的通信

    vue表单标签和组件的基本用法,父子组件间的通信,直接看例子吧. <!DOCTYPE html> <html> <head> <meta charset=&q ...

  8. 聊聊Vue.js组件间通信的几种姿势

    写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:https://github.com/a ...

  9. 【Vue】利用父子组件间通信实现一个场景

    组件间通信是组件开发的,我们既希望组件的独立性,数据能互不干扰,又不可避免组件间会有联系和交互. 在vue中,父子组件的关系可以总结为props down,events up: 在vue2.0中废弃了 ...

随机推荐

  1. MYSQL分数排名

    编写一个 SQL 查询来实现分数排名.如果两个分数相同,则两个分数排名(Rank)相同.请注意,平分后的下一个名次应该是下一个连续的整数值.换句话说,名次之间不应该有“间隔”. +----+----- ...

  2. apache httpd.conf alias

    参考 Apache alias目录配置 我的环境是 Ubuntu apache2,配置文件目录在 /etc/apache2/sites-available/000-default.conf 在这个配置 ...

  3. vue 学习二 深入vue双向绑定原理

    vue双向绑定原理 请示总体来讲 就是为data的中的每个属性字段添加一个getter/seter属性 以此来追踪数据的变化,而执行这部操作,依赖的就是js的Object.defineProperty ...

  4. Java——面向对象的特征三:多态性

    2.5面向对象的特征三:多态性 2.5.1 关于java语言中的向上转型和向下转型 ①向上转型(upcasting) :  子--->父(自动类型转换) ②向下转型(downcasting) : ...

  5. 【重磅来袭】阿里小程序IDE上线8大功能

    时隔两个月,10月10日阿里小程序IDE上线了uni-app 跨平台研发支持.预览和真机调试交互优化.预检测新增代码扫描等8项功能,进一步完善了阿里小程序IDE的功能池,给大家更好的开发体验和环境. ...

  6. luoguP1273 有线电视网 [树形dp]

    题目描述 某收费有线电视网计划转播一场重要的足球比赛.他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点. 从转播站到转播站以及从 ...

  7. delphi DrawText 的用法

    DrawText(hDC: HDC; {设备句柄}lpString: PChar; {文本}nCount: Integer; {要绘制的字符个数; -1 表示全部}var lpRect: TRect; ...

  8. hive的数据压缩

    hive的数据压缩 在实际工作当中,hive当中处理的数据,一般都需要经过压缩,前期我们在学习hadoop的时候,已经配置过hadoop的压缩,我们这里的hive也是一样的可以使用压缩来节省我们的MR ...

  9. 使用 Lookaside List 分配内存

    1. 概述 windows 提供了一种基于 lookaside list 的快速内存分配方案,区别于一般的使用 ExAllocatePoolWithTag() 系列函数的内存分配方式.每次从 look ...

  10. HSF简单实现记录(基于 Pandora Boot 开发)

    文章目录 声明 注意 安装轻量配置中心 启动轻量配置中心 配置 hosts 结果验证 开发工具准备 在 Maven 中配置 EDAS 的私服地址 验证配置是否成功 开发 demo下载 服务注册与发现 ...