CSS `Custom Elements` `Lifecycle Callbacks` 结合样式更新

admin
Tue, 22 Jul 2025 11:22:23 +0000
CSS, css, custom, elements

各位观众老爷，大家好！今天咱们聊点儿有意思的，关于 Web Components 里面那些“生命周期回调函数”和它们怎么跟 CSS 搅和在一起，搞出点新花样。保证让各位听完之后，感觉自己又行了！ 开场白：Web Components，组件化的未来？ 现在前端框架满天飞，Vue、React、Angular，个个都说自己是宇宙第一。但实际上，Web Components 才是真正“官方钦定”的组件化方案。它不依赖任何框架，直接靠浏览器原生支持，这才是真正的“一次编写，到处运行”！ Web Components 主要由三个部分组成： Custom Elements (自定义元素): 允许你定义自己的 HTML 标签。 Shadow DOM (影子 DOM): 为你的组件提供独立的 DOM 树，避免样式冲突。 HTML Templates (HTML 模板): 让你能定义可重用的 HTML 片段。 今天咱们重点关注 Custom Elements，尤其是它的“生命周期回调函数”，它们就像组件的“生老病死”记录仪，告诉你组件什么时候出生（添加到 DOM），什么时候更新，什么时候要驾鹤西去（从 D …

继续阅读“CSS `Custom Elements` `Lifecycle Callbacks` 结合样式更新”

The post CSS `Custom Elements` `Lifecycle Callbacks` 结合样式更新 first appeared on 智猿学院-前后端，数据库，人工智能，云计算等领域前沿技术讲座.

各位观众老爷，大家好！今天咱们聊点儿有意思的，关于 Web Components 里面那些“生命周期回调函数”和它们怎么跟 CSS 搅和在一起，搞出点新花样。保证让各位听完之后，感觉自己又行了！

开场白：Web Components，组件化的未来？

现在前端框架满天飞，Vue、React、Angular，个个都说自己是宇宙第一。但实际上，Web Components 才是真正“官方钦定”的组件化方案。它不依赖任何框架，直接靠浏览器原生支持，这才是真正的“一次编写，到处运行”！

Web Components 主要由三个部分组成：

Custom Elements (自定义元素): 允许你定义自己的 HTML 标签。

Shadow DOM (影子 DOM): 为你的组件提供独立的 DOM 树，避免样式冲突。

HTML Templates (HTML 模板): 让你能定义可重用的 HTML 片段。

今天咱们重点关注 Custom Elements，尤其是它的“生命周期回调函数”，它们就像组件的“生老病死”记录仪，告诉你组件什么时候出生（添加到 DOM），什么时候更新，什么时候要驾鹤西去（从 DOM 移除）。

第一部分：生命周期回调函数：组件的“人生大事”

Custom Elements 提供了几个关键的生命周期回调函数，它们会在组件的不同阶段被自动调用：

1. constructor() (构造函数):

这是组件的“出生证明”，在组件实例被创建时调用。

主要用来初始化组件的状态，设置默认值，绑定事件监听器（但别急着操作 DOM）。

重要提示： 必须调用 super()，不然你会收到一个大大的错误！

class MyElement extends HTMLElement {
  constructor() {
    super(); // 必须调用！
    this._message = 'Hello, World!'; // 初始化状态
  }
}

2. connectedCallback() (连接回调):

组件被添加到 DOM 树时调用，就像组件正式“入职”了。

通常在这里进行 DOM 操作，比如创建子元素，设置属性，添加事件监听器。

这个回调函数可能会被多次调用，比如组件被移动到 DOM 树的不同位置。

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' }); // 创建 Shadow DOM
  }

  connectedCallback() {
    // 在 Shadow DOM 中创建内容
    this.shadowRoot.innerHTML = `

        p { color: blue; }

${this._message}
    `;
  }
}

customElements.define('my-element', MyElement);

3. disconnectedCallback() (断开连接回调):

组件从 DOM 树中移除时调用，就像组件“离职”了。

在这里进行清理工作，比如移除事件监听器，释放资源，防止内存泄漏。

class MyElement extends HTMLElement {
  connectedCallback() {
    this.addEventListener('click', this._handleClick);
  }

  disconnectedCallback() {
    this.removeEventListener('click', this._handleClick); // 移除事件监听器
  }

  _handleClick() {
    console.log('Clicked!');
  }
}

4. attributeChangedCallback(name, oldValue, newValue) (属性改变回调):

组件的属性发生变化时调用，就像组件的“个人信息”被修改了。

只有在 observedAttributes 属性中声明的属性才会触发这个回调函数。

name 是属性名，oldValue 是旧值，newValue 是新值。

class MyElement extends HTMLElement {
  static get observedAttributes() {
    return ['message']; // 声明要监听的属性
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'message') {
      this._message = newValue; // 更新组件的状态
      this.render(); // 重新渲染组件
    }
  }

  connectedCallback() {
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = `
${this._message}
    `;
  }
}

