使用 Web Components 构建框架无关的跨项目 UI 组件库,需结合 Custom Elements、Shadow DOM、HTML 模板等原生技术,并通过标准化封装与跨框架适配实现复用。 以下是具体步骤与关键要点:
1. 定义自定义元素(Custom Elements)- 通过 customElements.define() 注册组件,命名需包含短横线(如 my-button),避免与原生标签冲突。
- 示例:创建一个按钮组件,继承 HTMLElement 并实现生命周期回调(如 connectedCallback):class MyButton extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); // 启用 Shadow DOM } connectedCallback() { const label = this.getAttribute('label') || '按钮'; this.shadowRoot.innerHTML = ` <button>${label}</button> `; }}customElements.define('my-button', MyButton);
- 使用方式:直接在 HTML 中调用 <my-button label="提交"></my-button>。
2. 利用 Shadow DOM 实现样式隔离- 封装性:Shadow DOM 将组件样式与外部隔离,防止 CSS 污染。
- 样式定制:通过 CSS 变量(如 --btn-bg)暴露可配置项,外部通过宿主元素设置:connectedCallback() { this.shadowRoot.innerHTML = ` <style> button { background: var(--btn-bg, #007bff); /* 默认值 + 变量覆盖 */ color: white; } </style> <button><slot></slot></button> `;}
- 外部调用时可通过行内样式或 CSS 文件覆盖变量:<my-button style="--btn-bg: #ff0000;">红色按钮</my-button>
3. 支持插槽(Slot)实现内容分发- 使用 <slot> 允许用户插入自定义内容,提升组件灵活性。
- 命名插槽:通过 name 属性区分不同位置的内容(如前缀图标):this.shadowRoot.innerHTML = ` <button> <slot name="prefix"></slot> <!-- 前缀插槽 --> <slot></slot> <!-- 默认插槽 --> </button>`;
- 使用方式:<my-button> <span slot="prefix">▶</span> 开始播放</my-button>
4. 封装组件库并发布为 NPM 包- 组织结构:components/ button.js input.jsindex.js // 统一导出组件
- 批量注册:在 index.js 中导入并注册所有组件:import './button.js';import './input.js';export function defineComponents() { // 可选:添加全局配置(如组件名前缀)}
- 打包发布:
配置 package.json 的 main(CommonJS)和 module(ES Module)字段。
使用 Rollup 或 Vite 打包为浏览器可用的模块。
发布到 NPM 后,其他项目通过 npm install my-ui-library 安装,并直接使用标签(无需额外导入)。
5. 跨框架兼容性处理- React 适配:
事件处理:React 不自动监听自定义事件,需改用 on-click 属性或通过 ref 手动绑定:// 方法1:使用 on-click(需组件内部触发)<my-button on-click={() => console.log('clicked')}></my-button>// 方法2:通过 ref 绑定(推荐)const buttonRef = useRef();useEffect(() => { buttonRef.current.addEventListener('click', () => console.log('clicked'));}, []);<my-button ref={buttonRef}></my-button>
布尔属性:React 中布尔属性需转为字符串(如 disabled="true")或通过 JS 设置。
- 文档说明:提供各框架的使用示例(如 Vue 的 v-model 适配、Angular 的指令绑定)。
6. 优化与维护- 性能优化:避免在 connectedCallback 中频繁操作 DOM,使用 HTMLTemplate 预编译模板。
- 类型支持:为 TypeScript 项目提供类型声明文件(.d.ts)。
- 版本管理:遵循 SemVer 规范,确保兼容性更新不破坏现有项目。
示例组件库结构// components/button.jsclass MyButton extends HTMLElement { static get observedAttributes() { return ['disabled']; // 监听属性变化 } attributeChangedCallback(name, oldValue, newValue) { if (name === 'disabled') { this.shadowRoot.querySelector('button').disabled = newValue !== null; } } // ...其他逻辑}customElements.define('my-button', MyButton);// index.jsexport * from './button.js';export * from './input.js';通过以上步骤,可构建出真正框架无关的 UI 组件库,兼顾封装性、灵活性与跨项目复用能力。