树在前端的应用 | 树的操作

引言:上篇我们说了如何根据实际业务抽象表达式树,本篇我们就来讲讲树的操作在业务中的应用。

树的操作

树的操作有很多,根据基本的增删改查进行分类,结果如下:

增加操作:

  • 插入节点:在树中插入一个新节点。
  • 添加子节点:将一个节点作为另一个节点的子节点添加到树中。

删除操作:

  • 删除节点:从树中删除指定的节点。
  • 删除子树:删除一个节点及其所有子节点。

修改操作:

  • 更新节点值:修改树中指定节点的值。
  • 移动节点:将一个节点从一个位置移动到另一个位置。

查询操作:

  • 查找节点:在树中搜索指定值的节点。
  • 遍历树:按照特定顺序访问树中的所有节点。
  • 获取子树:获取一个节点及其所有子节点组成的子树。
  • 获取父节点:获取指定节点的父节点。
  • 获取兄弟节点:获取指定节点的兄弟节点(具有相同父节点的节点)。
  • 获取子节点:获取指定节点的所有子节点。
  • 获取根节点:获取树的根节点。
  • 判断节点是否为叶子节点:检查节点是否没有子节点。
  • 判断节点是否为根节点:检查节点是否没有父节点。
  • 判断节点是否为某节点的子节点:检查节点是否是指定节点的子节点。
  • 判断节点是否为某节点的祖先节点:检查节点是否是指定节点的祖先节点。

从上面我们可以看出,树的操作有非常多种,但这里,我们只谈基于上篇的结构所涉及到的操作。

业务需求

接下来,我们从实际的业务需求出发,先对业务需求进行分析,然后拆解出针对树的操作,再实现实际代码。

根据原型,我们列出可能的操作:

1、切换交并类型

切换交并类型属于对节点的修改。

2、拖拽标签到组内

拖拽标签到组内,对标签节点属于添加节点,对类型标签属于修改节点。

3、增加或者删除组

属于添加、删除节点操作。

4、根据标签组合生成sql

这属于表达式树的遍历生成。

在开始实际代码之前,我们先根据上篇定义的数据结构,初始化一个嵌套对象。 数据结构:

typescript 复制代码
interface ExpressionNode { 
    // 索引,保证唯一性
    id: string
    // 节点类型 
    type: 'inter'| 'union' | 'diff' | 'label' 
    // 节点的值 
    value: 'and' | 'or' | 'not' | LabelNode, 
    // 子节点,可选 
    children?: ExpressionNode[] 
}

这里增加了一个 id 索引,保证每个节点的唯一性。 初始化对象为: 这里要说明的是,为了方便操作,我并没有存储最上层的 交集操作,而是将其存储为一个数组对象。 为了方便大家对照,再放一遍定义的表达式树:

typescript 复制代码
const labelOps: ExpressionNode[] = [
    {
        id: '1',
        // 此type为组间交并关系
        type: 'union',
        value: 'or',
        children: [
          {
            id: '11',
            // 此type为组内交并关系
            type: 'union',
            value: 'or',
            children: [],
          },
          {
            id: '12',
            // 此type为组内交并关系
            type: 'union',
            value: 'or',
            children: [],
          },
        ],
    },
    {
        id: '0',
        // 此为差集
        type: 'diff',
        value: 'not', 
        children: [
          {
            id: '01',
            // diff 内的标签是并集
            type: 'union',
            value: 'or',
            children: [],
          },
        ],
    },
]

定义好了初始结构,我们就来按照所涉及的操作来写实现函数。

1、切换交并类型

切换交并分为组间的交并类型切换,和组内的交并类型切换。

组间的交并类型切换是直接改变数组第一项的type和value 类型。 我们可以定义函数如下:

typescript 复制代码
const valueMap = {
    'union': 'or',
    'inter': 'and',
    'diff': 'not',
}

