Henry Henry
  • JavaScript
  • TypeScript
  • Vue
  • ElementUI
  • React
  • HTML
  • CSS
  • 技术文档
  • GitHub 技巧
  • Nodejs
  • Chrome
  • VSCode
  • Other
  • Mac
  • Windows
  • Linux
  • Vim
  • VSCode
  • Chrome
  • iTerm
  • Mac
  • Obsidian
  • lazygit
  • Vim 技巧
  • 分类
  • 标签
  • 归档
  • 网站
  • 资源
  • Vue 资源
GitHub (opens new window)

Henry

小学生中的前端大佬
  • JavaScript
  • TypeScript
  • Vue
  • ElementUI
  • React
  • HTML
  • CSS
  • 技术文档
  • GitHub 技巧
  • Nodejs
  • Chrome
  • VSCode
  • Other
  • Mac
  • Windows
  • Linux
  • Vim
  • VSCode
  • Chrome
  • iTerm
  • Mac
  • Obsidian
  • lazygit
  • Vim 技巧
  • 分类
  • 标签
  • 归档
  • 网站
  • 资源
  • Vue 资源
GitHub (opens new window)
  • JavaScript

  • TypeScript

  • Vue

    • Vue 编码指南
    • vue-cli 启动本地服务局域网不能访问的原因分析
    • 解决 Vue 相同路由参数不同不会刷新的问题
    • 解决 vuex requires a Promise polyfill in this browser 问题
    • 关于父组件通过 v-on 接收子组件多个参数的一点研究
    • Vue $attrs 和 $listeners
    • Vue axios 发送 Form Data 数据格式请求
    • Vue 开发技巧
    • Vue 动态路由
    • Vue 集成 UEditor 富文本编辑器
    • Vue 修饰符
    • Vue 问题集合
    • Vue props 传多值的问题
    • vue-router 在 IE11 下手动更改 URL 的 hash 不会触发路由
    • vue-router 路由参数刷新消失的问题
    • 那些年被我们忽略的 vue 语法
    • vue 生命周期深入
    • vue 组件通信深入
      • 1. 组件的注册
        • 1.1 全局注册
        • 1.2 局部注册
      • 2. 组件的组织
      • 3. 组件之间的数据传递
        • 3.1 组件间简单的数据通信
        • 3.2 \$root 方式
        • 3.3 总线 Bus 方式
        • 3.4 Vuex 方式
    • vue 组件通信深入 Vuex
    • vue项目移动端、pc端适配方案
    • vuepress 如何引入 vuex
  • ElementUI

  • React

  • AntD

  • 前端
  • Vue
Henry
2018-03-22
目录

vue 组件通信深入

vue 中, 组件是带有一个名字、可复用的 Vue 实例. 由于 Vue 是面向视图的 MVVM 框架, 组件可以看做是对数据和方法的简单封装、具有独立的逻辑和功能的界面, 多个组件按照一定规则的组合最终成为一个完整的应用

# 1. 组件的注册

# 1.1 全局注册

Vue.component() 用来创建全局组件, 一旦注册, 即可在该实例 Vue 下的任何子组件中使用, 常用于一些使用较为频繁的基础组件, 如 Alert 组件、Button 组件、布局组件等

使用方式:

Vue.component('my-component', {
  // vue 实例方法和生命周期(el 除外)
})
1
2
3

如果你使用过 element-ui , 下面的写法你可能比较熟悉:

import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'

Vue.use(ElementUI)

new Vue({
  el: '#app',
  render: h => h(App)
})
1
2
3
4
5
6
7
8
9
10
11

其中 Vue.use(ElementUI); 的方式便是间接调用了全局组件注册的方式, 在 element-ui 内部: (插件中, 使用 Vue.use() 的方式, 相当于调用了其中的 install 方法)

const install = function(Vue, opts = {}) {
  // ...
  components.map(component => {
    Vue.component(component.name, component)
  })
  // ...
}
1
2
3
4
5
6
7

可以看出, 在其内部也是依次全局注册了 element 中的插件

# 1.2 局部注册

Vue 官网上如是说:

全局注册往往是不够理想的. 比如, 如果你使用一个像 webpack 这样的构建系统, 全局注册所有的组件意味着即便你已经不再使用一个组件了, 它仍然会被包含在你最终的构建结果中. 这造成了用户下载的 JavaScript 的无谓的增加.