observedAttributes

这个静态属性非常重要。它告诉浏览器，你关心哪些属性的变化。只有在 observedAttributes 中声明的属性，其变化才会触发 attributeChangedCallback。

static get observedAttributes() {
  return ['my-attribute', 'another-attribute'];
}

attributeChangedCallback 参数详解

name: 发生变化的属性的名称 (字符串)。

oldValue: 属性之前的旧值 (字符串)。如果属性之前不存在，则为 null。

newValue: 属性的新值 (字符串)。

何时使用 attributeChangedCallback?

当你需要根据外部属性的变化来更新组件的内部状态或外观时，attributeChangedCallback 就派上用场了。例如：

根据 theme 属性的值来切换不同的 CSS 样式。

根据 disabled 属性的值来禁用组件的交互。

根据 value 属性的值来更新输入框的内容。

第二部分：CSS 和生命周期回调：让组件更“听话”

现在我们已经了解了生命周期回调函数，接下来看看它们如何与 CSS 结合，让我们的组件更加灵活和可控。

1. 使用属性选择器：根据属性值改变样式

我们可以使用 CSS 的属性选择器，根据组件的属性值来改变样式。这在与 attributeChangedCallback 结合时非常有用。

<my-element message="Hello"></my-element>
<my-element message="Goodbye"></my-element>


  my-element[message="Hello"] p {
    color: green;
  }

  my-element[message="Goodbye"] p {
    color: red;
  }



  class MyElement extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: 'open' });
    }

    connectedCallback() {
      this.render();
    }

    static get observedAttributes() {
      return ['message'];
    }

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

    render() {
      this.shadowRoot.innerHTML = `

          :host {
            display: block; /* 让组件占据独立的块级空间 */
          }
          p {
            font-size: 20px;
          }

${this.getAttribute('message')}
      `;
    }
  }

  customElements.define('my-element', MyElement);


在这个例子中，我们使用了 my-element[message="Hello"] 和 my-element[message="Goodbye"] 选择器，根据 message 属性的值来设置不同的颜色。

2. 使用 :host 选择器：设置组件自身的样式

:host 选择器允许你设置组件自身的样式，例如背景颜色、边框、字体等。

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `

        :host {
          display: block;
          border: 1px solid black;
          padding: 10px;
        }

        p {
          color: purple;
        }

This is my element!
    `;
  }
}

customElements.define('my-element', MyElement);

在这个例子中，我们使用了 :host 选择器来设置组件的边框和内边距。

3. 使用 CSS Variables (自定义属性): 动态改变样式

CSS Variables 允许你在 CSS 中定义变量，并在 JavaScript 中修改它们。这可以让你动态地改变组件的样式。

<my-element theme="light"></my-element>


  :root {
    --light-bg-color: white;
    --dark-bg-color: black;
    --light-text-color: black;
    --dark-text-color: white;
  }

  my-element {
    --bg-color: var(--light-bg-color);
    --text-color: var(--light-text-color);
    background-color: var(--bg-color);
    color: var(--text-color);
    display: block;
    padding: 10px;
  }

  my-element[theme="dark"] {
    --bg-color: var(--dark-bg-color);
    --text-color: var(--dark-text-color);
  }



  class MyElement extends HTMLElement {
    static get observedAttributes() {
      return ['theme'];
    }

    attributeChangedCallback(name, oldValue, newValue) {
      if (name === 'theme') {
        // No need to update anything here, CSS handles it directly
      }
    }

    connectedCallback() {
      this.render();
    }

    render() {
      this.innerHTML = `
Hello, World!
      `;
    }
  }

  customElements.define('my-element', MyElement);


在这个例子中，我们定义了 --bg-color 和 --text-color 两个 CSS 变量，并根据 theme 属性的值来改变它们的值。

更灵活的 CSS Variables 用法

你还可以通过 JavaScript 直接修改 CSS 变量的值，实现更精细的控制。

class MyElement extends HTMLElement {
  connectedCallback() {
    this.shadowRoot.innerHTML = `

        :host {
          --main-color: blue;
          color: var(--main-color);
        }

Hello, World!
    `;
  }

  updateColor(newColor) {
    this.shadowRoot.host.style.setProperty('--main-color', newColor);
  }
}

然后，你可以通过调用 updateColor() 方法来动态改变颜色。

4. 利用 connectedCallback 和 disconnectedCallback 管理事件监听器

在 connectedCallback 中添加事件监听器，并在 disconnectedCallback 中移除它们，可以有效地避免内存泄漏。

class MyElement extends HTMLElement {
  connectedCallback() {
    this.addEventListener('click', this._handleClick);
  }

  disconnectedCallback() {
    this.removeEventListener('click', this._handleClick);
  }

