基于Vue2.0树形组件的实现

作者 : 开心源码 本文共4879个字,预计阅读时间需要13分钟 发布时间: 2022-05-13 共213人阅读

先简单列一下在实现树组件的过程中少量值得关注的节点。

~ 如何调用组件自身

因为树是一个递归的数据结构,必然需要对组件自身的递归调用。
我们只要给组件指定name属性,就可以在组件内部直接使用。此处需要注意的是每次调用都会生成一个独立的作用域。

<!-- html --><template>  <div>    ...    <my-tree></my-tree>  </div></template><!-- js -->export default {  name: 'myTree',  ...}
~ props及事件监听如何传递给子组件(以下属性需vue2.4.0+)

我们在使用组件的时候可能会指定少量属性以实现对组件的差异化定制,这就需要我们自己去实现对父组件的属性继承。

<my-tree v-bind="{...$props, ...$attrs}" v-on="$listeners"  :data="item[props.children]"  :child-node="true"></my-tree>

$props表示客户调用时指定并且在组件props中有公告去接收的属性集合,没有公告接收的属性则会归类到$attrs中。但是此时我们一定不希望继承父组件的数据,所以我们需要自己指定data去覆盖掉父组件中对应的属性。
此外,当我们想触发子组件的监听事件时,因为子组件的调用者是其父组件,然而我们想要通知的是外层树组件的调用者。此时我们即可以借用$listeners$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器,这样不论子组件所处的层级,事件都会直接触发给外层树组件的调用者。
我们可以子组件中指定一个child-node属性,通过props去接收它,这样即可以非常方便的区分能否是最顶层的作用域。

~ 组件slot

允许客户灵活自己设置内容是必不可少的一环,我们不可能兼顾所有的使用的场景,所以slot的使用也是组件的一部分。
1、slot数据的传递
当我们自己设置组件slot时,我们需要将当前组件的数据传出去以便客户展现数据的内容。
slot分具名slot和不具名slot(即默认的slot)

子组件 -- 不具名slot (以下两种写法是等价的)<slot v-bind="{...item}">{{item.label}}</slot><slot v-bind:default="{...item}">{{item.label}}</slot>子组件 -- 具名slot (指定了名字:emptyText)<slot v-bind:emptyText="keywords">暂无数据</slot>

父组件自己设置slot

<my-tree>  <!-- scope[slotName]的值就是v-bind传递过来的数据 -->  <template v-slot="scope">    <div>{{scope.default.xxx}}</div>  </template>  <template v-slot:emptyText="scope">    <span>{{scope.emptyText}}下无搜索结果</span>  </template></my-tree>

2、slot递归
因为组件递归调用自身,所以组件内部需要将slot逐级传递给子组件。
以下以默认slot为例:

<my-tree v-bind="{...$props, ...$attrs}" v-on="$listeners"  :data="item[props.children]"  :child-node="true">  <template v-slot="scope">    <slot v-bind:default="scope.default"></slot>  </template></my-tree>
~ 组件全局变量

前面提到组件的每次调用都会生成一个独立的作用域,但是很多情况下我们需要一个全局的变量去记录组件的状态,如:当前高亮选中的节点、已开展节点的keys等。
此时我们需求是不管在哪个组件作用域内,修改变量可以达到影响全局的效果,很容易就能想到它–Object对象。所以我们可以通过在顶层组件内定义一个对象,逐级传给所有的子组件,这样我们就能拥有一个全局的变量了。