正是因为上面的原因, 除了一些常用的基础组件外, 尽可能的使用局部注册的方式

// 普通引入方式
var ComponentA = {
  /* ... */
}

// ES6 引入方式
import ComponentA from './ComponentA.vue'

export default {
  // ...
  components: {
    'component-a': ComponentA
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

值得注意的是:局部注册方式仅能在当前组件中使用, 在其子组件中使用需要再次注册

# 2. 组件的组织

上面提到, 多个组件按照一定规则的组合最终成为一个完整的应用 , 因此, 我们可以将组件看作是 Vue 页面中的最小单元, 那么应该如何组织组件, 整合成一个页面呢?

有这样一个 需求: 要求按照下图组织页面结构

我们可以这样组织:

<template>
  <div class="m-body">
    我是主体内容
  </div>
</template>

<script>
  export default {}
</script>

<style scoped>
  .m-body {
    min-height: 500px;
    color: #fff;
    padding: 20px;
    background-color: #39f;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

按照这种方式, 依次写出 header、aside、content、footer 四个组件, 并用一个组件作为这四个组件的父组件来组织页面结构, 最后的结构如下:

父组件如下:

<template>
  <div class="comp">
    <m-header />
    <div class="main">
      <m-side />
      <m-body />
    </div>
    <m-footer />
  </div>
</template>

<script>
  import MHeader from './MHeader'
  import MFooter from './MFooter'
  import MBody from './MBody'
  import MSide from './MSide'

  export default {
    components: {
      MHeader,
      MFooter,
      MBody,
      MSide
    }
  }
</script>

<style lang="scss" scoped>
  .main {
    margin: 10px 0;
    display: flex;

    .m-side {
      width: 200px;
      margin-right: 10px;
    }

    .m-body {
      flex: 1;
    }
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

打开 Vue 调试界面, 将看到如下的结构

注意:父组件负责控制容器结构样式(各个直接子组件的位置、大小等), 子组件负责其内部的样式, 不要在子组件中写自身的容器样式

# 3. 组件之间的数据传递

组件的组合仅仅只是将页面结构搭建了起来, 要完成页面的交互功能, 组件之间必定会有数据传递 按照页面结构, 大体上可以将组件间的数据传递分成三种:

  1. 父子组件间的数据传递
  2. 兄弟组件间的数据传递
  3. 非直接关联性组件间的数据传递

# 3.1 组件间简单的数据通信

Vue 官网中对 props、$emit、slot 有非常详细的描述, 在此不再唠述

现有新的 需求: 在上面例子的基础上, 需要满足:header 中有一个数值, side 中新增重置和增加按钮, body 中新增数组输入框, 当对按钮和表单作操作时, 对应的数值作相应改变

基本 思路: 将数值放在几个组件公共上层组件中, header 中 prop 接受该值, side 和 body 中点击按钮向他们的公共上层组件分发 $emit 事件, 改变该数值, 核心思路:多个组件操作的值均为上层组件的变量

代码如下:

(1)父级组件: 主要用于数据传递与接收子组件分发的事件来改变对应的变量

<div class="comp">
  <m-header :num="num" />
  <div class="main">
    <m-side @add="handleAdd" @reset="handleReset" />
    <m-body :num="num" @change="handleChange" />
  </div>
  <m-footer />
</div>
1
2
3
4
5
6
7
8
export default {
  data() {
    return {
      num: 0
    }
  },
  methods: {
    handleAdd() {
      this.num += 1
    },
    handleChange(val) {
      this.num = val
    },
    handleReset() {
      this.num = 0
    }
  }
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

(2)Header 组件: 接受并展示数值

template 中仅添加

props: {
  num: {
    type: Number,
    default: 0
  }
}
1
2
3
4
5
6

(3)Side 组件: 向上分发增加和重置事件

<!-- 新增 -->
<el-button @click="add">ADD</el-button>
<el-button @click="reset">RESET</el-button>
1
2
3
methods: {
  add () {
    this.$emit('add')
  },
  reset () {
    this.$emit('reset')
  }
}
1
2
3
4
5
6
7
8

(4)Body 组件: 监控传值, 向上分发事件

<!-- 新增 -->
<el-input-number v-model="currentVal" @change="handleChange"></el-input-number>
1
2
props: {
    num: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      currentVal: 0
    }
  },
  // 外层数据改变时,currentVal 值需要同步修改
  watch: {
    num: {
      handler (val) {
        this.currentVal = val
      },
      immediate: true
    }
  },
  methods: {
    handleChange (val) {
      this.$emit('change', val)
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

这种简单的数据交互使用 prop 和 $emit 足以应付, 但是 (1)对于深层组件嵌套中的数据传递, 使用这种通信方式则需要一层一层向下 prop, 改变时需要一层一层向上 $emit (2)对于兄弟组件之间的数据传递, 先要向上分发, 再向下 prop, 过于繁琐且不易监控调试

这里有一个 新的需求: 在最初组件组合的基础上, side 组件中有一个数据展示, 要求通过 body 中深层嵌套的组件操作以改变 side 中的数据

修改: 在 body 组件中添加 <slot></slot> , 并新增一个组件挂载在该插槽上, 用以模拟深层嵌套(当然了, 实际的工作中的嵌套可能涉及到四层甚至更多)

# 3.2 $root 方式

上面方法的核心是所有子组件统一管理和操作父组件的数据, 子组件负责展示和分发事件, 实际操作值的始终在父组件, Vue 提供了一个能访问到根组件的方法, 官网中如是描述:处理边界情况 (opens new window) 中访问根实例部分

(1)在入口文件 main.js 中添加:

new Vue({
  data: {
    rootNum: 0
  }
  // ...
})
1
2
3
4
5
6

(2)在 父组件 中添加:

<!-- 局部注册不作详述 -->
<m-body>
  <m-body-item></m-body-item>
</m-body>
1
2
3
4

(3)新添加的组件 MBodyItem

<template>
  <div class="m-body-item">
    <el-button @click="add">ADD</el-button>
    <el-button @click="reset">RESET</el-button>
  </div>
</template>

<script>
  // 可直接操作 $root 中声明的变量
  export default {
    methods: {
      add() {
        this.$root.rootNum += 1
      },
      reset() {
        this.$root.rootNum = 0
      }
    }
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

(4)side 组件:

<div class="m-side">
  我是侧边栏 {{$root.rootNum}}
</div>
1
2
3

对于 demo 或非常小型的有少量组件的应用来说直接使用 $root 的方式很方便. 不过这个模式扩展到中大型应用来说就不然了, 数据量过大不易维护, 也不易追踪数据的变化

# 3.3 总线 Bus 方式

总线 Bus 的思路:将事件的注册和触发单独放在一个 Vue 实例中, 点击按钮时触发指定的事件以驱动接下来的操作.Bus 总线仅仅是用来驱动事件 的, 具体的数据操作还是在原有的组件中

在 $root 的结构基础上, 作如下更改:

(1)原入口文件 main.js 还原, 去掉 data 属性

(2)新定义一个总线文件 bus.js

import Vue from 'vue'
export default new Vue()
1
2

(3)side 组件中注册总线事件并显示数据

import Bus from './bus'
export default {
  data() {
    return {
      sideNum: 0
    }
  },
  created() {
    Bus.$on('change', step => {
      this.sideNum += step
    })
    Bus.$on('reset', () => {
      this.sideNum = 0
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

(4)bodyItem 组件中分发总线事件

import Bus from './bus'
export default {
  methods: {
    add() {
      Bus.$emit('change', 1)
    },
    reset() {
      Bus.$emit('reset')
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11

总线的方式, 将原有的数据传递转换成了事件驱动的形式, 这一点规避了组件层级的嵌套问题, 但是开发人员无法追踪调试数据

# 3.4 Vuex 方式

由于内容较多, 将在下一篇博客中详细介绍

编辑 (opens new window)
#Vue
上次更新: 5/27/2023, 1:02:05 PM
vue 生命周期深入
vue 组件通信深入 Vuex

← vue 生命周期深入 vue 组件通信深入 Vuex→

最近更新
01
version 1.15
07-01
02
version 1.14
06-27
03
version 1.13
06-27
更多文章>
Theme by Vdoing | Copyright © 2017-2023 HenryTSZ | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式