  _handleClick() {
    console.log('Clicked!');
  }
}

最佳实践：Shadow DOM 和 CSS

使用 Shadow DOM: 尽量使用 Shadow DOM 来封装你的组件，避免样式冲突。

明确的 CSS 选择器: 避免使用过于宽泛的 CSS 选择器，尽量使用 :host 和属性选择器。

CSS Variables: 使用 CSS Variables 来实现样式的动态改变。

事件监听器管理: 在 disconnectedCallback 中移除事件监听器。

第三部分：进阶技巧：让你的组件更上一层楼

1. 使用 Template 和 Slot：构建可重用的组件

HTML Template 允许你定义可重用的 HTML 片段，而 Slot 允许你向组件中插入内容。



    .container {
      border: 1px solid gray;
      padding: 10px;
    }


<slot name="title">Default Title</slot>
<slot>Default Content</slot>



<my-element>
My Custom Title
My Custom Content
</my-element>


  class MyElement extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: 'open' });
      const template = document.getElementById('my-template').content.cloneNode(true);
      this.shadowRoot.appendChild(template);
    }
  }

  customElements.define('my-element', MyElement);


在这个例子中，我们使用了 Template 和 Slot 来创建一个可重用的组件，可以自定义标题和内容。

2. 使用 Custom Events：组件之间的通信

Custom Events 允许你触发自定义事件，让组件之间进行通信。

class MyButton extends HTMLElement {
  connectedCallback() {
    this.addEventListener('click', () => {
      const event = new CustomEvent('my-button-click', {
        detail: {
          message: 'Button clicked!'
        }
      });
      this.dispatchEvent(event);
    });
  }
}

customElements.define('my-button', MyButton);

// 在父组件中监听事件
document.addEventListener('my-button-click', (event) => {
  console.log(event.detail.message);
});

在这个例子中，我们创建了一个 MyButton 组件，当按钮被点击时，会触发一个 my-button-click 事件，父组件可以监听这个事件并获取事件的详细信息。

第四部分：实战案例：构建一个可配置的主题切换组件

让我们用一个实际的例子来巩固一下所学知识。我们将创建一个名为 theme-switcher 的组件，允许用户切换不同的主题。

class ThemeSwitcher extends HTMLElement {
  static get observedAttributes() {
    return ['theme'];
  }

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

  connectedCallback() {
    this.render();
  }

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

  render() {
    const theme = this.getAttribute('theme') || 'light';
    this.shadowRoot.innerHTML = `

        :host {
          display: block;
          padding: 10px;
          border: 1px solid gray;
        }

        :host([theme="light"]) {
          background-color: white;
          color: black;
        }

        :host([theme="dark"]) {
          background-color: black;
          color: white;
        }

        button {
          padding: 5px 10px;
          cursor: pointer;
        }

Current theme: ${theme}
Toggle Theme
    `;

    this.shadowRoot.getElementById('toggleButton').addEventListener('click', () => {
      const currentTheme = this.getAttribute('theme') || 'light';
      const newTheme = currentTheme === 'light' ? 'dark' : 'light';
      this.setAttribute('theme', newTheme);
    });
  }
}

customElements.define('theme-switcher', ThemeSwitcher);

代码解释：

observedAttributes: 监听 theme 属性的变化。

attributeChangedCallback: 当 theme 属性改变时，重新渲染组件。

render: 根据 theme 属性的值设置不同的样式，并添加一个切换主题的按钮。

CSS 选择器: 使用 :host 和属性选择器来设置组件的样式。

事件监听器: 在 render 方法中添加事件监听器，切换主题。

使用方法：

<theme-switcher theme="light"></theme-switcher>

你可以通过修改 theme 属性的值来切换不同的主题。

总结：

今天我们深入探讨了 Custom Elements 的生命周期回调函数，以及它们如何与 CSS 结合，构建出更加灵活和可控的 Web Components。希望各位观众老爷能够学以致用，打造出属于自己的组件库！

表格总结

生命周期回调函数 | 触发时机 | 主要用途
constructor() | 组件实例被创建时 | 初始化组件状态，设置默认值，绑定事件监听器 (避免操作 DOM)
connectedCallback() | 组件被添加到 DOM 树时 | 进行 DOM 操作，创建子元素，设置属性，添加事件监听器
disconnectedCallback() | 组件从 DOM 树中移除时 | 清理工作，移除事件监听器，释放资源，防止内存泄漏
attributeChangedCallback() | 组件的属性发生变化时 (在 observedAttributes 中声明的属性) | 根据属性变化更新组件内部状态或外观

额外提示：

性能优化: 尽量减少 DOM 操作，避免频繁的重新渲染。

可访问性: 确保你的组件具有良好的可访问性，方便残障人士使用。

测试: 为你的组件编写单元测试，确保其功能正常。

好了，今天的分享就到这里，希望对大家有所帮助。 咱们下次再见！