前言:

假设我们已经了解vue组件常见的有父子组件通信,兄弟组件通信。而父子组件通信很简单,父组件会通过 props 向下传数据给子组件,当子组件有事情要告诉父组件时会通过 $emit 事件告诉父组件。那么当两个组件之间不是父子关系,怎样传递数据呢?

先来看一下这个例子:

递归嵌套组件参数传递

我们封装了一个名为 NestedDir 的子组件(嵌套目录的意思),内容如下(用到了element ui组件):

<!-- NestedDir.vue -->
<template>
  <ul class="nest_wrapper">
    <li v-for="(el, index) in nested" :key="index">
      <div v-if="el.type ==='dir'" class="dir">
        <p>{{el.name}}</p>
        <div class="btn_group">
          <el-button type="warning" size="mini" @click="add({id: el.id, type: 'dir'})">新增目录</el-button>
          <el-button type="warning" size="mini" @click="add({id: el.id, type: 'file'})">新增文件</el-button>
        </div>
      </div>
      <div v-if="el.type ==='file'" class="file">
        <p>{{el.name}}</p>
      </div>
      <NestedDir v-if="el.children" :nested="el.children"/>
    </li>
  </ul>
</template>

<script>
export default {
  name: "NestedDir",
  props: {
    nested: {
      type: Array,
    }
  },
  methods: {
    add(el) {
      this.$emit('change', el)
    }
  }
}
</script>

可以看出这个 NestedDir 接收父级传来的 nested 数组类型的数据,并且它的内部点击 新增目录、新增文件,可以触发 父级 监听的 change 事件。比较特殊的是这个组件中调用了自己:

<NestedDir v-if="el.children" :nested="el.children"/>

不过要注意的是调用自己的时候我们并没有在它上面监听它内部传来的change事件,这也是导致二级目录点击新增按钮无效的原因。

我们传递给它的 nested 数据结构大概是下面的样子:

[{
    "id": 1,
    "name": "目录1",
    "type": "dir",
    "children": [{
        "id": 2,
        "name": "目录3",
        "type": "dir",
        "children": [],
        "pid": 1
    }, {
        "id": 3,
        "name": "文件2",
        "type": "file",
        "pid": 1
    }]
}, {
    "id": 4,
    "name": "目录2",
    "type": "dir",
    "children": []
}, {
    "id": 5,
    "name": "文件1",
    "type": "file",
    "children": []
}]

父组件中调用 NestedDir:

<!-- directory.vue -->
<template>
  <div style="width: 50%;box-shadow: 0 0 4px 2px rgba(0,0,0,.1);margin: 10px auto;padding-bottom: 10px;">
    <!-- 顶部按钮组 -->
    <div class="btn_group">
      <el-button type="warning" size="mini" @click="showDialog({type: 'dir'})">新增目录</el-button>
      <el-button type="warning" size="mini" @click="showDialog({type: 'file'})">新增文件</el-button>
    </div>
    <!-- 嵌套组件 -->
    <NestedDir :nested="catalog" @change="handleChange"/>
    <!-- 新增弹出框 -->
    <el-dialog :title="title" :visible.sync="dialogFormVisible" width="300px">
      <el-form :model="form">
        <el-form-item label="名称">
          <el-input v-model="form.name" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">取 消</el-button>
        <el-button type="primary" @click="confirm">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import NestedDir from "./NestedDir";

export default {
  name: "directory",
  components: {
    NestedDir
  },
  created() {
    this.catalog = this.getTree()
  },
  computed: {
    maxId() {
      return this.arr.lastIndex   2
    },
    topNodes() {
      this.arr.forEach(item => {
        if (item.children) item.children = []
      })
      return this.arr.filter(item => !item.pid)
    }
  },
  data() {
    return {
      arr: [
        {id: 1, name: '目录1', type: 'dir', children: []},
        {id: 2, name: '目录3', type: 'dir', children: [], pid: 1},
        {id: 3, name: '文件2', type: 'file', pid: 1},
        {id: 4, name: '目录2', type: 'dir', children: []},
        {id: 5, name: '文件1', type: 'file'},
      ],
      title: '',
      dialogFormVisible: false,
      form: {
        id: '',
        name: '',
        type: '',
        pid: ''
      },
      catalog: []
    }
  },
  methods: {
    handleChange(el) {
      this.showDialog(el)
    },
    confirm() {
      this.arr.push({...this.form})
      this.dialogFormVisible = false
      this.catalog = this.getTree()
      this.form = {
        id: '',
        name: '',
        type: '',
        pid: '' , // 父级的id
      }
    },
    showDialog(el) {
      if (el.type === 'dir') {
        this.title = '新增目录'
        this.form.children = []
        this.form.type = 'dir'
      } else {
        this.title = '新增文件'
        this.form.type = 'file'
      }
      if (el.id) {
        this.form.pid = el.id
        this.form.id = this.maxId
      } else {
        this.form.id = this.maxId
      }
      this.dialogFormVisible = true
    },
    getTree() {
      this.topNodes.forEach(node => {
        this.getChildren(this.arr, node.children, node.id)
      })
      return this.topNodes
    },
    getChildren(data, result, pid) {
      for (let item of data) {
        if (item.pid === pid) {
          const newItem = {...item, children: []}
          result.push(newItem)
          this.getChildren(data, newItem.children, item.id)
        }
      }
    }
  }
}
</script>

