转载: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. const 命令

    const 命令声明一个只读的常量,声明后值不可以改变 const 变量不可以重复声明 const一旦声明变量,就必须立即初始化,不能留到以后赋值. const命令声明的常量也是不提升,同样存在暂时性 ...

  2. js 404页面跳转

    非原创 <script type="text/javascript"> var num = 5; function redirect() { num--; docume ...

  3. java----int,string 转化为long

    String: 1.返回Long包装类型: String str = "aaa"; long l = Long.parseLong([str]); 2.返回long基本数据类型: ...

  4. java 接受带有中文的get请求文件下载时的问题

    参数是接受到了 , debug的时候也能看的到 , 但是奇怪的是就是找不到文件 @ApiOperation(value = "文件下载/图片预览") @GetMapping(val ...

  5. bzoj1002题解

    [题意分析] 给你一张特殊的,被称为“轮状基”的无向图,求其生成树个数. [解题思路] 引理: 基尔霍夫矩阵: 基尔霍夫矩阵=度数矩阵-邻接矩阵(邻接矩阵权=两点连边数) Matrix-Tree定理: ...

  6. openstack nova 源码解析 — Nova API 执行过程从(novaclient到Action)

    目录 目录 Nova API Nova API 的执行过程 novaclient 将 Commands 转换为标准的HTTP请求 PasteDeploy 将 HTTP 请求路由到具体的 WSGI Ap ...

  7. Openstack贡献者须知 2 — 社区工作运作 & 代码贡献流程

    目录 目录 前文列表 订阅邮件列表 Mailing Lists 社区工作运作流程 Openstack 代码贡献流程 PEP8 Python编程风格 查阅相关资源 前文列表 Openstack贡献者须知 ...

  8. 剑指offer——35二叉树的后序遍历

    题目描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同.   题解: 这道题,一开始以为将后序遍历排序后的得 ...

  9. parallels desktop虚拟机与Mac共享网络设置方法

    查看vnic0的ip centos7设置ip parallels desktop偏好设置 最后可以互ping 也可以ping外网

  10. Deep Dive into Neo4j 3.5 Full Text Search

    In this blog we will go over the Full Text Search capabilities available in the latest major release ...