基于 Web Components 封装下拉树组件 select-tree

一、封装背景与需求分析

1. 老项目技术困境

在公司某核心业务系统中,存在一个运行多年的 jQuery 老系统。随着业务复杂度提升,原有 UI 组件库不满足业务要求:缺乏 现代树形控件 必备的多选、懒加载、节点合并等能力

特别是在机构管理模块中,急需实现类似下图的多层级多选功能:

2. 选型决策

在传统 jQuery 项目中实现组件化面临三大障碍:

全局污染:命名冲突、样式覆盖

复用困难:依赖特定上下文环境

维护成本高:逻辑与 DOM 强耦合

Web Components 提供了浏览器原生的解决方案:

特性 作用 在本组件中的应用场景
Custom Elements 自定义 HTML 标签 <select-tree> 声明式使用
Shadow DOM 样式隔离与封装 组件内部样式与全局样式隔离
HTML Templates 声明式 DOM 结构 组件模板与逻辑分离
ES Modules 模块化开发 组件代码组织与依赖管理

最终,我决定采用以下方案:

技术栈选择:纯 Web Components 方案(Custom Elements + Shadow DOM)

底层依赖:复用 ZTree 核心能力(节点操作、事件体系、懒加载)

隔离策略:通过 Shadow DOM 实现样式沙箱,避免污染老项目全局样式

该方案在兼容性、开发效率和维护成本之间取得最佳平衡。


二、组件架构设计

1. 核心设计原则

graph TD A[用户交互] --> B[Shadow DOM] B --> C{事件处理} C --> D[Tree 数据操作] D --> E[ZTree API] E --> F[数据同步] F --> G[视图更新]

分层解耦:将 DOM 操作、事件处理、数据管理划分为独立层级

最小侵入:仅暴露必要的 public API(setValue/getValue等)

渐进增强:降级支持普通 input 行为,保证基础可用性

2. 技术实现亮点

(1)Shadow DOM 封装

html 复制代码
<!-- Shadow DOM 结构 -->

<div class="select-tree-container">

  <input readonly class="select-input">

  <div class="tree-container ztree"></div>

</div>

 

/* Shadow DOM 样式 */

<style>

  :host { display: inline-block; }

  .select-input {

    width: 100%;

    padding: 8px;

    border: 1px solid #ccc;

  }

  .tree-container {

    position: absolute;

    top: 100%;

    max-height: 300px;

    overflow-y: auto;

  }

</style>

(2)双向数据绑定

通过自定义事件实现与外部系统的双向通信:

javascript 复制代码
// 值变更时触发自定义事件

_updateValue() {

  const checkedNodes = this.handleCheckedNodesData();

  this.dispatchEvent(

    new CustomEvent("value-change", {

      detail: { value: checkedNodes.map(n => n.id) },

      bubbles: true,

      composed: true

    })

  );

}

 

// 外部可通过 value 属性设置初始值

static get observedAttributes() {

  return ['value'];

}

 

attributeChangedCallback(name, oldValue, newValue) {

  if (name === 'value') {

    this.setValue(newValue.split(','));

  }

}

三、核心代码解析

1. Web Components 基础搭建

javascript 复制代码
class SelectTree extends HTMLElement {

  constructor() {

    super();

    this.attachShadow({ mode: 'open' });

    

    // 初始化 Shadow DOM

    this.shadowRoot.innerHTML = `

      <style>

        /* 组件内联样式 */

        .select-input { ... }

        .tree-container { ... }

      </style>

      <div class="select-tree-container">

        <input readonly class="select-input">

        <div class="tree-container ztree"></div>

      </div>

    `;

    

    // 缓存 DOM 引用

    this.inputElement = this.shadowRoot.querySelector('.select-input');

    this.treeContainer = this.shadowRoot.querySelector('.tree-container');

    

    // 事件绑定

    this.inputElement.addEventListener('click', () => this.toggleTree());

    document.addEventListener('click', (e) => {

      if (!this.contains(e.target)) {

        this.closeTree();

      }

    });

  }

 

  connectedCallback() {

    // 初始化树容器

    this.initTree();

  }

}

 

customElements.define('select-tree', SelectTree);

2. 树形功能增强

(1)多选逻辑封装

javascript 复制代码
handleCheckedNodesData() {

  const nodes = this.tree.getCheckedNodes(true);

  return this.mergeNodeType === 'leaf'

    ? nodes.filter(n => !n.children)

    : nodes;

}

 

updateValue() {

  const checkedNodes = this.handleCheckedNodesData();

  this.value = checkedNodes.map(n => n.id);

  this._updateInputText();

}

(2)样式隔离实践

css 复制代码
/* Shadow DOM 内样式 */

.select-tree-container {

  position: relative;

  width: 100%;

}

 

/* 透传 ZTree 基础样式 */

.tree-container {

  padding: 5px;

  border: 1px solid #ccc;

}

 

/* 自定义展开图标 */

.tree-container .node-icon {

  margin-right: 8px;

}

四、与老项目集成实践

html 复制代码
<!-- 第一阶段:独立页面验证 -->

<select-tree id="demoTree"></select-tree>

 
<script>

   document.getElementById('demoTree').init();
   document.getElementById('demoTree').setOptions([]);
</script>

 

<!-- 第二阶段:局部替换 -->

<div>

  <label>所属机构:</label>

  <select-tree id="deptId"></select-tree>

</div>

 

<!-- 第三阶段:全局样式适配 -->

<link rel="stylesheet" href="/css/ztree-overrides.css">

通过以上实践,我们在保留 ZTree 核心能力的同时,成功将其改造为符合现代标准的 Web Component 组件,显著提升了组件复用率和开发效率。

相关推荐
「、皓子~20 分钟前
后台管理系统的诞生 - 利用AI 1天完成整个后台管理系统的微服务后端+前端
前端·人工智能·微服务·小程序·go·ai编程·ai写作
就改了22 分钟前
Ajax——在OA系统提升性能的局部刷新
前端·javascript·ajax
凌冰_24 分钟前
Ajax 入门
前端·javascript·ajax
京东零售技术39 分钟前
京东小程序JS API仓颉改造实践
前端
老A技术联盟1 小时前
从小白入门,基于Cursor开发一个前端小程序之Cursor 编程实践与案例分析
前端·小程序
风铃喵游1 小时前
构建引擎: 打造小程序编译器
前端·小程序·架构
sunbyte1 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | ThemeClock(主题时钟)
前端·javascript·css·vue.js·前端框架·tailwindcss
小飞悟1 小时前
🎯 什么是模块化?CommonJS 和 ES6 Modules 到底有什么区别?小白也能看懂
前端·javascript·设计
浏览器API调用工程师_Taylor1 小时前
AOP魔法:一招实现登录弹窗的全局拦截与动态处理
前端·javascript·vue.js
FogLetter1 小时前
初识图片懒加载:让网页像"懒人"一样聪明加载
前端·javascript