<style scoped>
.btn_group {
  padding: 20px 10px;
  background-color: rgba(87, 129, 189, 0.13);
}
</style>

渲染出的页面是下面的样子:

递归组件

深层递归组件事件丢失

我们构造出了一个理论上可以无限嵌套的目录结构,但是经过测试发现 在二级目录上的 新增按钮 点击是没有任何反应的,原因是我们在 NestedDir 中调用了它自己并没有监听内部的change事件(上边提到过),所以它无法触发 父级的-父级 的监听事件。

如何解决?

  • 在递归调用的时候也监听一下change事件,并间接传递到最外层组件(这个是最容易想到的方法,但是如果组件嵌套很深,简直就是个噩梦)
  • EventBus(事件总线)

EventBus

什么事EventBus?

它其实就是一个Vue实例,有$emit、$on、$off方法,允许从一个组件向另一组件传递数据,而不需要借助父组件。具体做法是在一个组件$emit,在另一个组件$on,可以像下面这样做:

// main.js
import Vue from 'vue'
import App from './App.vue'

export const eventBus = new Vue(); // creating an event bus.

new Vue({
  render: h => h(App),
}).$mount('#app')

这样我们来改造一下 directory.vue,只需要改动srcipt部分:

<script>
import NestedDir from "./NestedDir";
import { eventBus } from "../main";

export default {
  name: "directory",
  components: {
    NestedDir
  },
  created() {
    this.catalog = this.getTree()

    eventBus.$on('change', function (data) {
      // todo 向之前一样处理即可
    })
  },
  destroyed() {
    eventBus.$off('change')
  },
  computed: {
    maxId() {
      return this.arr.lastIndex   2
    }
  },
  data() {
    return {
      arr: [
        {id: 1, name: '目录1', type: 'dir', children: []},
        {id: 2, name: '目录3', type: 'dir', children: [], pid: 1},
        {id: 3, name: '文件2', type: 'file', pid: 1},
        {id: 4, name: '目录2', type: 'dir', children: []},
        {id: 5, name: '文件1', type: 'file'},
      ],
      title: '',
      dialogFormVisible: false,
      form: {
        id: '',
        name: '',
        type: '',
        pid: ''
      },
      catalog: []
    }
  },
  methods: {
    handleChange(el) {
      this.showDialog(el)
    },
    confirm() {
      this.arr.push({...this.form})
      this.dialogFormVisible = false
      this.catalog = this.getTree()
      this.form = {
        id: '',
        name: '',
        type: '',
        pid: '' , // 父级的id
      }
    },
    showDialog(el) {
      if (el.type === 'dir') {
        this.title = '新增目录'
        this.form.children = []
        this.form.type = 'dir'
      } else {
        this.title = '新增文件'
        this.form.type = 'file'
      }
      if (el.id) {
        this.form.pid = el.id
        this.form.id = this.maxId
      } else {
        this.form.id = this.maxId
      }
      this.dialogFormVisible = true
    },
    getTree() {
      this.topNodes.forEach(node => {
        this.getChildren(this.arr, node.children, node.id)
      })
      return this.topNodes
    },
    getChildren(data, result, pid) {
      for (let item of data) {
        if (item.pid === pid) {
          const newItem = {...item, children: []}
          result.push(newItem)
          this.getChildren(data, newItem.children, item.id)
        }
      }
    }
  }
}
</script>

引入了import { eventBus } from "../main";

在页面创建时加入事件监听,销毁时移除事件监听:

created() {
  eventBus.$on('change', function (data) {
    this.handleChange(data)
  })
},
destroyed() {
  eventBus.$off('change')
}

NestedDir.vue 中也需要做相应改动,只需要修改methods中的add方法:

import { eventBus } from "../main";