const onClickGroupType = (type)=>{
labelOps[0].type = type;
labelOps[0].value = valueMap[type]
}

上面只是一个改变的示例,在实际的代码中,由于这个节点的值跟页面样式直接相关,所以,这里的type是直接和dom绑定的。

2、增加或者删除组

将对象转化为数组之后,处理就方便多了。因为我们这里只有两层结构,不同的删除只需要去判断不同的层,然后通过filter 进行过滤就好了。 这里我简单列下代码:

typescript 复制代码
// jsx 代码
{uniInterFilter.value.children.map((group, index) => (
  <div class="group-item" key={group.id}>
    <div class="flex-space-between">
      <span>
        <span class="title btn-right-12">组合{index + 1}</span>
        {index > 1 && <DeleteOutlined style="color: #8594AD;" onClick={() => clearGroupLabel(index)} />}
      </span>
      {/* 组内交并差 */}
      <a-radio-group v-model:value={group.type} button-style="solid" size="small">
        <a-radio-button value="union">并集</a-radio-button>
        <a-radio-button value="inter">交集</a-radio-button>
      </a-radio-group>
    </div>
  </div> )))
// 函数
//  响应点击删除当前组合
const clearGroupLabel = (delIndex: number) => {
  uniInterFilter.value.children = uniInterFilter.value.children.filter((item, curIndex) => curIndex !== delIndex)
}

3、拖拽标签到组内

拖拽标签也是利用了draggable 第三方库,直接绑定数组到上面完成的。

4、根据标签组合生成sql

如果是一个二叉树,那么这个遍历过程用中序来做最合理,但是由于这是一个多叉树,所以通过深度遍历的方式完成,在将每个子节点处理成sql后,再根据当前的type类型进行组合。

typescript 复制代码
/** 将交 or 并 or 差集标签列表转为sql */
export const getTreeSql = (type: 'ck' | 'hive', labelOp: LabelExpressionNode): string => {
  // 如果是标签的话,直接将标签转为sql
  if (labelOp.type === 'label') return labelToSql(type, labelOp.value!)

  const labelSqlList = labelOp.children
    .map((child) =>  getTreeSql(type, child))
    .filter(Boolean)   // 过滤掉空的标签或数组

  if (!labelSqlList.length) return ''
  // 根据当前的type类型进行组合,如果存在多个组合的时候,就添加外括号
  return `${labelSqlList.length > 1 ? `(${labelSqlList.join(valueMap[labelOp.type])})` : labelSqlList.join(valueMap[labelOp.type])}`
}

在实际需求中,基本上所涉及到的相关树的操作就是以上。

相关推荐
小亦苦学编程3 分钟前
HTML基础用法介绍二
前端·javascript·css·html
无名小小卒7 分钟前
三小时快速上手TypeScript,TS速通教程(上篇、中篇、下篇、附加篇)
开发语言·前端·typescript
qq_4246358810 分钟前
要实现在Vue 2中点击按钮后在新浏览器标签页中预览PDF文件 ,pdf文件默认放大125% 禁止PDF的工具栏下载功能
前端·vue.js·pdf
JAMJAM_NoName13 分钟前
【前端学习】前端存储--Cookie、localStorage和sessionStorage
前端·学习
秋雨凉人心20 分钟前
Webpack和GuIp打包原理以及不同
开发语言·前端·javascript·webpack·gulp
john_hjy21 分钟前
4. 数据结构: 对象和数组
java·开发语言·前端
bjzhang751 小时前
使用Chrome浏览器时打开网页如何禁用缓存
前端·chrome·缓存
夏天想1 小时前
uni-app+vue3+pina实现全局加载中效果,自定义全局变量和函数可供所有页面使用
前端·javascript·uni-app
深情废杨杨1 小时前
前端vue-form表单的验证
前端·javascript·vue.js
Fenderisfine1 小时前
使用 vite 快速初始化 shadcn-vue 项目
前端·css·vue.js·前端框架·postcss