全局组件实现递归树,避免循环引用

概述

目录分类展示会通常要用到树形结构。本例结合vue的父子组件,采用递归渲染,实现一个基于树的curd小demo

知识点

  • 父子组件递归渲染
  • class 样式对象写法,CSS伪元素 ::before 用法
  • vue 指令v-model,内联模板x-template
  • 事件 dblclick blur, 防止事件传播修饰符 stop
  • 引用父实例属性 $parent, 全局方法 Vue.set Vue.delete,子组件类型校验props

效果图

vue 实现树形数据 curd

节点模型

树形结构,节点通常由数据域+子节点引用组成。缺少指向子节点引用的节点,称之为叶子节点。
同理,缺少父节点的节点谓之根节点。二者分别代表递归的出口和入口,在逻辑部分通过动态响应实现渲染效果。

  • 下面这段代码描述了一个 data 基本treedata模型节点对象。 namestring 类型,children是数组类型。
var data = {
  name: 'home',
  children: [
    { name: 'hello.js' },
  ]
}

模板

  • x-template 形式描述当单个节点,然后递归复用该组件。
  • 用文件与文件夹关系类比,文件则显示文件名,有文件夹则迭代。
  • 所呈现的无非文件或文件夹,遇文件夹则递归使用组件本身。

用v-model实现双向绑定,双击文件名则达到文件变身文件夹,此外有相应的清除,删除,修改vue事件绑定

    <script type="text/x-template" id="item-template">
    <li>
        <div :class="{foldero:isFolder && open ,folderc: !(isFolder && open),file:!isFolder}" 
            @click="toggle" @dblclick="changeType">
            <span v-show="show">{{ name }}</span>
            <input type="text"  v-show="!show" v-model="name"
               @blur="doneEdit(name)" 
               @keyup.enter="doneEdit(name)" 
               @keyup.esc="cancelEdit()" 
               @click.stop @dblclick.stop >
            <span @click.stop='editToggle'>[e]</span>
            <span @click.stop='delChild' v-show="isFolder">[c]</span>
            <span @click.stop='delFile' v-show="!isFolder">[x]</span>
            <span v-if="isFolder">[{{ open ? '-':'+'}}]</span>
        </div>
        <ul v-show="open" v-if="isFolder">
            <item class="item" 
                v-for="(treeData,index) in this.model.children" :
                model="treeData" :key="index"/>
            <li><span class="add" @click.stop="addChild">[append] </span></li>
        </ul>
    </li>
    </script>

组件

  • 注册全局组件 item
    • 需要注意在直接在 DOM (即非字符串的模板如 x-template ) 中使用时只有 item 有效
    • 约定校验数据类型是Object,属性名 model 视为组件接收父组件的模型数据接口
Vue.component('item', {
  template: '#item-template',
  // 类型校验,接收父组件传递过来的数据(只读) 
  props: {
    model: Object,
  },
  computed: {
    isFolder() {
      return this.model.children && this.model.children.length
    }
  },
  ...

方法

  • 动态增/删新节点
    • Vue.delete 清除文件夹下子节点
    • Vue.set 向响应式对象中添加一个新属性,需确保这个新属性同样是响应式的,以触发视图更新
  methods: {
    changeType() {
      if (!this.isFolder) {
        Vue.set(this.model, 'children', [])
        this.addChild()
        this.open = true
      }
    },
    addChild() {
      this.model.children.push({name: 'new file'})
    },
    delChild() {
      if (this.isFolder) {
        Vue.delete(this.model, 'children')
      }
    }
    ...
  • 删除文件
    • 通过 $prarent 引用父实例节点删除当前节点
    • 用引用类型数组方法, indexOf 定位当前模型在父节点中的索引,splice 改变数据源
    • 需要排除没有父节点(即当前节点是根节点情况),以及判断父节点是一个完整节点(是否存在有效子节点)
    delFile() {
      if (this.$parent.model === undefined) {
        return
      }
      var parent = this.$parent.model.children
      if (parent && Array.isArray(parent) && parent.length) {
        parent.splice(parent.indexOf(this.model), 1)
      }
    },

源码

vue实现目录的curd

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!