导读:本期聚焦于小伙伴创作的《如何构建一个无框架、基于原生Web Components的复杂应用?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何构建一个无框架、基于原生Web Components的复杂应用?》有用,将其分享出去将是对创作者最好的鼓励。

原生Web Components提供了自定义元素、shadow DOM、HTML模板等核心能力,能够让开发者在不依赖任何前端框架的情况下,实现高内聚低耦合的组件化开发,进而构建出可维护的复杂应用。下面我们将逐步讲解完整的构建流程。

如何构建一个无框架、基于原生Web Components的复杂应用?

核心基础概念

要构建基于Web Components的复杂应用,首先需要理解三个核心特性:

  • 自定义元素:允许开发者定义自己的HTML标签,浏览器会将其作为普通HTML元素解析,支持自定义属性和方法。
  • shadow DOM:为组件创建独立的DOM和样式作用域,避免组件内部的样式和逻辑与外部产生冲突。
  • HTML模板:通过<template>和<slot>标签定义组件的可复用结构和内容分发规则。

定义基础自定义元素

首先我们通过继承HTMLElement类来定义第一个基础组件,这里以一个按钮组件为例:

// 定义自定义元素类
class MyButton extends HTMLElement {
  // 指定需要监听的属性变化
  static get observedAttributes() {
    return ['text', 'type'];
  }

  // 元素挂载到DOM时触发
  connectedCallback() {
    this.render();
  }

  // 监听的属性发生变化时触发
  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      this.render();
    }
  }

  // 渲染组件内容
  render() {
    const text = this.getAttribute('text') || '默认按钮';
    const type = this.getAttribute('type') || 'default';
    // 设置组件内部内容
    this.innerHTML = `
      <style>
        .btn {
          padding: 8px 16px;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          font-size: 14px;
        }
        .default {
          background: #f0f0f0;
          color: #333;
        }
        .primary {
          background: #1890ff;
          color: #fff;
        }
      </style>
      <button class="btn ${type}">${text}</button>
    `;
  }
}

// 注册自定义元素,标签名为my-button
customElements.define('my-button', MyButton);

注册完成后,我们就可以在HTML中直接使用这个组件:

<my-button text="点击提交" type="primary"></my-button>
<my-button text="取消操作" type="default"></my-button>

使用shadow DOM实现样式隔离

上面的基础组件没有使用shadow DOM,内部样式可能会影响到外部元素,也不受外部样式影响,实际复杂应用中建议使用shadow DOM实现真正的隔离:

class ShadowButton extends HTMLElement {
  static get observedAttributes() {
    return ['text', 'type'];
  }

  constructor() {
    super();
    // 创建shadow DOM,mode为open表示外部可以通过元素.shadowRoot访问
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      this.render();
    }
  }

  render() {
    const text = this.getAttribute('text') || '默认按钮';
    const type = this.getAttribute('type') || 'default';
    // 向shadow DOM中插入内容
    this.shadowRoot.innerHTML = `
      <style>
        .btn {
          padding: 8px 16px;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          font-size: 14px;
        }
        .default {
          background: #f0f0f0;
          color: #333;
        }
        .primary {
          background: #1890ff;
          color: #fff;
        }
        /* 这里的样式只会作用于当前组件的shadow DOM内部 */
      </style>
      <button class="btn ${type}">${text}</button>
    `;
  }
}

customElements.define('shadow-button', ShadowButton);

复杂组件的结构设计

构建复杂应用需要拆分多个不同层级的组件,比如我们可以拆分出布局组件、列表组件、表单组件等。以一个简单的用户列表组件为例,使用<template>和<slot>实现内容分发:

<!-- 定义列表项的模板 -->
<template id="user-item-template">
  <style>
    .user-item {
      display: flex;
      align-items: center;
      padding: 12px;
      border-bottom: 1px solid #eee;
    }
    .avatar {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      margin-right: 12px;
    }
    .info {
      flex: 1;
    }
    .name {
      font-weight: bold;
      margin-bottom: 4px;
    }
    .desc {
      color: #666;
      font-size: 12px;
    }
  </style>
  <div class="user-item">
    <img class="avatar" src="" />
    <div class="info">
      <div class="name"><slot name="name">默认名称</slot></div>
      <div class="desc"><slot name="desc">默认描述</slot></div>
    </div>
  </div>
