Vue3计算属性终极实战:可媲美Element Plus Tree组件研发之节点勾选

前面完成了JuanTree组件的节点编辑和保存功能后,我们把精力放到节点勾选功能实现上来。**注意,对于组件的开发者来说,要充分考虑用户的使用场景,组件提供的多个特性同时启用时必须要工作良好。**就拿Tree组件来说,用户完全可以在启用勾选的情况下,同时启用节点增删功能,此时增删不能影响到勾选的一致性。

Tree组件的节点勾选会向下级联所有子孙节点,向上也会影响父节点的全选和半选状态。大部分教程在实现节点勾选,主导的思路就是手动的遍历处理,实现的非常麻烦;本教程的思路让功能实现变得非常简单------使用可写的计算属性即可!

可以拿官方的例子来复习下可写计算属性的用法:

然后我们开始上车,进入计算属性的终极实战,Are you ready? Gooooo!

用法示例

示例演示

启用设置:

具体实现如下

组件属性和ts类型

JuanTree组件的可选配置上再新增一个可选项,基于它来决定是否启用节点勾选功能。

ts 复制代码
export interface OptionProps {
  ...
  checkable?: boolean // 是否启用勾选,默认不启用
}

节点勾选会用三种状态:未选中、选中和半选中,为此定义一个枚举类型:

ts 复制代码
export enum ECheckedStatus {
  UNCHECKED,
  CHECKED,
  HALF_CHECKED
}

JuanTree的结构化节点类型ITreeNode中新增勾选相关的属性:

ts 复制代码
// 结构化节点
export interface ITreeNode {
  ...
  checkedStatus?: WritableComputedRef<ECheckedStatus> // 可写的勾选计算属性
  checked?: boolean // 是否被选中,实际操作时用到的属性,也会参与到勾选计算属性的计算中
}

计算属性核心逻辑

utils.ts工具中编写结构化节点ITreeNode的初始化绑定函数,关键代码的注释都加了,操作逻辑非常的简单易读:

ts 复制代码
// 初始化结构化节点
function initTreeNode(treeNode: ITreeNode, props: OptionProps) {
  // 如果没有启用checkable,则返回
  if (!props.checkable) return
  // 获得节点的响应式操作对象
  const node = ref(treeNode).value
  const childrenName = props.childrenName as 'children'
  // 绑定节点的勾选计算属性
  treeNode.checkedStatus = computed({
    get() {
      // 如果是叶子节点,返回checked属性值
      if (!node[childrenName]) {
        // 叶子节点只有两种状态:选中和未选中
        return node.checked ? ECheckedStatus.CHECKED : ECheckedStatus.UNCHECKED
      } else {
        // 父节点的勾选状态判断逻辑,需要统计子节点的勾选状态
        let checkedCount = 0,
          uncheckedCount = 0
        // 遍历子一代节点列表
        node[childrenName].forEach((item) => {
          // 获取子节点的勾选计算属性得到的状态枚举值
          const status = item.checkedStatus
          // 统计选中的数量
          if (status === ECheckedStatus.CHECKED) {
            checkedCount++
          } else if (status === ECheckedStatus.UNCHECKED || status == null) {
            // 统计未选中的数量
            uncheckedCount++
          }
        })
        const childrenLength = node[childrenName].length
        // 先进行全选中的判断:存在选中的且选中数量等于子一代节点数量
        if (checkedCount > 0 && checkedCount === childrenLength) {
          return ECheckedStatus.CHECKED
        } else if (uncheckedCount === childrenLength) {
          // 再进行未选中的判断:没有全选中并且未选中的数量等于子一代节点数量
          return ECheckedStatus.UNCHECKED
        } else {
          // 其余则归结为半选
          return ECheckedStatus.HALF_CHECKED
        }
      }
    },
    set(status: ECheckedStatus) {
      // 基于写入的状态来判断checked值
      const checked = status === ECheckedStatus.CHECKED
      // 设置checked属性
      node.checked = checked
      // 如果是父节点则进行子一代节点的级联设置,注意设置子节点的checkedStatus计算属性会触发递归调用,达到我们向下递归设置的目的!
      if (node[childrenName]) {
        node[childrenName].forEach((child) => {
          child.checkedStatus = status
          child.checked = checked
        })
      }
    }
  })
}

初始化扁平化节点的逻辑完善:

新增一级节点的处理逻辑完善:

Tree原始数据结构拍平函数的完善:

tsx模板完善

新增一个点击勾选的事件处理函数

ts 复制代码
// 节点勾选点击事件处理函数
const checkNode = (node: IFlatTreeNode) => {
  // 注意,是对原始节点进行操作
  const oNode = node.originalNode
  const status = oNode.checkedStatus as any
  // 如果没有选中(可能是半选),执行选中的计算属性写入
  if (status !== ECheckedStatus.CHECKED) {
    oNode.checkedStatus = ECheckedStatus.CHECKED as any
  } else {
    // 如果选中则执行反选写入计算属性
    oNode.checkedStatus = ECheckedStatus.UNCHECKED as any
  }
}

tsx模板中增加选中的图标组件svg-icon,并按照选中状态动态设置icon属性

相关推荐
会功夫的李白16 小时前
uniapp自动构建pages.json的vite插件
前端·uni-app·vite
零凌林2 天前
使用exceljs将excel文件转化为html预览最佳实践(完整源码)
前端·html·excel·vue3·最佳实践·文件预览·exceljs
岁岁岁平安3 天前
Vue3学习(组合式API——reactive()和ref()函数详解)
前端·javascript·vue.js·学习·vue3·reactive·ref
三天不学习3 天前
Vue3 本地环境 Vite 与生产环境 Nginx 反向代理配置方法汇总【反向代理篇】
运维·nginx·vue3·vite·反向代理
岁岁岁平安4 天前
Vue3学习(组合式API——计算属性computed详解)
前端·javascript·vue.js·学习·computed·计算属性
小张快跑。6 天前
【Vue3】使用vite创建Vue3工程、Vue3基本语法讲解
前端·前端框架·vue3·vite
halo141610 天前
vue中scss使用js的变量
javascript·vue3·scss
緑水長流*z10 天前
(14)Element Plus项目综合案例
vue.js·elementui·vue3·element plus·elementplus项目·完整项目案例·项目学习笔记
A-刘晨阳10 天前
Algolia - Docsearch的申请配置安装【以踩坑解决版】
vue3·ts·vuepress·algolia·docsearch
我是哈哈hh11 天前
【Vue】全局事件总线 & TodoList 事件总线
前端·javascript·vue.js·vue3·vue2