5.1 组件架构设计
5.1.1 组件设计原则
graph TD
A[组件设计原则] --> B[单一职责]
A --> C[可复用性]
A --> D[可组合性]
A --> E[可测试性]
A --> F[可维护性]
B --> G[功能聚焦]
B --> H[职责明确]
C --> I[通用接口]
C --> J[配置灵活]
D --> K[组件嵌套]
D --> L[插槽机制]
E --> M[单元测试]
E --> N[集成测试]
F --> O[代码清晰]
F --> P[文档完善]
5.1.2 基础组件类
// 基础组件类
class Component {
constructor(element, options = {}) {
this.element = element;
this.options = this.mergeOptions(this.getDefaultOptions(), options);
this.state = {};
this.children = new Map();
this.eventListeners = new Map();
this.isDestroyed = false;
this.init();
}
// 获取默认选项
getDefaultOptions() {
return {
className: '',
autoRender: true,
autoDestroy: true
};
}
// 合并选项
mergeOptions(defaults, options) {
return Object.assign({}, defaults, options);
}
// 初始化组件
init() {
this.beforeCreate();
this.create();
this.afterCreate();
if (this.options.autoRender) {
this.render();
}
this.setupEvents();
this.mounted();
}
// 生命周期钩子
beforeCreate() {
// 组件创建前
}
create() {
// 组件创建
this.setupState();
this.setupTemplate();
}
afterCreate() {
// 组件创建后
}
mounted() {
// 组件挂载完成
}
beforeUpdate() {
// 更新前
}
updated() {
// 更新后
}
beforeDestroy() {
// 销毁前
}
destroyed() {
// 销毁后
}
// 设置状态
setupState() {
this.state = this.getInitialState();
}
getInitialState() {
return {};
}
// 设置模板
setupTemplate() {
const template = this.getTemplate();
if (template) {
this.element.innerHTML = template;
}
}
getTemplate() {
return '';
}
// 渲染组件
render() {
this.beforeUpdate();
this.updateDOM();
this.updated();
}
updateDOM() {
// 子类实现具体的 DOM 更新逻辑
}
// 设置事件
setupEvents() {
const events = this.getEvents();
Object.entries(events).forEach(([selector, handlers]) => {
Object.entries(handlers).forEach(([event, handler]) => {
this.on(selector, event, handler);
});
});
}
getEvents() {
return {};
}
// 事件绑定
on(selector, event, handler) {
const elements = selector === 'this' ? [this.element] :
this.element.querySelectorAll(selector);
elements.forEach(el => {
const boundHandler = handler.bind(this);
el.addEventListener(event, boundHandler);
// 存储事件监听器以便后续清理
const key = `${selector}-${event}`;
if (!this.eventListeners.has(key)) {
this.eventListeners.set(key, []);
}
this.eventListeners.get(key).push({ element: el, handler: boundHandler });
});
}
// 触发事件
emit(eventName, detail = {}) {
const event = new CustomEvent(eventName, {
detail,
bubbles: true,
cancelable: true
});
this.element.dispatchEvent(event);
}
// 状态管理
setState(newState, shouldRender = true) {
const oldState = { ...this.state };
this.state = { ...this.state, ...newState };
this.emit('state-changed', {
oldState,
newState: this.state,
changedKeys: Object.keys(newState)
});
if (shouldRender) {
this.render();
}
}
getState(key) {
return key ? this.state[key] : this.state;
}
// 子组件管理
addChild(name, component) {
this.children.set(name, component);
return component;
}
getChild(name) {
return this.children.get(name);
}
removeChild(name) {
const child = this.children.get(name);
if (child && typeof child.destroy === 'function') {
child.destroy();
}
this.children.delete(name);
}
// 查找元素
$(selector) {
return this.element.querySelector(selector);
}
$$(selector) {
return this.element.querySelectorAll(selector);
}
// 销毁组件
destroy() {
if (this.isDestroyed) return;
this.beforeDestroy();
// 清理事件监听器
this.eventListeners.forEach(listeners => {
listeners.forEach(({ element, handler }) => {
element.removeEventListener(event, handler);
});
});
this.eventListeners.clear();
// 销毁子组件
this.children.forEach(child => {
if (typeof child.destroy === 'function') {
child.destroy();
}
});
this.children.clear();
// 清理 DOM
if (this.options.autoDestroy && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
this.isDestroyed = true;
this.destroyed();
}
}
// 使用示例:创建一个按钮组件
class Button extends Component {
getDefaultOptions() {
return {
...super.getDefaultOptions(),
text: 'Button',
variant: 'primary',
size: 'medium',
disabled: false,
loading: false
};
}
getInitialState() {
return {
clicked: false,
loading: this.options.loading
};
}
getTemplate() {
return `
<button class="btn btn-${this.options.variant} btn-${this.options.size}"
${this.options.disabled ? 'disabled' : ''}>
<span class="btn-content">${this.options.text}</span>
<span class="btn-loading" style="display: none;">⏳</span>
</button>
`;
}
getEvents() {
return {
'button': {
'click': this.handleClick
}
};
}
handleClick(event) {
if (this.state.loading || this.options.disabled) {
event.preventDefault();
return;
}
this.setState({ clicked: true });
this.emit('button-click', {
originalEvent: event,
button: this
});
// 重置点击状态
setTimeout(() => {
this.setState({ clicked: false });
}, 150);
}
setLoading(loading) {
this.setState({ loading });
this.updateLoadingState();
}
updateLoadingState() {
const button = this.$('button');
const content = this.$('.btn-content');
const loadingIcon = this.$('.btn-loading');
if (this.state.loading) {
button.disabled = true;
content.style.display = 'none';
loadingIcon.style.display = 'inline';
} else {
button.disabled = this.options.disabled;
content.style.display = 'inline';
loadingIcon.style.display = 'none';
}
}
setText(text) {
this.options.text = text;
this.$('.btn-content').textContent = text;
}
setDisabled(disabled) {
this.options.disabled = disabled;
this.$('button').disabled = disabled || this.state.loading;
}
}
5.1.3 组件工厂
class ComponentFactory {
constructor() {
this.components = new Map();
this.instances = new WeakMap();
}
// 注册组件
register(name, componentClass, options = {}) {
this.components.set(name, {
class: componentClass,
options: options
});
}
// 创建组件实例
create(name, element, options = {}) {
const componentInfo = this.components.get(name);
if (!componentInfo) {
throw new Error(`组件 "${name}" 未注册`);
}
const mergedOptions = { ...componentInfo.options, ...options };
const instance = new componentInfo.class(element, mergedOptions);
// 存储实例引用
this.instances.set(element, instance);
return instance;
}
// 获取元素关联的组件实例
getInstance(element) {
return this.instances.get(element);
}
// 自动初始化页面中的组件
autoInit(container = document) {
this.components.forEach((componentInfo, name) => {
const elements = container.querySelectorAll(`[data-component="${name}"]`);
elements.forEach(element => {
if (!this.instances.has(element)) {
const options = this.parseDataAttributes(element);
this.create(name, element, options);
}
});
});
}
// 解析 data 属性
parseDataAttributes(element) {
const options = {};
Array.from(element.attributes).forEach(attr => {
if (attr.name.startsWith('data-') && attr.name !== 'data-component') {
const key = attr.name.substring(5).replace(/-([a-z])/g, (g) => g[1].toUpperCase());
let value = attr.value;
// 尝试解析 JSON
try {
value = JSON.parse(value);
} catch (e) {
// 保持原始字符串值
}
options[key] = value;
}
});
return options;
}
// 销毁所有组件实例
destroyAll(container = document) {
const elements = container.querySelectorAll('[data-component]');
elements.forEach(element => {
const instance = this.instances.get(element);
if (instance && typeof instance.destroy === 'function') {
instance.destroy();
}
});
}
}
// 全局组件工厂实例
const componentFactory = new ComponentFactory();
// 注册组件
componentFactory.register('button', Button);
componentFactory.register('modal', Modal);
componentFactory.register('dropdown', Dropdown);
// 自动初始化
document.ready = function() {
componentFactory.autoInit();
};
5.2 生命周期管理
5.2.1 生命周期钩子系统
class LifecycleManager {
constructor() {
this.hooks = new Map();
this.globalHooks = new Map();
}
// 注册生命周期钩子
registerHook(component, hookName, callback) {
if (!this.hooks.has(component)) {
this.hooks.set(component, new Map());
}
const componentHooks = this.hooks.get(component);
if (!componentHooks.has(hookName)) {
componentHooks.set(hookName, []);
}
componentHooks.get(hookName).push(callback);
}
// 注册全局钩子
registerGlobalHook(hookName, callback) {
if (!this.globalHooks.has(hookName)) {
this.globalHooks.set(hookName, []);
}
this.globalHooks.get(hookName).push(callback);
}
// 执行钩子
async executeHook(component, hookName, ...args) {
const results = [];
// 执行全局钩子
const globalCallbacks = this.globalHooks.get(hookName) || [];
for (const callback of globalCallbacks) {
try {
const result = await callback(component, ...args);
results.push(result);
} catch (error) {
console.error(`全局钩子 ${hookName} 执行错误:`, error);
}
}
// 执行组件特定钩子
const componentHooks = this.hooks.get(component);
if (componentHooks) {
const callbacks = componentHooks.get(hookName) || [];
for (const callback of callbacks) {
try {
const result = await callback(...args);
results.push(result);
} catch (error) {
console.error(`组件钩子 ${hookName} 执行错误:`, error);
}
}
}
return results;
}
// 清理组件钩子
clearComponentHooks(component) {
this.hooks.delete(component);
}
}
// 增强的组件基类
class EnhancedComponent extends Component {
constructor(element, options = {}) {
super(element, options);
this.lifecycleManager = new LifecycleManager();
this.setupLifecycleHooks();
}
setupLifecycleHooks() {
// 可以在这里设置默认的生命周期钩子
}
// 重写生命周期方法以支持钩子
async beforeCreate() {
await this.lifecycleManager.executeHook(this, 'beforeCreate');
super.beforeCreate();
}
async create() {
await this.lifecycleManager.executeHook(this, 'create');
super.create();
}
async afterCreate() {
await this.lifecycleManager.executeHook(this, 'afterCreate');
super.afterCreate();
}
async mounted() {
await this.lifecycleManager.executeHook(this, 'mounted');
super.mounted();
}
async beforeUpdate() {
await this.lifecycleManager.executeHook(this, 'beforeUpdate');
super.beforeUpdate();
}
async updated() {
await this.lifecycleManager.executeHook(this, 'updated');
super.updated();
}
async beforeDestroy() {
await this.lifecycleManager.executeHook(this, 'beforeDestroy');
super.beforeDestroy();
}
async destroyed() {
await this.lifecycleManager.executeHook(this, 'destroyed');
this.lifecycleManager.clearComponentHooks(this);
super.destroyed();
}
// 添加生命周期钩子的便捷方法
onBeforeCreate(callback) {
this.lifecycleManager.registerHook(this, 'beforeCreate', callback);
}
onCreate(callback) {
this.lifecycleManager.registerHook(this, 'create', callback);
}
onAfterCreate(callback) {
this.lifecycleManager.registerHook(this, 'afterCreate', callback);
}
onMounted(callback) {
this.lifecycleManager.registerHook(this, 'mounted', callback);
}
onBeforeUpdate(callback) {
this.lifecycleManager.registerHook(this, 'beforeUpdate', callback);
}
onUpdated(callback) {
this.lifecycleManager.registerHook(this, 'updated', callback);
}
onBeforeDestroy(callback) {
this.lifecycleManager.registerHook(this, 'beforeDestroy', callback);
}
onDestroyed(callback) {
this.lifecycleManager.registerHook(this, 'destroyed', callback);
}
}
5.2.2 组件状态管理
class StateManager {
constructor(initialState = {}) {
this.state = { ...initialState };
this.watchers = new Map();
this.computed = new Map();
this.computedCache = new Map();
this.mutations = new Map();
this.actions = new Map();
}
// 获取状态
getState(path) {
if (!path) return this.state;
return path.split('.').reduce((obj, key) => {
return obj && obj[key] !== undefined ? obj[key] : undefined;
}, this.state);
}
// 设置状态
setState(path, value) {
const keys = path.split('.');
const lastKey = keys.pop();
const target = keys.reduce((obj, key) => {
if (!obj[key]) obj[key] = {};
return obj[key];
}, this.state);
const oldValue = target[lastKey];
target[lastKey] = value;
// 触发观察者
this.notifyWatchers(path, value, oldValue);
// 清除相关的计算属性缓存
this.clearComputedCache(path);
}
// 批量更新状态
updateState(updates) {
Object.entries(updates).forEach(([path, value]) => {
this.setState(path, value);
});
}
// 监听状态变化
watch(path, callback, options = {}) {
if (!this.watchers.has(path)) {
this.watchers.set(path, []);
}
const watcher = {
callback,
immediate: options.immediate || false,
deep: options.deep || false
};
this.watchers.get(path).push(watcher);
// 立即执行
if (watcher.immediate) {
callback(this.getState(path), undefined);
}
// 返回取消监听的函数
return () => {
const watchers = this.watchers.get(path);
if (watchers) {
const index = watchers.indexOf(watcher);
if (index > -1) {
watchers.splice(index, 1);
}
}
};
}
// 通知观察者
notifyWatchers(path, newValue, oldValue) {
// 精确匹配
const exactWatchers = this.watchers.get(path) || [];
exactWatchers.forEach(watcher => {
watcher.callback(newValue, oldValue);
});
// 深度监听
this.watchers.forEach((watchers, watchPath) => {
if (watchPath !== path && (path.startsWith(watchPath + '.') || watchPath.startsWith(path + '.'))) {
watchers.forEach(watcher => {
if (watcher.deep) {
watcher.callback(this.getState(watchPath), undefined);
}
});
}
});
}
// 计算属性
computed(name, getter, dependencies = []) {
this.computed.set(name, { getter, dependencies });
// 监听依赖变化
dependencies.forEach(dep => {
this.watch(dep, () => {
this.computedCache.delete(name);
});
});
}
// 获取计算属性
getComputed(name) {
if (this.computedCache.has(name)) {
return this.computedCache.get(name);
}
const computedInfo = this.computed.get(name);
if (!computedInfo) {
throw new Error(`计算属性 "${name}" 不存在`);
}
const value = computedInfo.getter(this.state);
this.computedCache.set(name, value);
return value;
}
// 清除计算属性缓存
clearComputedCache(changedPath) {
this.computed.forEach((computedInfo, name) => {
const shouldClear = computedInfo.dependencies.some(dep =>
changedPath.startsWith(dep) || dep.startsWith(changedPath)
);
if (shouldClear) {
this.computedCache.delete(name);
}
});
}
// 注册 mutation
registerMutation(name, mutator) {
this.mutations.set(name, mutator);
}
// 提交 mutation
commit(name, payload) {
const mutator = this.mutations.get(name);
if (!mutator) {
throw new Error(`Mutation "${name}" 不存在`);
}
mutator(this.state, payload);
this.notifyWatchers('', this.state, {});
}
// 注册 action
registerAction(name, action) {
this.actions.set(name, action);
}
// 分发 action
async dispatch(name, payload) {
const action = this.actions.get(name);
if (!action) {
throw new Error(`Action "${name}" 不存在`);
}
const context = {
state: this.state,
getState: this.getState.bind(this),
setState: this.setState.bind(this),
commit: this.commit.bind(this),
dispatch: this.dispatch.bind(this)
};
return await action(context, payload);
}
}
// 带状态管理的组件
class StatefulComponent extends EnhancedComponent {
constructor(element, options = {}) {
super(element, options);
this.stateManager = new StateManager(this.getInitialState());
this.setupStateManagement();
}
setupStateManagement() {
// 设置计算属性
this.setupComputed();
// 设置 mutations
this.setupMutations();
// 设置 actions
this.setupActions();
// 设置状态监听
this.setupWatchers();
}
setupComputed() {
// 子类实现
}
setupMutations() {
// 子类实现
}
setupActions() {
// 子类实现
}
setupWatchers() {
// 子类实现
}
// 重写状态方法
setState(path, value) {
this.stateManager.setState(path, value);
this.render();
}
getState(path) {
return this.stateManager.getState(path);
}
watch(path, callback, options) {
return this.stateManager.watch(path, callback, options);
}
computed(name, getter, dependencies) {
this.stateManager.computed(name, getter, dependencies);
}
getComputed(name) {
return this.stateManager.getComputed(name);
}
commit(name, payload) {
this.stateManager.commit(name, payload);
this.render();
}
dispatch(name, payload) {
return this.stateManager.dispatch(name, payload);
}
}
5.3 组件通信机制
5.3.1 事件总线
class EventBus {
constructor() {
this.events = new Map();
this.onceEvents = new Set();
this.maxListeners = 10;
}
// 监听事件
on(eventName, callback, context = null) {
if (!this.events.has(eventName)) {
this.events.set(eventName, []);
}
const listeners = this.events.get(eventName);
// 检查监听器数量
if (listeners.length >= this.maxListeners) {
console.warn(`事件 "${eventName}" 的监听器数量超过限制 (${this.maxListeners})`);
}
const listener = {
callback,
context,
id: Symbol('listener')
};
listeners.push(listener);
// 返回取消监听的函数
return () => this.off(eventName, listener.id);
}
// 监听一次
once(eventName, callback, context = null) {
const listener = {
callback,
context,
id: Symbol('listener')
};
this.onceEvents.add(listener.id);
const unsubscribe = this.on(eventName, (...args) => {
callback.apply(context, args);
unsubscribe();
this.onceEvents.delete(listener.id);
}, context);
return unsubscribe;
}
// 取消监听
off(eventName, listenerId = null) {
if (!this.events.has(eventName)) return;
const listeners = this.events.get(eventName);
if (listenerId) {
// 移除特定监听器
const index = listeners.findIndex(l => l.id === listenerId);
if (index > -1) {
listeners.splice(index, 1);
}
} else {
// 移除所有监听器
listeners.length = 0;
}
// 如果没有监听器了,删除事件
if (listeners.length === 0) {
this.events.delete(eventName);
}
}
// 触发事件
emit(eventName, ...args) {
if (!this.events.has(eventName)) return false;
const listeners = [...this.events.get(eventName)];
let hasListeners = false;
listeners.forEach(listener => {
try {
hasListeners = true;
listener.callback.apply(listener.context, args);
} catch (error) {
console.error(`事件 "${eventName}" 监听器执行错误:`, error);
}
});
return hasListeners;
}
// 异步触发事件
async emitAsync(eventName, ...args) {
if (!this.events.has(eventName)) return false;
const listeners = [...this.events.get(eventName)];
let hasListeners = false;
for (const listener of listeners) {
try {
hasListeners = true;
await listener.callback.apply(listener.context, args);
} catch (error) {
console.error(`异步事件 "${eventName}" 监听器执行错误:`, error);
}
}
return hasListeners;
}
// 获取事件监听器数量
listenerCount(eventName) {
return this.events.has(eventName) ? this.events.get(eventName).length : 0;
}
// 获取所有事件名称
eventNames() {
return Array.from(this.events.keys());
}
// 设置最大监听器数量
setMaxListeners(n) {
this.maxListeners = n;
}
// 清除所有事件
clear() {
this.events.clear();
this.onceEvents.clear();
}
}
// 全局事件总线
const globalEventBus = new EventBus();
// 组件间通信混入
const CommunicationMixin = {
// 发送消息给父组件
$emit(eventName, data) {
this.emit(`component:${eventName}`, {
source: this,
data
});
},
// 发送消息给子组件
$broadcast(eventName, data) {
this.children.forEach(child => {
if (typeof child.$receive === 'function') {
child.$receive(eventName, data, this);
}
});
},
// 接收消息
$receive(eventName, data, sender) {
const handler = this[`on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`];
if (typeof handler === 'function') {
handler.call(this, data, sender);
}
},
// 全局消息
$publish(eventName, data) {
globalEventBus.emit(eventName, {
source: this,
data
});
},
$subscribe(eventName, callback) {
return globalEventBus.on(eventName, callback, this);
},
$unsubscribe(eventName, listenerId) {
globalEventBus.off(eventName, listenerId);
}
};
5.3.2 Props 和 Slots 系统
class PropsManager {
constructor(component, propDefinitions = {}) {
this.component = component;
this.definitions = propDefinitions;
this.props = {};
this.watchers = new Map();
}
// 定义 prop
define(name, definition) {
this.definitions[name] = this.normalizePropDefinition(definition);
}
// 标准化 prop 定义
normalizePropDefinition(definition) {
if (typeof definition === 'function') {
return { type: definition };
}
if (Array.isArray(definition)) {
return { type: definition };
}
return {
type: definition.type || String,
default: definition.default,
required: definition.required || false,
validator: definition.validator
};
}
// 设置 props
setProps(props) {
Object.entries(props).forEach(([name, value]) => {
this.setProp(name, value);
});
}
// 设置单个 prop
setProp(name, value) {
const definition = this.definitions[name];
if (!definition) {
console.warn(`未定义的 prop: ${name}`);
return;
}
// 类型检查
if (!this.validateType(value, definition.type)) {
console.error(`Prop "${name}" 类型错误`);
return;
}
// 自定义验证
if (definition.validator && !definition.validator(value)) {
console.error(`Prop "${name}" 验证失败`);
return;
}
const oldValue = this.props[name];
this.props[name] = value;
// 触发监听器
this.notifyWatchers(name, value, oldValue);
}
// 获取 prop
getProp(name) {
if (this.props.hasOwnProperty(name)) {
return this.props[name];
}
const definition = this.definitions[name];
if (definition && definition.default !== undefined) {
return typeof definition.default === 'function' ?
definition.default() : definition.default;
}
return undefined;
}
// 类型验证
validateType(value, type) {
if (Array.isArray(type)) {
return type.some(t => this.validateSingleType(value, t));
}
return this.validateSingleType(value, type);
}
validateSingleType(value, type) {
if (value === null || value === undefined) {
return true;
}
switch (type) {
case String:
return typeof value === 'string';
case Number:
return typeof value === 'number' && !isNaN(value);
case Boolean:
return typeof value === 'boolean';
case Array:
return Array.isArray(value);
case Object:
return typeof value === 'object' && !Array.isArray(value);
case Function:
return typeof value === 'function';
default:
return value instanceof type;
}
}
// 监听 prop 变化
watch(name, callback) {
if (!this.watchers.has(name)) {
this.watchers.set(name, []);
}
this.watchers.get(name).push(callback);
}
// 通知监听器
notifyWatchers(name, newValue, oldValue) {
const callbacks = this.watchers.get(name) || [];
callbacks.forEach(callback => {
try {
callback(newValue, oldValue);
} catch (error) {
console.error(`Prop "${name}" 监听器错误:`, error);
}
});
}
// 验证必需的 props
validateRequired() {
const missing = [];
Object.entries(this.definitions).forEach(([name, definition]) => {
if (definition.required && !this.props.hasOwnProperty(name)) {
missing.push(name);
}
});
if (missing.length > 0) {
console.error(`缺少必需的 props: ${missing.join(', ')}`);
}
return missing.length === 0;
}
}
// Slots 管理器
class SlotsManager {
constructor(component) {
this.component = component;
this.slots = new Map();
this.namedSlots = new Map();
}
// 注册插槽
registerSlot(name, element) {
this.namedSlots.set(name, element);
}
// 设置插槽内容
setSlotContent(name, content) {
const slot = this.namedSlots.get(name);
if (!slot) {
console.warn(`插槽 "${name}" 不存在`);
return;
}
if (typeof content === 'string') {
slot.innerHTML = content;
} else if (content instanceof Element) {
slot.innerHTML = '';
slot.appendChild(content);
} else if (Array.isArray(content)) {
slot.innerHTML = '';
content.forEach(item => {
if (typeof item === 'string') {
slot.insertAdjacentHTML('beforeend', item);
} else if (item instanceof Element) {
slot.appendChild(item);
}
});
}
}
// 获取插槽内容
getSlotContent(name) {
const slot = this.namedSlots.get(name);
return slot ? slot.innerHTML : '';
}
// 检查插槽是否有内容
hasSlotContent(name) {
const slot = this.namedSlots.get(name);
return slot && slot.children.length > 0;
}
// 清空插槽
clearSlot(name) {
const slot = this.namedSlots.get(name);
if (slot) {
slot.innerHTML = '';
}
}
// 自动发现插槽
discoverSlots() {
const slots = this.component.element.querySelectorAll('[data-slot]');
slots.forEach(slot => {
const name = slot.dataset.slot;
this.registerSlot(name, slot);
});
}
}
// 带 Props 和 Slots 的组件
class PropsComponent extends StatefulComponent {
constructor(element, options = {}) {
super(element, options);
this.propsManager = new PropsManager(this, this.getPropDefinitions());
this.slotsManager = new SlotsManager(this);
this.setupProps();
this.setupSlots();
}
getPropDefinitions() {
return {};
}
setupProps() {
// 从 data 属性解析 props
const props = this.parsePropsFromAttributes();
this.propsManager.setProps(props);
// 验证必需的 props
this.propsManager.validateRequired();
// 监听 props 变化
Object.keys(this.getPropDefinitions()).forEach(name => {
this.propsManager.watch(name, (newValue, oldValue) => {
this.onPropChanged(name, newValue, oldValue);
});
});
}
setupSlots() {
this.slotsManager.discoverSlots();
}
parsePropsFromAttributes() {
const props = {};
Array.from(this.element.attributes).forEach(attr => {
if (attr.name.startsWith('data-prop-')) {
const propName = attr.name.substring(10).replace(/-([a-z])/g, (g) => g[1].toUpperCase());
let value = attr.value;
try {
value = JSON.parse(value);
} catch (e) {
// 保持字符串值
}
props[propName] = value;
}
});
return props;
}
onPropChanged(name, newValue, oldValue) {
// 子类可以重写此方法来响应 prop 变化
this.render();
}
// 获取 prop 值
$prop(name) {
return this.propsManager.getProp(name);
}
// 设置插槽内容
$slot(name, content) {
this.slotsManager.setSlotContent(name, content);
}
// 检查插槽
$hasSlot(name) {
return this.slotsManager.hasSlotContent(name);
}
}
5.4 模块化开发模式
5.4.1 模块系统
// 模块管理器
class ModuleManager {
constructor() {
this.modules = new Map();
this.dependencies = new Map();
this.loadedModules = new Set();
this.loadingModules = new Set();
}
// 定义模块
define(name, dependencies, factory) {
if (typeof dependencies === 'function') {
factory = dependencies;
dependencies = [];
}
this.modules.set(name, {
dependencies,
factory,
exports: null
});
this.dependencies.set(name, dependencies);
// 尝试加载模块
this.tryLoadModule(name);
}
// 加载模块
async load(name) {
if (this.loadedModules.has(name)) {
return this.modules.get(name).exports;
}
if (this.loadingModules.has(name)) {
// 等待模块加载完成
return new Promise((resolve) => {
const checkLoaded = () => {
if (this.loadedModules.has(name)) {
resolve(this.modules.get(name).exports);
} else {
setTimeout(checkLoaded, 10);
}
};
checkLoaded();
});
}
return this.loadModule(name);
}
// 加载模块实现
async loadModule(name) {
const moduleInfo = this.modules.get(name);
if (!moduleInfo) {
throw new Error(`模块 "${name}" 未定义`);
}
this.loadingModules.add(name);
try {
// 加载依赖
const dependencies = await Promise.all(
moduleInfo.dependencies.map(dep => this.load(dep))
);
// 执行工厂函数
const exports = moduleInfo.factory(...dependencies);
moduleInfo.exports = exports;
this.loadedModules.add(name);
this.loadingModules.delete(name);
return exports;
} catch (error) {
this.loadingModules.delete(name);
throw error;
}
}
// 尝试加载模块
tryLoadModule(name) {
const dependencies = this.dependencies.get(name) || [];
const canLoad = dependencies.every(dep => this.loadedModules.has(dep));
if (canLoad && !this.loadedModules.has(name) && !this.loadingModules.has(name)) {
this.loadModule(name).catch(error => {
console.error(`模块 "${name}" 加载失败:`, error);
});
}
}
// 获取模块依赖图
getDependencyGraph() {
const graph = {};
this.dependencies.forEach((deps, name) => {
graph[name] = deps;
});
return graph;
}
// 检查循环依赖
checkCircularDependencies() {
const visited = new Set();
const visiting = new Set();
const cycles = [];
const visit = (name, path = []) => {
if (visiting.has(name)) {
const cycleStart = path.indexOf(name);
cycles.push(path.slice(cycleStart).concat(name));
return;
}
if (visited.has(name)) return;
visiting.add(name);
const deps = this.dependencies.get(name) || [];
deps.forEach(dep => {
visit(dep, path.concat(name));
});
visiting.delete(name);
visited.add(name);
};
this.dependencies.forEach((_, name) => {
if (!visited.has(name)) {
visit(name);
}
});
return cycles;
}
}
// 全局模块管理器
const moduleManager = new ModuleManager();
// 便捷函数
function define(name, dependencies, factory) {
moduleManager.define(name, dependencies, factory);
}
function require(name) {
return moduleManager.load(name);
}
// 使用示例
// 定义工具模块
define('utils', [], function() {
return {
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof Array) return obj.map(item => this.deepClone(item));
if (typeof obj === 'object') {
const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = this.deepClone(obj[key]);
}
}
return clonedObj;
}
}
};
});
// 定义 HTTP 模块
define('http', ['utils'], function(utils) {
class HttpClient {
constructor(baseURL = '') {
this.baseURL = baseURL;
this.interceptors = {
request: [],
response: []
};
}
async request(config) {
// 应用请求拦截器
for (const interceptor of this.interceptors.request) {
config = await interceptor(config);
}
const url = this.baseURL + config.url;
const options = {
method: config.method || 'GET',
headers: config.headers || {},
body: config.data ? JSON.stringify(config.data) : undefined
};
if (config.data && options.method !== 'GET') {
options.headers['Content-Type'] = 'application/json';
}
try {
const response = await fetch(url, options);
let result = {
data: await response.json(),
status: response.status,
statusText: response.statusText,
headers: response.headers
};
// 应用响应拦截器
for (const interceptor of this.interceptors.response) {
result = await interceptor(result);
}
return result;
} catch (error) {
throw new Error(`HTTP 请求失败: ${error.message}`);
}
}
get(url, config = {}) {
return this.request({ ...config, method: 'GET', url });
}
post(url, data, config = {}) {
return this.request({ ...config, method: 'POST', url, data });
}
put(url, data, config = {}) {
return this.request({ ...config, method: 'PUT', url, data });
}
delete(url, config = {}) {
return this.request({ ...config, method: 'DELETE', url });
}
}
return {
HttpClient,
create: (config) => new HttpClient(config.baseURL)
};
});
// 定义组件模块
define('components/modal', ['utils'], function(utils) {
class Modal extends PropsComponent {
getPropDefinitions() {
return {
title: { type: String, default: '' },
visible: { type: Boolean, default: false },
closable: { type: Boolean, default: true },
maskClosable: { type: Boolean, default: true },
width: { type: [String, Number], default: '520px' },
zIndex: { type: Number, default: 1000 }
};
}
getTemplate() {
return `
<div class="modal-mask" style="display: none;">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<div class="modal-title" data-slot="title"></div>
<button class="modal-close" v-if="closable">×</button>
</div>
<div class="modal-body" data-slot="default"></div>
<div class="modal-footer" data-slot="footer"></div>
</div>
</div>
</div>
`;
}
getEvents() {
return {
'.modal-close': {
'click': this.close
},
'.modal-mask': {
'click': this.handleMaskClick
}
};
}
onPropChanged(name, newValue, oldValue) {
if (name === 'visible') {
if (newValue) {
this.show();
} else {
this.hide();
}
}
}
show() {
const mask = this.$('.modal-mask');
mask.style.display = 'flex';
mask.style.zIndex = this.$prop('zIndex');
const container = this.$('.modal-container');
container.style.width = this.$prop('width');
// 设置标题
if (this.$prop('title')) {
this.$slot('title', this.$prop('title'));
}
this.$emit('show');
}
hide() {
const mask = this.$('.modal-mask');
mask.style.display = 'none';
this.$emit('hide');
}
close() {
if (this.$prop('closable')) {
this.hide();
this.$emit('close');
}
}
handleMaskClick(event) {
if (event.target === event.currentTarget && this.$prop('maskClosable')) {
this.close();
}
}
}
return Modal;
});
5.4.2 插件系统
class PluginManager {
constructor() {
this.plugins = new Map();
this.hooks = new Map();
this.installedPlugins = new Set();
}
// 注册插件
register(name, plugin) {
if (this.plugins.has(name)) {
console.warn(`插件 "${name}" 已存在`);
return;
}
this.plugins.set(name, plugin);
}
// 安装插件
install(name, options = {}) {
if (this.installedPlugins.has(name)) {
console.warn(`插件 "${name}" 已安装`);
return;
}
const plugin = this.plugins.get(name);
if (!plugin) {
throw new Error(`插件 "${name}" 不存在`);
}
// 检查依赖
if (plugin.dependencies) {
plugin.dependencies.forEach(dep => {
if (!this.installedPlugins.has(dep)) {
throw new Error(`插件 "${name}" 依赖 "${dep}",但 "${dep}" 未安装`);
}
});
}
// 安装插件
if (typeof plugin.install === 'function') {
plugin.install(this, options);
}
this.installedPlugins.add(name);
// 触发安装钩子
this.executeHook('plugin:installed', { name, plugin, options });
}
// 卸载插件
uninstall(name) {
if (!this.installedPlugins.has(name)) {
console.warn(`插件 "${name}" 未安装`);
return;
}
const plugin = this.plugins.get(name);
// 检查是否有其他插件依赖此插件
const dependents = [];
this.plugins.forEach((p, n) => {
if (p.dependencies && p.dependencies.includes(name) && this.installedPlugins.has(n)) {
dependents.push(n);
}
});
if (dependents.length > 0) {
throw new Error(`无法卸载插件 "${name}",以下插件依赖它: ${dependents.join(', ')}`);
}
// 卸载插件
if (typeof plugin.uninstall === 'function') {
plugin.uninstall(this);
}
this.installedPlugins.delete(name);
// 触发卸载钩子
this.executeHook('plugin:uninstalled', { name, plugin });
}
// 注册钩子
addHook(hookName, callback) {
if (!this.hooks.has(hookName)) {
this.hooks.set(hookName, []);
}
this.hooks.get(hookName).push(callback);
}
// 执行钩子
executeHook(hookName, data) {
const callbacks = this.hooks.get(hookName) || [];
callbacks.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`钩子 "${hookName}" 执行错误:`, error);
}
});
}
// 获取已安装的插件列表
getInstalledPlugins() {
return Array.from(this.installedPlugins);
}
// 获取插件信息
getPluginInfo(name) {
const plugin = this.plugins.get(name);
return plugin ? {
name,
version: plugin.version || '1.0.0',
description: plugin.description || '',
dependencies: plugin.dependencies || [],
installed: this.installedPlugins.has(name)
} : null;
}
}
// 全局插件管理器
const pluginManager = new PluginManager();
// 示例插件:表单验证
const FormValidationPlugin = {
name: 'form-validation',
version: '1.0.0',
description: '表单验证插件',
dependencies: ['utils'],
install(pluginManager, options = {}) {
// 扩展组件基类
const originalSetupEvents = Component.prototype.setupEvents;
Component.prototype.setupEvents = function() {
originalSetupEvents.call(this);
// 自动设置表单验证
if (this.element.tagName === 'FORM') {
this.setupFormValidation();
}
};
Component.prototype.setupFormValidation = function() {
this.validators = new Map();
this.validationRules = {};
// 添加验证方法
this.addValidator = function(field, rules) {
this.validationRules[field] = rules;
};
this.validate = function() {
const errors = {};
let isValid = true;
Object.entries(this.validationRules).forEach(([field, rules]) => {
const element = this.$(field);
if (!element) return;
const value = element.value;
const fieldErrors = [];
rules.forEach(rule => {
if (!rule.validator(value)) {
fieldErrors.push(rule.message);
isValid = false;
}
});
if (fieldErrors.length > 0) {
errors[field] = fieldErrors;
}
});
return { isValid, errors };
};
};
console.log('表单验证插件已安装');
},
uninstall(pluginManager) {
// 清理扩展
delete Component.prototype.setupFormValidation;
delete Component.prototype.addValidator;
delete Component.prototype.validate;
console.log('表单验证插件已卸载');
}
};
// 注册和安装插件
pluginsManager.register('form-validation', FormValidationPlugin);
pluginsManager.install('form-validation');
5.5 实践练习
5.5.1 创建一个完整的 Todo 应用组件
// Todo 项组件
class TodoItem extends PropsComponent {
getPropDefinitions() {
return {
todo: {
type: Object,
required: true,
validator: (value) => {
return value && typeof value.id !== 'undefined' &&
typeof value.text === 'string' &&
typeof value.completed === 'boolean';
}
},
editable: { type: Boolean, default: true },
deletable: { type: Boolean, default: true }
};
}
getInitialState() {
return {
editing: false,
editText: ''
};
}
getTemplate() {
const todo = this.$prop('todo');
return `
<div class="todo-item ${todo.completed ? 'completed' : ''}">
<div class="todo-content">
<input type="checkbox" class="todo-checkbox"
${todo.completed ? 'checked' : ''}>
<span class="todo-text" style="display: ${this.state.editing ? 'none' : 'block'}">
${todo.text}
</span>
<input type="text" class="todo-edit"
style="display: ${this.state.editing ? 'block' : 'none'}"
value="${todo.text}">
</div>
<div class="todo-actions">
${this.$prop('editable') ? '<button class="btn-edit">编辑</button>' : ''}
${this.$prop('deletable') ? '<button class="btn-delete">删除</button>' : ''}
</div>
</div>
`;
}
getEvents() {
return {
'.todo-checkbox': {
'change': this.handleToggle
},
'.btn-edit': {
'click': this.handleEdit
},
'.btn-delete': {
'click': this.handleDelete
},
'.todo-edit': {
'blur': this.handleSave,
'keydown': this.handleKeyDown
},
'.todo-text': {
'dblclick': this.handleEdit
}
};
}
handleToggle(event) {
const completed = event.target.checked;
this.$emit('toggle', {
id: this.$prop('todo').id,
completed
});
}
handleEdit() {
if (!this.$prop('editable')) return;
this.setState({
editing: true,
editText: this.$prop('todo').text
});
// 聚焦到编辑框
setTimeout(() => {
const editInput = this.$('.todo-edit');
editInput.focus();
editInput.select();
}, 0);
}
handleSave() {
const editText = this.$('.todo-edit').value.trim();
if (editText && editText !== this.$prop('todo').text) {
this.$emit('update', {
id: this.$prop('todo').id,
text: editText
});
}
this.setState({ editing: false });
}
handleKeyDown(event) {
if (event.key === 'Enter') {
this.handleSave();
} else if (event.key === 'Escape') {
this.setState({ editing: false });
}
}
handleDelete() {
if (!this.$prop('deletable')) return;
this.$emit('delete', {
id: this.$prop('todo').id
});
}
}
// Todo 列表组件
class TodoList extends StatefulComponent {
getInitialState() {
return {
todos: [],
filter: 'all', // all, active, completed
newTodoText: ''
};
}
setupMutations() {
this.stateManager.registerMutation('ADD_TODO', (state, todo) => {
state.todos.push({
id: Date.now(),
text: todo.text,
completed: false,
createdAt: new Date()
});
});
this.stateManager.registerMutation('TOGGLE_TODO', (state, { id, completed }) => {
const todo = state.todos.find(t => t.id === id);
if (todo) {
todo.completed = completed;
}
});
this.stateManager.registerMutation('UPDATE_TODO', (state, { id, text }) => {
const todo = state.todos.find(t => t.id === id);
if (todo) {
todo.text = text;
}
});
this.stateManager.registerMutation('DELETE_TODO', (state, { id }) => {
const index = state.todos.findIndex(t => t.id === id);
if (index > -1) {
state.todos.splice(index, 1);
}
});
this.stateManager.registerMutation('SET_FILTER', (state, filter) => {
state.filter = filter;
});
this.stateManager.registerMutation('CLEAR_COMPLETED', (state) => {
state.todos = state.todos.filter(todo => !todo.completed);
});
}
setupComputed() {
this.computed('filteredTodos', (state) => {
switch (state.filter) {
case 'active':
return state.todos.filter(todo => !todo.completed);
case 'completed':
return state.todos.filter(todo => todo.completed);
default:
return state.todos;
}
}, ['todos', 'filter']);
this.computed('todoStats', (state) => {
const total = state.todos.length;
const completed = state.todos.filter(todo => todo.completed).length;
const active = total - completed;
return { total, completed, active };
}, ['todos']);
}
getTemplate() {
return `
<div class="todo-app">
<header class="todo-header">
<h1>Todo List</h1>
<div class="todo-input-container">
<input type="text" class="todo-input"
placeholder="添加新任务..."
value="${this.state.newTodoText}">
<button class="btn-add">添加</button>
</div>
</header>
<div class="todo-filters">
<button class="filter-btn ${this.state.filter === 'all' ? 'active' : ''}"
data-filter="all">全部</button>
<button class="filter-btn ${this.state.filter === 'active' ? 'active' : ''}"
data-filter="active">未完成</button>
<button class="filter-btn ${this.state.filter === 'completed' ? 'active' : ''}"
data-filter="completed">已完成</button>
</div>
<div class="todo-list-container">
<!-- Todo 项将在这里动态渲染 -->
</div>
<footer class="todo-footer">
<div class="todo-stats">
<span>总计: ${this.getComputed('todoStats').total}</span>
<span>未完成: ${this.getComputed('todoStats').active}</span>
<span>已完成: ${this.getComputed('todoStats').completed}</span>
</div>
<button class="btn-clear-completed">清除已完成</button>
</footer>
</div>
`;
}
getEvents() {
return {
'.todo-input': {
'input': this.handleInputChange,
'keydown': this.handleInputKeyDown
},
'.btn-add': {
'click': this.handleAddTodo
},
'.filter-btn': {
'click': this.handleFilterChange
},
'.btn-clear-completed': {
'click': this.handleClearCompleted
}
};
}
updateDOM() {
super.updateDOM();
this.renderTodoItems();
}
renderTodoItems() {
const container = this.$('.todo-list-container');
container.innerHTML = '';
const filteredTodos = this.getComputed('filteredTodos');
filteredTodos.forEach(todo => {
const itemElement = document.createElement('div');
container.appendChild(itemElement);
const todoItem = new TodoItem(itemElement, {
todo: todo
});
// 监听 Todo 项事件
todoItem.on('toggle', (event) => {
this.commit('TOGGLE_TODO', event.detail.data);
});
todoItem.on('update', (event) => {
this.commit('UPDATE_TODO', event.detail.data);
});
todoItem.on('delete', (event) => {
this.commit('DELETE_TODO', event.detail.data);
});
this.addChild(`todo-${todo.id}`, todoItem);
});
}
handleInputChange(event) {
this.setState('newTodoText', event.target.value);
}
handleInputKeyDown(event) {
if (event.key === 'Enter') {
this.handleAddTodo();
}
}
handleAddTodo() {
const text = this.state.newTodoText.trim();
if (text) {
this.commit('ADD_TODO', { text });
this.setState('newTodoText', '');
this.$('.todo-input').value = '';
}
}
handleFilterChange(event) {
const filter = event.target.dataset.filter;
this.commit('SET_FILTER', filter);
}
handleClearCompleted() {
this.commit('CLEAR_COMPLETED');
}
}
// 注册组件
componentFactory.register('todo-item', TodoItem);
componentFactory.register('todo-list', TodoList);
5.6 本章小结
5.6.1 核心概念
- 组件架构:基于类的组件系统,支持生命周期和状态管理
- 模块化:AMD 风格的模块定义和依赖管理
- 组件通信:Props、Events、Slots 和全局事件总线
- 插件系统:可扩展的插件架构
5.6.2 技术要点
- 生命周期管理:完整的组件生命周期钩子
- 状态管理:响应式状态系统和计算属性
- 组件通信:多种通信方式的组合使用
- 模块化开发:依赖注入和模块加载
5.6.3 最佳实践
- 遵循单一职责原则设计组件
- 使用 Props 进行父子组件通信
- 合理使用事件总线避免过度耦合
- 模块化组织代码提高可维护性
5.6.4 下一章预告
下一章将学习 数据绑定与状态管理,包括: - 双向数据绑定实现 - 全局状态管理 - 数据流管理模式 - 状态持久化
通过本章的学习,你已经掌握了完整的组件开发和模块化技能,能够构建复杂的应用程序架构。