</template>

对应的自定义元素逻辑:

class UserItem extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    // 获取模板内容
    const template = document.getElementById('user-item-template');
    const content = template.content.cloneNode(true);
    // 设置头像地址
    const avatar = content.querySelector('.avatar');
    avatar.src = this.getAttribute('avatar') || 'https://picsum.photos/40/40?random=1';
    // 将模板内容插入shadow DOM
    this.shadowRoot.appendChild(content);
  }
}

customElements.define('user-item', UserItem);

使用时可以通过slot插入自定义内容:

<user-item avatar="https://picsum.photos/40/40?random=2">
  <span slot="name">张三</span>
  <span slot="desc">前端开发工程师</span>
</user-item>

跨组件状态管理

复杂应用必然涉及跨组件的状态共享,我们可以使用发布订阅模式实现一个简单的全局状态管理:

// 全局状态管理器
class Store {
  constructor() {
    this.state = {};
    this.listeners = new Map();
  }

  // 更新状态并通知所有监听者
  setState(key, value) {
    this.state[key] = value;
    if (this.listeners.has(key)) {
      this.listeners.get(key).forEach(callback => callback(value));
    }
  }

  // 获取状态
  getState(key) {
    return this.state[key];
  }

  // 监听状态变化
  subscribe(key, callback) {
    if (!this.listeners.has(key)) {
      this.listeners.set(key, []);
    }
    this.listeners.get(key).push(callback);
  }
}

// 创建全局单例store
const globalStore = new Store();

在组件中监听状态变化:

class StatusDisplay extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `<div>当前状态:<span id="status">未加载</span></div>`;
  }

  connectedCallback() {
    // 监听userStatus状态变化
    globalStore.subscribe('userStatus', (status) => {
      this.shadowRoot.querySelector('#status').textContent = status;
    });
  }
}

customElements.define('status-display', StatusDisplay);

// 另一个组件更新状态
class StatusUpdater extends HTMLElement {
  connectedCallback() {
    this.innerHTML = '<button>更新状态</button>';
    this.querySelector('button').addEventListener('click', () => {
      globalStore.setState('userStatus', '已登录');
    });
  }
}

customElements.define('status-updater', StatusUpdater);

应用路由实现

复杂应用通常需要路由功能,我们可以基于原生History API实现简单的路由:

class Router {
  constructor() {
    this.routes = new Map();
    this.currentComponent = null;
    // 监听浏览器前进后退
    window.addEventListener('popstate', () => {
      this.render(location.pathname);
    });
  }

  // 注册路由
  register(path, componentName) {
    this.routes.set(path, componentName);
  }

  // 跳转路由
  navigate(path) {
    history.pushState(null, '', path);
    this.render(path);
  }

  // 渲染对应路由的组件
  render(path) {
    const appContainer = document.getElementById('app');
    // 移除当前组件
    if (this.currentComponent) {
      this.currentComponent.remove();
    }
    const componentName = this.routes.get(path) || this.routes.get('/');
    if (componentName) {
      const component = document.createElement(componentName);
      appContainer.appendChild(component);
      this.currentComponent = component;
    }
  }
}

const router = new Router();
// 注册路由
router.register('/', 'home-page');
router.register('/user', 'user-page');
// 初始渲染
router.render(location.pathname);

应用整合与优化建议

当所有组件开发完成后,我们可以按照功能模块整合整个应用,同时需要注意以下优化点:

  • 组件的属性变化尽量做节流处理,避免频繁触发渲染。
  • 对于不常变化的静态内容,可以使用缓存减少重复渲染开销。
  • 复杂组件可以拆分更小的子组件,保持单个组件的职责单一。
  • 可以使用自定义事件实现组件间的直接通信,减少全局状态的滥用。

通过以上步骤,我们就可以完全基于原生Web Components构建一个功能完整的复杂应用,整个过程不需要引入任何第三方框架,应用的包体积会更小,同时也能享受到浏览器原生能力的支持,兼容性也能覆盖大部分现代浏览器。

Web_Components自定义元素shadow_DOM无框架开发修改时间:2026-07-01 16:57:59

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。