<!-- 子组件 --><my-tree v-bind="{...$props, ...$attrs}" v-on="$listeners"  :data="item[props.children]"  :child-node="true"  :treeGlobal="treeGlobal">  ...</my-tree><!-- js -->export default {  name: 'myTree',  data() {    let globalTemp = {      currentKey: '', // 当前选中的key      selectedItems: [], // 已选择的节点 (可选状态下)      openedItems: [], // 已开展的节点      ...    }    // treeGlobal 没有props 接收,在 $sttrs 中    if(this.$attrs.treeGlobal) {      globalTemp = this.$attrs.treeGlobal;    }    return {      treeGlobal: globalTemp,      treeData: [],      ...    }  },  ...},

这样我们就拥有了一个全局变量treeGlobal,每个子组件对treeGlobal的修改都会在全局范围内生效。
需要注意的是,记录节点能否开展或者者选中,最容易想到的方法就是给节点增加对应的属性,而后根据操作修改该属性的状态。然而这个属性是在申明了treeData后再在其子对象上追加上去的,vue并不是监听其值得变化,话句话说,它的变化并不会刷新视图(用 $set 也没用的)。当然可以通过$forceUpdate()去强制刷新视图,但官方并不推荐使用$forceUpdate(),更何况每个需要变化的组件自身都需要触发一下$forceUpdate()($forceUpdate()只会刷新当前组件的内容)。以上,我们可以在全局变量中申明一个对象去记录这些状态,监听的事即可以还给vue自身,我们只要关注数据的变化就好了。

~ 部分代码展现

代码太长就不贴了,贴一点主要的部分,辅助了解。

<!--自己设置props? 自己设置缩进? 自己设置行高? 内容slot? 默认开展(key)? 事件响应? 选中样式(默认选中及样式可配置)? 宽度问题? checkbox?--><template>  <div :class="['my-tree-box', childNode ? '' : className]" ref="treeOwnSelf">    <div v-for="item in treeData" :key="item[nodeKey]">      <div :class="['list-cell-box', 'list-cell-leaf', {'current-cell-style': funCurrentItem(item)}]"            v-if="item.isLeaf"            @click="nodeClick(item, 1)"            :style="{paddingLeft: `${indent * item.treeLevel + 10}px`, ...cellHeightStyle}">        <div class="list-label-box">          <div :class="['list-label', {'text-ellipsis': textEllipsis}, {'list-label-checkbox': showCheckbox}]">            <slot v-bind:default="simplifyItem(item)">{{item[props.label]}}</slot>          </div>          <span v-if="showCheckbox"                :class="['select-checkbox', {'active': nodeSelect[item[nodeKey]]}]"                @click.stop="checkboxClick(item)"></span>        </div>      </div>      <template v-if="!item.isLeaf">        <div :class="['list-cell-box', {'current-cell-style': funCurrentItem(item)}]"              @click="nodeClick(item, 1)"              :style="{paddingLeft: `${indent * item.treeLevel + 10}px`, ...cellHeightStyle}">          <div class="list-label-box">            <span :class="['arrow-box', item.isExpand ? 'arrow-bottom' : 'arrow-top']"                  @click.stop="nodeClick(item, 0)"></span>            <div :class="['list-label', {'text-ellipsis': textEllipsis}, {'list-label-checkbox': showCheckbox}]">              <slot v-bind:default="simplifyItem(item)">{{item[props.label]}}</slot>            </div>            <span v-if="showCheckbox"                  :class="['select-checkbox', {'active': nodeSelect[item[nodeKey]] === 2}, {'half': nodeSelect[item[nodeKey]] === 1}]"                  @click.stop="checkboxClick(item)"></span>          </div>        </div>        <div class="list-childs-box" v-if="isExpand(item)">          <my-tree v-bind="{...$props, ...$attrs}" v-on="$listeners"                    :data="item[props.children]"                    :child-node="true"                    :treeGlobal="treeGlobal">            <template v-slot="scope">              <slot v-bind:default="scope.default"></slot>            </template>          </my-tree>        </div>      </template>    </div>  </div></template><!-- props 部分 -->props: {  data: {    type: Array,    default() {return []}  },  props: {    type: Object,    default() {      return {        label: 'label',        children: 'children'      }    }  },  childNode: {    type: Boolean, // 能否内部循环调用的子节点 若是则不重复调用格式化数据    default: false  },  indent: {    type: Number, // 缩进    default: 20  },  cellHeight: {    type: Number, // 单行高度    default: 34  },  nodeKey: {    type: String, // 必需 设置nodeKey    default: ''  },  currentNodeKey: {    type: [String, Number], // 当前选中的节点    default: ''  },  highlightCurrent: {    type: String, // 高亮展现    default: 'leaf' // 'none': 都不高亮 leaf: 仅叶子节点(默认) all: 所有节点  },  className: {    type: String, // 最顶层样式    default: ''  },  showCheckbox: {    type: Boolean, // 显示复选框    default: false  },},

附一张效果图

以上,tks~

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 基于Vue2.0树形组件的实现

发表回复