前端组件化
理解组件化思想及其在前端开发中的应用
问题
什么是前端组件化?为什么需要组件化?如何实现组件化?
解答
什么是组件化
组件化是将页面拆分成多个独立、可复用的组件,每个组件包含自己的结构(HTML)、样式(CSS)和逻辑(JS)。
组件化的好处
- 复用性 - 同一组件可在多处使用
- 可维护性 - 修改只影响单个组件
- 可测试性 - 组件可独立测试
- 协作效率 - 团队成员可并行开发不同组件
组件化实现示例
原生 Web Components
// 定义一个自定义按钮组件
class MyButton extends HTMLElement {
constructor() {
super();
// 创建 Shadow DOM,实现样式隔离
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
// 组件挂载时执行
this.render();
this.bindEvents();
}
render() {
const text = this.getAttribute('text') || '按钮';
const type = this.getAttribute('type') || 'default';
this.shadowRoot.innerHTML = `
<style>
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-default { background: #e0e0e0; }
.btn-primary { background: #1890ff; color: white; }
</style>
<button class="btn btn-${type}">${text}</button>
`;
}
bindEvents() {
this.shadowRoot.querySelector('button').addEventListener('click', () => {
// 触发自定义事件
this.dispatchEvent(new CustomEvent('btn-click', {
bubbles: true,
detail: { text: this.getAttribute('text') }
}));
});
}
// 监听属性变化
static get observedAttributes() {
return ['text', 'type'];
}
attributeChangedCallback() {
if (this.shadowRoot.innerHTML) {
this.render();
}
}
}
// 注册组件
customElements.define('my-button', MyButton);
<!-- 使用组件 -->
<my-button text="提交" type="primary"></my-button>
<my-button text="取消"></my-button>
React 组件示例
// Button.jsx - 函数组件
import { useState } from 'react';
import './Button.css';
function Button({ text = '按钮', type = 'default', onClick }) {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
if (loading) return;
setLoading(true);
await onClick?.();
setLoading(false);
};
return (
<button
className={`btn btn-${type}`}
onClick={handleClick}
disabled={loading}
>
{loading ? '加载中...' : text}
</button>
);
}
export default Button;
Vue 组件示例
<!-- Button.vue - 单文件组件 -->
<template>
<button
:class="['btn', `btn-${type}`]"
:disabled="loading"
@click="handleClick"
>
{{ loading ? '加载中...' : text }}
</button>
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue';
const props = defineProps({
text: { type: String, default: '按钮' },
type: { type: String, default: 'default' }
});
const emit = defineEmits(['click']);
const loading = ref(false);
const handleClick = async () => {
if (loading.value) return;
loading.value = true;
emit('click');
loading.value = false;
};
</script>
<style scoped>
.btn { padding: 8px 16px; border-radius: 4px; }
.btn-primary { background: #1890ff; color: white; }
</style>
组件设计原则
// 1. 单一职责 - 一个组件只做一件事
// ❌ 不好:一个组件又展示列表又处理表单
// ✅ 好:拆分成 List 组件和 Form 组件
// 2. 高内聚低耦合 - 组件内部紧密,组件之间松散
// 通过 props 传入数据,通过事件传出数据
function List({ items, onSelect }) {
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => onSelect(item)}>
{item.name}
</li>
))}
</ul>
);
}
// 3. 可配置性 - 通过 props 控制组件行为
function Modal({
visible, // 是否显示
title, // 标题
width = 500, // 宽度,有默认值
onClose, // 关闭回调
children // 内容插槽
}) {
if (!visible) return null;
return (
<div className="modal" style={{ width }}>
<header>{title}</header>
<main>{children}</main>
<button onClick={onClose}>关闭</button>
</div>
);
}
关键点
- 组件是独立的 UI 单元,包含结构、样式和逻辑
- 组件通过 props 接收数据,通过事件向外通信
- 遵循单一职责原则,一个组件只做一件事
- 样式隔离可通过 Shadow DOM、CSS Modules、Scoped CSS 实现
- 组件应该是可复用、可测试、可组合的
目录