//...略

methods: {
  add(el) {
    // this.$emit('change', el)
    eventBus.$emit('change', el)
  }
}

这样点击二级目录的新增按钮,就可以正常触发弹出框了。

上面的eventBus只在Vue2中有效,Vue3中已经移除了$on, $off 这些方法,所以下一篇文章打算自己做一个Vue的插件来处理这种类似于Pub/Sub的情况。

到此这篇关于vue嵌套组件传参实例分享的文章就介绍到这了,更多相关vue嵌套组件传参内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

vue嵌套组件传参实例分享的更多相关文章

  1. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  2. ios – Xcode只看到一些嵌套类的类似扩展,这些扩展是用不同的文件编写的

    解决方法我遇到过类似的问题,似乎编译器正在尝试处理扩展嵌套类的文件,在嵌套类定义之前.因此,您有此错误说该Space没有成员SomeClass.我发现的解决方案是转到目标设置,打开BuildPhases.在“编译源”部分中,您应该将用于定义嵌套类的文件放在扩展它的文件上.这个解决方案似乎甚至可以很好地与您的观察结果一致,当您重新创建文件时,它有时会编译,因为当您重新创建文件时,它在编译源中的位置会发生变化.

  3. ios – 嵌套递归函数

    我试图做一个嵌套递归函数,但是当我编译时,编译器崩溃.这是我的代码:编译器记录arehere解决方法有趣的…它似乎也许在尝试在定义之前捕获到内部的引用时,它是bailing?以下修复它为我们:当然没有嵌套,我们根本没有任何问题,例如以下工作完全如预期:我会说:报告!

  4. ios – 在swift中将捕获列表正确放置在嵌套闭包中

    在Swift中为哪些嵌套闭包定义捕获的引用?如果[weakself]被捕获在只有内部最后面的闭包,GCD将保留ExampleDataSource,直到块完成执行,这就解释了为什么调试看起来像这样:同样的事情会发生,如果没有捕获列表被包括,我们从来没有可选地解开自己,尽管编译器,确实试图警告你!

  5. ios – 无效的软件包 – 嵌套软件包没有在CFBundleSupportedPlatforms Info.plist键中列出的正确平台

    我上传了一个应用程序到iOSAppStoretestflight.iOSAppStore收到以下电子邮件:InvalidBundle–Anestedbundledoesn’thavetherightplatformslistedinCFBundleSupportedplatformsInfo.plistkey.Oncetheseissueshavebeencorrected,youcanthenr

  6. ios – 如何在使用嵌套上下文时自动设置Core Data关系

    解决方法想到的第一个想法是,虽然姓名的人际关系是不可选的,但你并没有说Person的姓名关系也是不可选的.创建一个没有名字的人,可以用您的代码处理,然后在实际需要时创建名称吗?当然不用打扰自定义awakeFromInsert…

  7. cocoa-touch – Interface Builder:如何选择嵌套元素?

    在界面构建器中是否有一种方法可以看到我的元素树,以便我可以选择它们.在实际视图中选择内容非常困难,特别是当我有很多元素和嵌套视图等时.谢谢解决方法尝试按住shift并右键单击元素.它应该显示该元素下的视图层次结构的菜单.实际上,等一下,在层次结构中显示出这个元素之上的元素.你想要做的是使用Nib窗口,选择’Window’然后设置它的列视图,这样你就可以更轻松地导航.

  8. 寒城攻略:Listo 教你 25 天学会 Swift 语言 - 21 Nested Types

    //***********************************************************************************************//1.nestedTypes(类型嵌套)//________________________________________________________________________________

  9. Swift基础语法: 22 - Swift的函数类型, 嵌套函数

    前面我们讲解了函数里面的形参,现在让我们继续来看看函数的类型,以及嵌套函数,让我们一起来看看:1.使用函数类型在Swift中的函数声明和在OC中没什么区别,只有语法上的差异,但在Swift中有一项比较有趣的就是,声明变量或者常量的时候也是可以指定返回值的,比如:当然,常量也是可以如此的,这里就不多做解释了,想知道的朋友可以自己回去试试.2.作为形参类型的函数类型还有更好玩的就是,我们可以在声明新的

  10. Swift类型嵌套

    Swift中的类,结构体和枚举可以进行嵌套,即在某一类型的内部定义类,这种类型嵌套在JAVA中称为内部类,在C#中称为嵌套类,它们的形式和定义是相似的,类型嵌套的有点是能够访问它外部的成员,包括方法,属性和其他的嵌套类型,嵌套还可以有多个层次示例:

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部