7.1 组件化概述
什么是组件化
组件化是现代前端开发的核心思想,它将复杂的用户界面拆分成独立、可复用的组件。在Sciter中,组件化开发能够帮助我们:
组件化的优势
- 可复用性:一次编写,多处使用
- 可维护性:独立的组件便于维护和调试
- 可测试性:组件可以独立进行单元测试
- 团队协作:不同开发者可以并行开发不同组件
- 代码组织:清晰的代码结构和职责分离
Sciter组件特性
- HTML模板:使用HTML定义组件结构
- CSS样式:独立的样式作用域
- TIScript逻辑:组件的行为和交互逻辑
- 属性传递:父子组件间的数据传递
- 事件通信:组件间的事件通信机制
- 生命周期:组件的创建、更新、销毁过程
组件的基本结构
// 基础组件类
class Component {
function this(element, props = {}) {
this.element = element;
this.props = props;
this.state = {};
this.children = [];
this.eventListeners = [];
this.init();
}
// 初始化组件
function init() {
this.beforeMount();
this.render();
this.mounted();
this.bindEvents();
}
// 生命周期钩子
function beforeMount() {
// 组件挂载前
}
function mounted() {
// 组件挂载后
}
function beforeUpdate() {
// 组件更新前
}
function updated() {
// 组件更新后
}
function beforeDestroy() {
// 组件销毁前
}
function destroyed() {
// 组件销毁后
}
// 渲染方法
function render() {
// 子类实现
}
// 绑定事件
function bindEvents() {
// 子类实现
}
// 更新组件
function update(newProps = {}) {
this.beforeUpdate();
// 更新属性
Object.assign(this.props, newProps);
// 重新渲染
this.render();
this.updated();
}
// 设置状态
function setState(newState) {
Object.assign(this.state, newState);
this.update();
}
// 销毁组件
function destroy() {
this.beforeDestroy();
// 移除事件监听器
for (var listener of this.eventListeners) {
listener.element.off(listener.event, listener.handler);
}
// 销毁子组件
for (var child of this.children) {
if (child.destroy) {
child.destroy();
}
}
// 移除DOM元素
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
this.destroyed();
}
// 添加事件监听器
function addEventListener(element, event, handler) {
element.on(event, handler);
this.eventListeners.push({ element, event, handler });
}
// 查找子元素
function $(selector) {
return this.element.$(selector);
}
function $$(selector) {
return this.element.$$(selector);
}
// 触发自定义事件
function emit(eventName, data) {
var event = new Event(eventName, { bubbles: true, cancelable: true });
event.detail = data;
this.element.dispatchEvent(event);
}
}
7.2 基础组件开发
按钮组件
// 按钮组件
class Button extends Component {
function this(element, props = {}) {
// 默认属性
var defaultProps = {
type: 'default', // default, primary, success, warning, danger
size: 'medium', // small, medium, large
disabled: false,
loading: false,
icon: null,
text: '按钮'
};
super(element, Object.assign(defaultProps, props));
}
function render() {
var { type, size, disabled, loading, icon, text } = this.props;
// 设置CSS类
var classes = ['btn', `btn-${type}`, `btn-${size}`];
if (disabled) classes.push('btn-disabled');
if (loading) classes.push('btn-loading');
this.element.className = classes.join(' ');
// 设置内容
var content = '';
if (loading) {
content += '<span class="btn-spinner"></span>';
} else if (icon) {
content += `<i class="btn-icon ${icon}"></i>`;
}
content += `<span class="btn-text">${text}</span>`;
this.element.innerHTML = content;
// 设置属性
this.element.disabled = disabled || loading;
}
function bindEvents() {
var self = this;
this.addEventListener(this.element, 'click', function(evt) {
if (self.props.disabled || self.props.loading) {
evt.preventDefault();
evt.stopPropagation();
return;
}
// 触发点击事件
self.emit('click', {
button: self,
originalEvent: evt
});
});
// 鼠标悬停效果
this.addEventListener(this.element, 'mouseenter', function() {
if (!self.props.disabled && !self.props.loading) {
self.element.addClass('btn-hover');
}
});
this.addEventListener(this.element, 'mouseleave', function() {
self.element.removeClass('btn-hover');
});
// 按下效果
this.addEventListener(this.element, 'mousedown', function() {
if (!self.props.disabled && !self.props.loading) {
self.element.addClass('btn-active');
}
});
this.addEventListener(this.element, 'mouseup', function() {
self.element.removeClass('btn-active');
});
}
// 设置加载状态
function setLoading(loading) {
this.update({ loading: loading });
}
// 设置禁用状态
function setDisabled(disabled) {
this.update({ disabled: disabled });
}
// 设置文本
function setText(text) {
this.update({ text: text });
}
}
// 使用示例
function createButton() {
var buttonElement = Element.create('button');
document.body.append(buttonElement);
var button = new Button(buttonElement, {
type: 'primary',
text: '点击我',
icon: 'icon-star'
});
// 监听点击事件
buttonElement.on('click', function(evt) {
stdout.println('按钮被点击了!');
// 模拟异步操作
button.setLoading(true);
setTimeout(function() {
button.setLoading(false);
button.setText('操作完成');
}, 2000);
});
return button;
}
输入框组件
// 输入框组件
class Input extends Component {
function this(element, props = {}) {
var defaultProps = {
type: 'text',
placeholder: '',
value: '',
disabled: false,
readonly: false,
maxLength: null,
pattern: null,
required: false,
size: 'medium',
prefix: null,
suffix: null,
clearable: false,
showPassword: false
};
super(element, Object.assign(defaultProps, props));
}
function render() {
var { type, placeholder, value, disabled, readonly, maxLength, pattern, required, size, prefix, suffix, clearable, showPassword } = this.props;
// 创建输入框容器
var classes = ['input-wrapper', `input-${size}`];
if (disabled) classes.push('input-disabled');
if (readonly) classes.push('input-readonly');
this.element.className = classes.join(' ');
var html = '';
// 前缀
if (prefix) {
html += `<span class="input-prefix">${prefix}</span>`;
}
// 输入框
var inputType = (type === 'password' && this.state.showPassword) ? 'text' : type;
var inputAttrs = [
`type="${inputType}"`,
`placeholder="${placeholder}"`,
`value="${value}"`,
'class="input-field"'
];
if (disabled) inputAttrs.push('disabled');
if (readonly) inputAttrs.push('readonly');
if (required) inputAttrs.push('required');
if (maxLength) inputAttrs.push(`maxlength="${maxLength}"`);
if (pattern) inputAttrs.push(`pattern="${pattern}"`);
html += `<input ${inputAttrs.join(' ')} />`;
// 清除按钮
if (clearable && value && !disabled && !readonly) {
html += '<span class="input-clear" title="清除">×</span>';
}
// 密码显示切换
if (type === 'password' && showPassword) {
var eyeIcon = this.state.showPassword ? 'icon-eye-off' : 'icon-eye';
html += `<span class="input-password-toggle ${eyeIcon}" title="${this.state.showPassword ? '隐藏' : '显示'}密码"></span>`;
}
// 后缀
if (suffix) {
html += `<span class="input-suffix">${suffix}</span>`;
}
this.element.innerHTML = html;
// 缓存输入框元素
this.inputElement = this.$('.input-field');
}
function bindEvents() {
var self = this;
if (!this.inputElement) return;
// 输入事件
this.addEventListener(this.inputElement, 'input', function(evt) {
var value = this.value;
self.props.value = value;
self.emit('input', {
value: value,
input: self,
originalEvent: evt
});
// 更新清除按钮
self.updateClearButton();
});
// 变化事件
this.addEventListener(this.inputElement, 'change', function(evt) {
self.emit('change', {
value: this.value,
input: self,
originalEvent: evt
});
});
// 焦点事件
this.addEventListener(this.inputElement, 'focus', function(evt) {
self.element.addClass('input-focused');
self.emit('focus', {
input: self,
originalEvent: evt
});
});
this.addEventListener(this.inputElement, 'blur', function(evt) {
self.element.removeClass('input-focused');
self.emit('blur', {
input: self,
originalEvent: evt
});
});
// 键盘事件
this.addEventListener(this.inputElement, 'keydown', function(evt) {
self.emit('keydown', {
key: evt.key,
keyCode: evt.keyCode,
input: self,
originalEvent: evt
});
// Enter键
if (evt.keyCode === 13) {
self.emit('enter', {
value: this.value,
input: self,
originalEvent: evt
});
}
});
// 清除按钮
var clearButton = this.$('.input-clear');
if (clearButton) {
this.addEventListener(clearButton, 'click', function(evt) {
evt.preventDefault();
self.setValue('');
self.inputElement.focus();
});
}
// 密码显示切换
var passwordToggle = this.$('.input-password-toggle');
if (passwordToggle) {
this.addEventListener(passwordToggle, 'click', function(evt) {
evt.preventDefault();
self.togglePasswordVisibility();
});
}
}
function updateClearButton() {
if (this.props.clearable) {
this.render(); // 重新渲染以更新清除按钮
}
}
function togglePasswordVisibility() {
this.setState({
showPassword: !this.state.showPassword
});
}
// 获取值
function getValue() {
return this.inputElement ? this.inputElement.value : this.props.value;
}
// 设置值
function setValue(value) {
this.props.value = value;
if (this.inputElement) {
this.inputElement.value = value;
}
this.updateClearButton();
this.emit('change', {
value: value,
input: this
});
}
// 聚焦
function focus() {
if (this.inputElement) {
this.inputElement.focus();
}
}
// 失焦
function blur() {
if (this.inputElement) {
this.inputElement.blur();
}
}
// 选择文本
function select() {
if (this.inputElement) {
this.inputElement.select();
}
}
// 验证
function validate() {
if (!this.inputElement) return true;
var value = this.getValue();
var { required, pattern, maxLength } = this.props;
// 必填验证
if (required && (!value || value.trim() === '')) {
this.setError('此字段为必填项');
return false;
}
// 长度验证
if (maxLength && value.length > maxLength) {
this.setError(`最多允许${maxLength}个字符`);
return false;
}
// 模式验证
if (pattern && value && !new RegExp(pattern).test(value)) {
this.setError('格式不正确');
return false;
}
this.clearError();
return true;
}
// 设置错误
function setError(message) {
this.element.addClass('input-error');
var errorElement = this.$('.input-error-message');
if (!errorElement) {
errorElement = Element.create('div', {
class: 'input-error-message',
text: message
});
this.element.append(errorElement);
} else {
errorElement.innerText = message;
}
}
// 清除错误
function clearError() {
this.element.removeClass('input-error');
var errorElement = this.$('.input-error-message');
if (errorElement) {
errorElement.remove();
}
}
}
// 使用示例
function createInput() {
var inputElement = Element.create('div');
document.body.append(inputElement);
var input = new Input(inputElement, {
type: 'text',
placeholder: '请输入用户名',
clearable: true,
required: true
});
// 监听输入事件
inputElement.on('input', function(evt) {
stdout.println('输入值: ' + evt.detail.value);
});
// 监听回车事件
inputElement.on('enter', function(evt) {
stdout.println('按下回车,值: ' + evt.detail.value);
input.validate();
});
return input;
}
模态框组件
// 模态框组件
class Modal extends Component {
function this(element, props = {}) {
var defaultProps = {
title: '',
content: '',
width: '500px',
height: 'auto',
closable: true,
maskClosable: true,
showFooter: true,
okText: '确定',
cancelText: '取消',
visible: false,
destroyOnClose: false,
zIndex: 1000
};
super(element, Object.assign(defaultProps, props));
}
function render() {
var { title, content, width, height, closable, showFooter, okText, cancelText, visible, zIndex } = this.props;
// 设置模态框样式
this.element.className = 'modal-wrapper';
this.element.style.display = visible ? 'flex' : 'none';
this.element.style.zIndex = zIndex;
var html = `
<div class="modal-mask"></div>
<div class="modal-container" style="width: ${width}; height: ${height};">
<div class="modal-header">
<div class="modal-title">${title}</div>
${closable ? '<button class="modal-close">×</button>' : ''}
</div>
<div class="modal-body">
${content}
</div>
${showFooter ? `
<div class="modal-footer">
<button class="modal-cancel btn btn-default">${cancelText}</button>
<button class="modal-ok btn btn-primary">${okText}</button>
</div>
` : ''}
</div>
`;
this.element.innerHTML = html;
// 缓存元素
this.maskElement = this.$('.modal-mask');
this.containerElement = this.$('.modal-container');
this.bodyElement = this.$('.modal-body');
}
function bindEvents() {
var self = this;
// 关闭按钮
var closeButton = this.$('.modal-close');
if (closeButton) {
this.addEventListener(closeButton, 'click', function(evt) {
evt.preventDefault();
self.close();
});
}
// 确定按钮
var okButton = this.$('.modal-ok');
if (okButton) {
this.addEventListener(okButton, 'click', function(evt) {
evt.preventDefault();
var result = self.emit('ok', {
modal: self,
originalEvent: evt
});
// 如果事件没有被阻止,则关闭模态框
if (!evt.defaultPrevented) {
self.close();
}
});
}
// 取消按钮
var cancelButton = this.$('.modal-cancel');
if (cancelButton) {
this.addEventListener(cancelButton, 'click', function(evt) {
evt.preventDefault();
self.cancel();
});
}
// 遮罩点击
if (this.maskElement && this.props.maskClosable) {
this.addEventListener(this.maskElement, 'click', function(evt) {
if (evt.target === this) {
self.close();
}
});
}
// ESC键关闭
this.addEventListener(document, 'keydown', function(evt) {
if (evt.keyCode === 27 && self.props.visible && self.props.closable) {
self.close();
}
});
// 阻止容器点击事件冒泡
if (this.containerElement) {
this.addEventListener(this.containerElement, 'click', function(evt) {
evt.stopPropagation();
});
}
}
// 显示模态框
function show() {
this.update({ visible: true });
// 添加动画效果
setTimeout(function() {
this.element.addClass('modal-show');
}.bind(this), 10);
// 聚焦到模态框
if (this.containerElement) {
this.containerElement.focus();
}
this.emit('show', { modal: this });
}
// 隐藏模态框
function hide() {
this.element.removeClass('modal-show');
// 等待动画完成后隐藏
setTimeout(function() {
this.update({ visible: false });
this.emit('hide', { modal: this });
// 如果设置了销毁选项,则销毁组件
if (this.props.destroyOnClose) {
this.destroy();
}
}.bind(this), 300);
}
// 关闭模态框
function close() {
var result = this.emit('close', { modal: this });
// 如果事件没有被阻止,则隐藏模态框
if (!result || !result.defaultPrevented) {
this.hide();
}
}
// 取消
function cancel() {
this.emit('cancel', { modal: this });
this.close();
}
// 设置内容
function setContent(content) {
this.update({ content: content });
}
// 设置标题
function setTitle(title) {
this.update({ title: title });
}
// 获取内容元素
function getBodyElement() {
return this.bodyElement;
}
}
// 模态框管理器
namespace ModalManager {
var modals = [];
var zIndexBase = 1000;
function create(props) {
var modalElement = Element.create('div');
document.body.append(modalElement);
var modal = new Modal(modalElement, Object.assign({
zIndex: zIndexBase + modals.length
}, props));
modals.push(modal);
// 监听销毁事件
modalElement.on('destroyed', function() {
var index = modals.indexOf(modal);
if (index >= 0) {
modals.splice(index, 1);
}
});
return modal;
}
function alert(message, title = '提示') {
return new Promise(function(resolve) {
var modal = create({
title: title,
content: message,
showFooter: true,
cancelText: '',
destroyOnClose: true
});
modal.element.on('ok', function() {
resolve(true);
});
modal.element.on('close', function() {
resolve(false);
});
modal.show();
});
}
function confirm(message, title = '确认') {
return new Promise(function(resolve) {
var modal = create({
title: title,
content: message,
showFooter: true,
destroyOnClose: true
});
modal.element.on('ok', function() {
resolve(true);
});
modal.element.on('cancel', function() {
resolve(false);
});
modal.element.on('close', function() {
resolve(false);
});
modal.show();
});
}
function prompt(message, defaultValue = '', title = '输入') {
return new Promise(function(resolve) {
var inputId = 'prompt-input-' + Date.now();
var content = `
<div class="prompt-content">
<p>${message}</p>
<input type="text" id="${inputId}" value="${defaultValue}" class="prompt-input" />
</div>
`;
var modal = create({
title: title,
content: content,
showFooter: true,
destroyOnClose: true
});
modal.element.on('ok', function(evt) {
var input = modal.$('#' + inputId);
var value = input ? input.value : '';
resolve(value);
});
modal.element.on('cancel', function() {
resolve(null);
});
modal.element.on('close', function() {
resolve(null);
});
modal.show();
// 聚焦到输入框
setTimeout(function() {
var input = modal.$('#' + inputId);
if (input) {
input.focus();
input.select();
}
}, 100);
});
}
function closeAll() {
for (var modal of modals.slice()) {
modal.close();
}
}
function getTopModal() {
return modals.length > 0 ? modals[modals.length - 1] : null;
}
// 导出函数
self.create = create;
self.alert = alert;
self.confirm = confirm;
self.prompt = prompt;
self.closeAll = closeAll;
self.getTopModal = getTopModal;
}
// 使用示例
function testModal() {
// 创建自定义模态框
var modal = ModalManager.create({
title: '用户信息',
content: '<p>这是一个自定义模态框</p>',
width: '400px'
});
modal.show();
// 使用内置对话框
setTimeout(function() {
ModalManager.alert('这是一个警告消息!', '警告')
.then(function(result) {
stdout.println('Alert结果: ' + result);
return ModalManager.confirm('确定要删除吗?', '确认删除');
})
.then(function(result) {
stdout.println('Confirm结果: ' + result);
if (result) {
return ModalManager.prompt('请输入新名称:', '默认名称', '重命名');
}
return null;
})
.then(function(result) {
if (result !== null) {
stdout.println('Prompt结果: ' + result);
}
});
}, 2000);
}
7.3 组件通信
父子组件通信
// 父子组件通信示例
// 子组件:计数器
class Counter extends Component {
function this(element, props = {}) {
var defaultProps = {
value: 0,
min: null,
max: null,
step: 1,
disabled: false
};
super(element, Object.assign(defaultProps, props));
this.state = {
value: this.props.value
};
}
function render() {
var { min, max, step, disabled } = this.props;
var { value } = this.state;
var canDecrease = !disabled && (min === null || value > min);
var canIncrease = !disabled && (max === null || value < max);
this.element.className = 'counter' + (disabled ? ' counter-disabled' : '');
this.element.innerHTML = `
<button class="counter-decrease" ${!canDecrease ? 'disabled' : ''}>-</button>
<span class="counter-value">${value}</span>
<button class="counter-increase" ${!canIncrease ? 'disabled' : ''}>+</button>
`;
}
function bindEvents() {
var self = this;
// 减少按钮
var decreaseBtn = this.$('.counter-decrease');
if (decreaseBtn) {
this.addEventListener(decreaseBtn, 'click', function() {
self.decrease();
});
}
// 增加按钮
var increaseBtn = this.$('.counter-increase');
if (increaseBtn) {
this.addEventListener(increaseBtn, 'click', function() {
self.increase();
});
}
}
function decrease() {
var { min, step } = this.props;
var newValue = this.state.value - step;
if (min !== null && newValue < min) {
newValue = min;
}
this.setValue(newValue);
}
function increase() {
var { max, step } = this.props;
var newValue = this.state.value + step;
if (max !== null && newValue > max) {
newValue = max;
}
this.setValue(newValue);
}
function setValue(value) {
var oldValue = this.state.value;
this.setState({ value: value });
// 通知父组件值变化
this.emit('change', {
value: value,
oldValue: oldValue,
counter: this
});
}
function getValue() {
return this.state.value;
}
}
// 父组件:购物车项目
class CartItem extends Component {
function this(element, props = {}) {
var defaultProps = {
product: {
id: null,
name: '',
price: 0,
image: ''
},
quantity: 1,
editable: true
};
super(element, Object.assign(defaultProps, props));
this.state = {
quantity: this.props.quantity
};
}
function render() {
var { product, editable } = this.props;
var { quantity } = this.state;
var total = (product.price * quantity).toFixed(2);
this.element.className = 'cart-item';
this.element.innerHTML = `
<div class="cart-item-image">
<img src="${product.image}" alt="${product.name}" />
</div>
<div class="cart-item-info">
<h4 class="cart-item-name">${product.name}</h4>
<p class="cart-item-price">¥${product.price}</p>
</div>
<div class="cart-item-quantity">
<div class="quantity-counter"></div>
</div>
<div class="cart-item-total">
¥${total}
</div>
<div class="cart-item-actions">
<button class="cart-item-remove">删除</button>
</div>
`;
// 创建数量计数器子组件
this.createQuantityCounter();
}
function createQuantityCounter() {
var counterContainer = this.$('.quantity-counter');
if (counterContainer) {
// 销毁旧的计数器
if (this.quantityCounter) {
this.quantityCounter.destroy();
}
// 创建新的计数器
this.quantityCounter = new Counter(counterContainer, {
value: this.state.quantity,
min: 1,
max: 99,
disabled: !this.props.editable
});
// 监听计数器变化
var self = this;
counterContainer.on('change', function(evt) {
self.onQuantityChange(evt.detail.value);
});
// 添加到子组件列表
this.children.push(this.quantityCounter);
}
}
function bindEvents() {
var self = this;
// 删除按钮
var removeBtn = this.$('.cart-item-remove');
if (removeBtn) {
this.addEventListener(removeBtn, 'click', function() {
self.remove();
});
}
}
function onQuantityChange(newQuantity) {
this.setState({ quantity: newQuantity });
// 通知父组件
this.emit('quantity-change', {
productId: this.props.product.id,
quantity: newQuantity,
cartItem: this
});
}
function remove() {
this.emit('remove', {
productId: this.props.product.id,
cartItem: this
});
}
function getTotal() {
return this.props.product.price * this.state.quantity;
}
function setQuantity(quantity) {
this.setState({ quantity: quantity });
if (this.quantityCounter) {
this.quantityCounter.setValue(quantity);
}
}
}
// 祖父组件:购物车
class ShoppingCart extends Component {
function this(element, props = {}) {
var defaultProps = {
items: [],
editable: true
};
super(element, Object.assign(defaultProps, props));
this.state = {
items: this.props.items.slice()
};
}
function render() {
var { editable } = this.props;
var { items } = this.state;
var totalAmount = this.calculateTotal();
var totalItems = items.reduce((sum, item) => sum + item.quantity, 0);
this.element.className = 'shopping-cart';
this.element.innerHTML = `
<div class="cart-header">
<h2>购物车 (${totalItems}件商品)</h2>
</div>
<div class="cart-items"></div>
<div class="cart-footer">
<div class="cart-total">
<span class="total-label">总计:</span>
<span class="total-amount">¥${totalAmount.toFixed(2)}</span>
</div>
<div class="cart-actions">
<button class="cart-clear">清空购物车</button>
<button class="cart-checkout">结算</button>
</div>
</div>
`;
// 创建购物车项目子组件
this.createCartItems();
}
function createCartItems() {
var itemsContainer = this.$('.cart-items');
if (!itemsContainer) return;
// 销毁旧的子组件
for (var child of this.children) {
child.destroy();
}
this.children = [];
// 清空容器
itemsContainer.innerHTML = '';
// 创建新的子组件
for (var item of this.state.items) {
var itemElement = Element.create('div');
itemsContainer.append(itemElement);
var cartItem = new CartItem(itemElement, {
product: item.product,
quantity: item.quantity,
editable: this.props.editable
});
// 监听子组件事件
this.setupCartItemEvents(cartItem, item);
this.children.push(cartItem);
}
}
function setupCartItemEvents(cartItem, item) {
var self = this;
// 监听数量变化
cartItem.element.on('quantity-change', function(evt) {
self.updateItemQuantity(evt.detail.productId, evt.detail.quantity);
});
// 监听删除事件
cartItem.element.on('remove', function(evt) {
self.removeItem(evt.detail.productId);
});
}
function bindEvents() {
var self = this;
// 清空购物车
var clearBtn = this.$('.cart-clear');
if (clearBtn) {
this.addEventListener(clearBtn, 'click', function() {
self.clear();
});
}
// 结算
var checkoutBtn = this.$('.cart-checkout');
if (checkoutBtn) {
this.addEventListener(checkoutBtn, 'click', function() {
self.checkout();
});
}
}
function addItem(product, quantity = 1) {
var existingItem = this.state.items.find(item => item.product.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
this.state.items.push({
product: product,
quantity: quantity
});
}
this.update();
this.emit('item-added', {
product: product,
quantity: quantity,
cart: this
});
}
function removeItem(productId) {
var index = this.state.items.findIndex(item => item.product.id === productId);
if (index >= 0) {
var removedItem = this.state.items.splice(index, 1)[0];
this.update();
this.emit('item-removed', {
product: removedItem.product,
cart: this
});
}
}
function updateItemQuantity(productId, quantity) {
var item = this.state.items.find(item => item.product.id === productId);
if (item) {
item.quantity = quantity;
this.update();
this.emit('item-updated', {
product: item.product,
quantity: quantity,
cart: this
});
}
}
function clear() {
this.setState({ items: [] });
this.emit('cleared', {
cart: this
});
}
function checkout() {
var total = this.calculateTotal();
var items = this.state.items.slice();
this.emit('checkout', {
items: items,
total: total,
cart: this
});
}
function calculateTotal() {
return this.state.items.reduce((total, item) => {
return total + (item.product.price * item.quantity);
}, 0);
}
function getItemCount() {
return this.state.items.reduce((count, item) => count + item.quantity, 0);
}
function isEmpty() {
return this.state.items.length === 0;
}
}
// 使用示例
function createShoppingCart() {
var cartElement = Element.create('div');
document.body.append(cartElement);
var cart = new ShoppingCart(cartElement, {
items: [
{
product: {
id: 1,
name: 'iPhone 15',
price: 5999,
image: 'iphone15.jpg'
},
quantity: 1
},
{
product: {
id: 2,
name: 'MacBook Pro',
price: 12999,
image: 'macbook.jpg'
},
quantity: 2
}
]
});
// 监听购物车事件
cartElement.on('item-added', function(evt) {
stdout.println('添加商品: ' + evt.detail.product.name);
});
cartElement.on('item-removed', function(evt) {
stdout.println('删除商品: ' + evt.detail.product.name);
});
cartElement.on('checkout', function(evt) {
stdout.println('结算金额: ¥' + evt.detail.total.toFixed(2));
});
return cart;
}
兄弟组件通信
// 事件总线
class EventBus {
function this() {
this.events = new Map();
}
// 订阅事件
function on(eventName, callback) {
if (!this.events.has(eventName)) {
this.events.set(eventName, []);
}
this.events.get(eventName).push(callback);
// 返回取消订阅函数
var self = this;
return function off() {
var callbacks = self.events.get(eventName);
if (callbacks) {
var index = callbacks.indexOf(callback);
if (index >= 0) {
callbacks.splice(index, 1);
}
}
};
}
// 触发事件
function emit(eventName, data) {
var callbacks = this.events.get(eventName);
if (callbacks) {
for (var callback of callbacks) {
try {
callback(data);
} catch (error) {
stderr.println(`事件回调错误: ${error.message}`);
}
}
}
}
// 一次性订阅
function once(eventName, callback) {
var self = this;
var off = this.on(eventName, function(data) {
off();
callback(data);
});
return off;
}
// 移除所有监听器
function removeAllListeners(eventName) {
if (eventName) {
this.events.delete(eventName);
} else {
this.events.clear();
}
}
}
// 全局事件总线
var globalEventBus = new EventBus();
// 产品列表组件
class ProductList extends Component {
function this(element, props = {}) {
var defaultProps = {
products: []
};
super(element, Object.assign(defaultProps, props));
this.state = {
products: this.props.products.slice()
};
}
function render() {
var { products } = this.state;
this.element.className = 'product-list';
var html = '<div class="product-list-header"><h3>商品列表</h3></div>';
html += '<div class="product-items">';
for (var product of products) {
html += `
<div class="product-item" data-product-id="${product.id}">
<img src="${product.image}" alt="${product.name}" class="product-image" />
<h4 class="product-name">${product.name}</h4>
<p class="product-price">¥${product.price}</p>
<button class="product-add-to-cart" data-product-id="${product.id}">加入购物车</button>
</div>
`;
}
html += '</div>';
this.element.innerHTML = html;
}
function bindEvents() {
var self = this;
// 加入购物车按钮
this.addEventListener(this.element, 'click', function(evt) {
if (evt.target.classList.contains('product-add-to-cart')) {
var productId = parseInt(evt.target.getAttribute('data-product-id'));
var product = self.state.products.find(p => p.id === productId);
if (product) {
// 通过事件总线通知购物车组件
globalEventBus.emit('add-to-cart', {
product: product,
quantity: 1
});
// 显示添加成功提示
self.showAddToCartSuccess(product);
}
}
});
}
function showAddToCartSuccess(product) {
// 创建提示元素
var toast = Element.create('div', {
class: 'add-to-cart-toast',
text: `${product.name} 已加入购物车`
});
document.body.append(toast);
// 自动移除
setTimeout(function() {
toast.remove();
}, 2000);
}
}
// 购物车摘要组件
class CartSummary extends Component {
function this(element, props = {}) {
var defaultProps = {
itemCount: 0,
totalAmount: 0
};
super(element, Object.assign(defaultProps, props));
this.state = {
itemCount: this.props.itemCount,
totalAmount: this.props.totalAmount
};
// 订阅事件总线
this.setupEventListeners();
}
function setupEventListeners() {
var self = this;
// 监听添加到购物车事件
this.unsubscribeAddToCart = globalEventBus.on('add-to-cart', function(data) {
self.onAddToCart(data.product, data.quantity);
});
// 监听购物车更新事件
this.unsubscribeCartUpdate = globalEventBus.on('cart-updated', function(data) {
self.updateSummary(data.itemCount, data.totalAmount);
});
}
function render() {
var { itemCount, totalAmount } = this.state;
this.element.className = 'cart-summary';
this.element.innerHTML = `
<div class="cart-summary-header">
<h4>购物车</h4>
</div>
<div class="cart-summary-content">
<div class="cart-item-count">
<span class="label">商品数量:</span>
<span class="value">${itemCount}</span>
</div>
<div class="cart-total-amount">
<span class="label">总金额:</span>
<span class="value">¥${totalAmount.toFixed(2)}</span>
</div>
</div>
<div class="cart-summary-actions">
<button class="view-cart-btn">查看购物车</button>
</div>
`;
}
function bindEvents() {
var self = this;
// 查看购物车按钮
var viewCartBtn = this.$('.view-cart-btn');
if (viewCartBtn) {
this.addEventListener(viewCartBtn, 'click', function() {
// 通过事件总线通知显示购物车
globalEventBus.emit('show-cart');
});
}
}
function onAddToCart(product, quantity) {
this.setState({
itemCount: this.state.itemCount + quantity,
totalAmount: this.state.totalAmount + (product.price * quantity)
});
// 添加动画效果
this.element.addClass('cart-updated');
setTimeout(function() {
this.element.removeClass('cart-updated');
}.bind(this), 500);
}
function updateSummary(itemCount, totalAmount) {
this.setState({
itemCount: itemCount,
totalAmount: totalAmount
});
}
function beforeDestroy() {
// 取消事件订阅
if (this.unsubscribeAddToCart) {
this.unsubscribeAddToCart();
}
if (this.unsubscribeCartUpdate) {
this.unsubscribeCartUpdate();
}
}
}
// 使用示例
function createProductShop() {
// 创建产品列表
var productListElement = Element.create('div');
document.body.append(productListElement);
var productList = new ProductList(productListElement, {
products: [
{ id: 1, name: 'iPhone 15', price: 5999, image: 'iphone15.jpg' },
{ id: 2, name: 'MacBook Pro', price: 12999, image: 'macbook.jpg' },
{ id: 3, name: 'iPad Air', price: 4599, image: 'ipad.jpg' }
]
});
// 创建购物车摘要
var cartSummaryElement = Element.create('div');
document.body.append(cartSummaryElement);
var cartSummary = new CartSummary(cartSummaryElement);
// 监听显示购物车事件
globalEventBus.on('show-cart', function() {
stdout.println('显示购物车详情');
// 这里可以显示完整的购物车组件
});
return { productList, cartSummary };
}
7.4 组件生命周期
生命周期管理
// 增强的组件基类,包含完整的生命周期管理
class AdvancedComponent extends Component {
function this(element, props = {}) {
this.lifecycleState = 'created';
this.updateQueue = [];
this.isUpdating = false;
this.shouldUpdateFlag = true;
super(element, props);
}
function init() {
this.lifecycleState = 'beforeMount';
this.beforeMount();
this.lifecycleState = 'mounting';
this.render();
this.lifecycleState = 'mounted';
this.mounted();
this.bindEvents();
}
// 生命周期钩子
function beforeMount() {
// 组件挂载前,可以进行数据初始化
this.onLifecycleHook('beforeMount');
}
function mounted() {
// 组件挂载后,可以访问DOM,设置定时器等
this.onLifecycleHook('mounted');
}
function beforeUpdate(newProps, newState) {
// 组件更新前,可以进行性能优化判断
this.onLifecycleHook('beforeUpdate', { newProps, newState });
return this.shouldUpdate(newProps, newState);
}
function updated(oldProps, oldState) {
// 组件更新后,可以进行DOM操作
this.onLifecycleHook('updated', { oldProps, oldState });
}
function beforeDestroy() {
// 组件销毁前,清理资源
this.lifecycleState = 'beforeDestroy';
this.onLifecycleHook('beforeDestroy');
this.cleanup();
}
function destroyed() {
// 组件销毁后
this.lifecycleState = 'destroyed';
this.onLifecycleHook('destroyed');
}
// 错误处理生命周期
function errorCaptured(error, instance, info) {
// 捕获子组件错误
this.onLifecycleHook('errorCaptured', { error, instance, info });
stderr.println(`组件错误: ${error.message}`);
return false; // 阻止错误继续传播
}
// 性能优化:判断是否需要更新
function shouldUpdate(newProps, newState) {
// 默认实现:浅比较props和state
if (!this.shouldUpdateFlag) return false;
// 比较props
for (var key in newProps) {
if (newProps[key] !== this.props[key]) {
return true;
}
}
// 比较state
for (var key in newState) {
if (newState[key] !== this.state[key]) {
return true;
}
}
return false;
}
// 更新组件(增强版)
function update(newProps = {}, newState = {}) {
if (this.lifecycleState === 'destroyed') {
stderr.println('尝试更新已销毁的组件');
return;
}
// 如果正在更新,加入队列
if (this.isUpdating) {
this.updateQueue.push({ newProps, newState });
return;
}
this.isUpdating = true;
try {
var oldProps = Object.assign({}, this.props);
var oldState = Object.assign({}, this.state);
var mergedProps = Object.assign({}, this.props, newProps);
var mergedState = Object.assign({}, this.state, newState);
// 检查是否需要更新
if (this.beforeUpdate(mergedProps, mergedState)) {
this.lifecycleState = 'updating';
// 更新props和state
this.props = mergedProps;
this.state = mergedState;
// 重新渲染
this.render();
this.lifecycleState = 'updated';
this.updated(oldProps, oldState);
}
} catch (error) {
this.handleError(error, 'update');
} finally {
this.isUpdating = false;
// 处理更新队列
this.processUpdateQueue();
}
}
// 处理更新队列
function processUpdateQueue() {
if (this.updateQueue.length > 0) {
var nextUpdate = this.updateQueue.shift();
setTimeout(function() {
this.update(nextUpdate.newProps, nextUpdate.newState);
}.bind(this), 0);
}
}
// 错误处理
function handleError(error, context) {
var errorInfo = {
error: error,
context: context,
component: this.constructor.name,
props: this.props,
state: this.state
};
// 触发错误事件
this.emit('error', errorInfo);
// 如果有父组件,通知父组件
if (this.parent && this.parent.errorCaptured) {
this.parent.errorCaptured(error, this, context);
}
stderr.println(`组件错误 [${context}]: ${error.message}`);
}
// 生命周期钩子回调
function onLifecycleHook(hookName, data) {
this.emit('lifecycle:' + hookName, {
component: this,
hookName: hookName,
data: data
});
}
// 清理资源
function cleanup() {
// 清理定时器
if (this.timers) {
for (var timer of this.timers) {
clearTimeout(timer);
}
this.timers = [];
}
// 清理事件监听器
if (this.globalEventListeners) {
for (var listener of this.globalEventListeners) {
listener.element.off(listener.event, listener.handler);
}
this.globalEventListeners = [];
}
// 清理观察者
if (this.observers) {
for (var observer of this.observers) {
if (observer.disconnect) {
observer.disconnect();
}
}
this.observers = [];
}
}
// 安全的定时器
function setTimeout(callback, delay) {
if (!this.timers) this.timers = [];
var timer = window.setTimeout(function() {
if (this.lifecycleState !== 'destroyed') {
callback.call(this);
}
}.bind(this), delay);
this.timers.push(timer);
return timer;
}
// 安全的间隔定时器
function setInterval(callback, interval) {
if (!this.timers) this.timers = [];
var timer = window.setInterval(function() {
if (this.lifecycleState !== 'destroyed') {
callback.call(this);
} else {
clearInterval(timer);
}
}.bind(this), interval);
this.timers.push(timer);
return timer;
}
// 获取生命周期状态
function getLifecycleState() {
return this.lifecycleState;
}
// 强制更新
function forceUpdate() {
this.shouldUpdateFlag = false;
this.update();
this.shouldUpdateFlag = true;
}
}
// 生命周期示例组件
class LifecycleDemo extends AdvancedComponent {
function this(element, props = {}) {
super(element, props);
this.state = {
count: 0,
message: 'Hello World'
};
}
function beforeMount() {
stdout.println('LifecycleDemo: beforeMount');
// 可以在这里进行数据预处理
}
function mounted() {
stdout.println('LifecycleDemo: mounted');
// 设置定时器
this.setTimeout(function() {
this.setState({ message: 'Component mounted!' });
}, 1000);
// 设置间隔定时器
this.setInterval(function() {
this.setState({ count: this.state.count + 1 });
}, 2000);
}
function beforeUpdate(newProps, newState) {
stdout.println('LifecycleDemo: beforeUpdate');
// 性能优化:如果count大于10就不再更新
if (newState.count > 10) {
return false;
}
return super.beforeUpdate(newProps, newState);
}
function updated(oldProps, oldState) {
stdout.println('LifecycleDemo: updated');
stdout.println(`Count changed from ${oldState.count} to ${this.state.count}`);
}
function beforeDestroy() {
stdout.println('LifecycleDemo: beforeDestroy');
super.beforeDestroy();
}
function destroyed() {
stdout.println('LifecycleDemo: destroyed');
super.destroyed();
}
function render() {
var { count, message } = this.state;
this.element.innerHTML = `
<div class="lifecycle-demo">
<h3>生命周期演示</h3>
<p>消息: ${message}</p>
<p>计数: ${count}</p>
<button class="increment-btn">手动增加</button>
<button class="destroy-btn">销毁组件</button>
</div>
`;
}
function bindEvents() {
var self = this;
// 手动增加按钮
var incrementBtn = this.$('.increment-btn');
if (incrementBtn) {
this.addEventListener(incrementBtn, 'click', function() {
self.setState({ count: self.state.count + 1 });
});
}
// 销毁按钮
var destroyBtn = this.$('.destroy-btn');
if (destroyBtn) {
this.addEventListener(destroyBtn, 'click', function() {
self.destroy();
});
}
}
function errorCaptured(error, instance, info) {
stdout.println(`捕获到错误: ${error.message}`);
return true; // 阻止错误继续传播
}
}
// 使用示例
function createLifecycleDemo() {
var demoElement = Element.create('div');
document.body.append(demoElement);
var demo = new LifecycleDemo(demoElement);
// 监听生命周期事件
demoElement.on('lifecycle:mounted', function(evt) {
stdout.println('外部监听到mounted事件');
});
demoElement.on('lifecycle:updated', function(evt) {
stdout.println('外部监听到updated事件');
});
demoElement.on('error', function(evt) {
stdout.println('外部监听到错误事件: ' + evt.detail.error.message);
});
return demo;
}
7.5 高级组件模式
高阶组件(HOC)
// 高阶组件:为组件添加加载状态
function withLoading(WrappedComponent) {
return class LoadingWrapper extends AdvancedComponent {
function this(element, props = {}) {
super(element, props);
this.state = {
loading: props.loading || false,
error: null
};
}
function render() {
var { loading, error } = this.state;
if (error) {
this.element.innerHTML = `
<div class="error-wrapper">
<p class="error-message">加载失败: ${error}</p>
<button class="retry-btn">重试</button>
</div>
`;
return;
}
if (loading) {
this.element.innerHTML = `
<div class="loading-wrapper">
<div class="loading-spinner"></div>
<p class="loading-text">加载中...</p>
</div>
`;
return;
}
// 渲染包装的组件
this.renderWrappedComponent();
}
function renderWrappedComponent() {
if (!this.wrappedComponent) {
this.wrappedComponent = new WrappedComponent(this.element, this.props);
this.children.push(this.wrappedComponent);
} else {
this.wrappedComponent.update(this.props);
}
}
function bindEvents() {
var self = this;
// 重试按钮
var retryBtn = this.$('.retry-btn');
if (retryBtn) {
this.addEventListener(retryBtn, 'click', function() {
self.retry();
});
}
}
function setLoading(loading) {
this.setState({ loading: loading, error: null });
}
function setError(error) {
this.setState({ loading: false, error: error });
}
function retry() {
this.setState({ loading: true, error: null });
this.emit('retry');
}
function getWrappedComponent() {
return this.wrappedComponent;
}
};
}
// 高阶组件:为组件添加权限控制
function withPermission(WrappedComponent, requiredPermissions = []) {
return class PermissionWrapper extends AdvancedComponent {
function this(element, props = {}) {
super(element, props);
this.state = {
hasPermission: this.checkPermissions()
};
}
function checkPermissions() {
// 模拟权限检查
var userPermissions = this.props.userPermissions || [];
return requiredPermissions.every(permission =>
userPermissions.includes(permission)
);
}
function render() {
if (!this.state.hasPermission) {
this.element.innerHTML = `
<div class="permission-denied">
<p>您没有权限访问此内容</p>
<p>需要权限: ${requiredPermissions.join(', ')}</p>
</div>
`;
return;
}
// 渲染包装的组件
if (!this.wrappedComponent) {
this.wrappedComponent = new WrappedComponent(this.element, this.props);
this.children.push(this.wrappedComponent);
} else {
this.wrappedComponent.update(this.props);
}
}
function updatePermissions(newPermissions) {
this.props.userPermissions = newPermissions;
this.setState({ hasPermission: this.checkPermissions() });
}
};
}
// 使用高阶组件
class UserProfile extends AdvancedComponent {
function render() {
this.element.innerHTML = `
<div class="user-profile">
<h3>用户资料</h3>
<p>姓名: ${this.props.user.name}</p>
<p>邮箱: ${this.props.user.email}</p>
</div>
`;
}
}
// 创建增强的组件
var LoadingUserProfile = withLoading(UserProfile);
var ProtectedUserProfile = withPermission(LoadingUserProfile, ['user:read']);
function createEnhancedUserProfile() {
var element = Element.create('div');
document.body.append(element);
var profile = new ProtectedUserProfile(element, {
user: { name: 'John Doe', email: 'john@example.com' },
userPermissions: ['user:read'],
loading: true
});
// 模拟异步加载
setTimeout(function() {
profile.setLoading(false);
}, 2000);
return profile;
}
渲染属性模式(Render Props)
// 渲染属性组件:数据获取器
class DataFetcher extends AdvancedComponent {
function this(element, props = {}) {
super(element, props);
this.state = {
data: null,
loading: false,
error: null
};
}
function mounted() {
if (this.props.url) {
this.fetchData();
}
}
function fetchData() {
this.setState({ loading: true, error: null });
// 模拟异步数据获取
setTimeout(function() {
try {
// 模拟API调用
var mockData = {
users: [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
]
};
this.setState({
data: mockData,
loading: false
});
} catch (error) {
this.setState({
error: error.message,
loading: false
});
}
}.bind(this), 1000);
}
function render() {
var { data, loading, error } = this.state;
var { render } = this.props;
if (typeof render === 'function') {
// 调用渲染函数,传递状态和方法
var renderResult = render({
data: data,
loading: loading,
error: error,
refetch: this.fetchData.bind(this)
});
this.element.innerHTML = renderResult;
} else {
this.element.innerHTML = '<div>No render function provided</div>';
}
}
function refetch() {
this.fetchData();
}
}
// 使用渲染属性
function createDataFetcherExample() {
var element = Element.create('div');
document.body.append(element);
var fetcher = new DataFetcher(element, {
url: '/api/users',
render: function(props) {
var { data, loading, error, refetch } = props;
if (loading) {
return '<div class="loading">加载中...</div>';
}
if (error) {
return `
<div class="error">
<p>错误: ${error}</p>
<button onclick="${refetch}">重试</button>
</div>
`;
}
if (data && data.users) {
var userList = data.users.map(user =>
`<li>${user.name} - ${user.email}</li>`
).join('');
return `
<div class="user-list">
<h3>用户列表</h3>
<ul>${userList}</ul>
<button onclick="${refetch}">刷新</button>
</div>
`;
}
return '<div>暂无数据</div>';
}
});
return fetcher;
}
// 鼠标位置跟踪器(另一个渲染属性示例)
class MouseTracker extends AdvancedComponent {
function this(element, props = {}) {
super(element, props);
this.state = {
x: 0,
y: 0
};
}
function mounted() {
this.addEventListener(document, 'mousemove', this.handleMouseMove.bind(this));
}
function handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
function render() {
var { x, y } = this.state;
var { render } = this.props;
if (typeof render === 'function') {
var renderResult = render({ x, y });
this.element.innerHTML = renderResult;
} else {
this.element.innerHTML = `<div>鼠标位置: (${x}, ${y})</div>`;
}
}
}
function createMouseTrackerExample() {
var element = Element.create('div');
document.body.append(element);
var tracker = new MouseTracker(element, {
render: function(mouse) {
return `
<div class="mouse-tracker">
<h3>鼠标跟踪器</h3>
<p>当前位置: (${mouse.x}, ${mouse.y})</p>
<div class="crosshair" style="left: ${mouse.x}px; top: ${mouse.y}px;"></div>
</div>
`;
}
});
return tracker;
}
组件组合模式
// 复合组件:卡片组件
class Card extends AdvancedComponent {
function render() {
this.element.className = 'card';
// 保留子元素内容
// 卡片组件作为容器,不修改内部内容
}
static Header = class CardHeader extends AdvancedComponent {
function render() {
this.element.className = 'card-header';
if (this.props.title) {
this.element.innerHTML = `<h3 class="card-title">${this.props.title}</h3>`;
}
}
};
static Body = class CardBody extends AdvancedComponent {
function render() {
this.element.className = 'card-body';
if (this.props.content) {
this.element.innerHTML = this.props.content;
}
}
};
static Footer = class CardFooter extends AdvancedComponent {
function render() {
this.element.className = 'card-footer';
if (this.props.actions) {
var actionsHtml = this.props.actions.map(action =>
`<button class="card-action" data-action="${action.name}">${action.label}</button>`
).join('');
this.element.innerHTML = actionsHtml;
}
}
function bindEvents() {
var self = this;
this.addEventListener(this.element, 'click', function(evt) {
if (evt.target.classList.contains('card-action')) {
var actionName = evt.target.getAttribute('data-action');
var action = self.props.actions.find(a => a.name === actionName);
if (action && action.handler) {
action.handler(evt);
}
self.emit('action', {
action: actionName,
originalEvent: evt
});
}
});
}
};
}
// 使用复合组件
function createCardExample() {
// 创建卡片容器
var cardElement = Element.create('div');
document.body.append(cardElement);
var card = new Card(cardElement);
// 创建卡片头部
var headerElement = Element.create('div');
cardElement.append(headerElement);
var header = new Card.Header(headerElement, {
title: '用户信息'
});
// 创建卡片主体
var bodyElement = Element.create('div');
cardElement.append(bodyElement);
var body = new Card.Body(bodyElement, {
content: `
<p><strong>姓名:</strong> 张三</p>
<p><strong>邮箱:</strong> zhangsan@example.com</p>
<p><strong>部门:</strong> 技术部</p>
`
});
// 创建卡片底部
var footerElement = Element.create('div');
cardElement.append(footerElement);
var footer = new Card.Footer(footerElement, {
actions: [
{
name: 'edit',
label: '编辑',
handler: function() {
stdout.println('编辑用户');
}
},
{
name: 'delete',
label: '删除',
handler: function() {
stdout.println('删除用户');
}
}
]
});
// 监听底部动作
footerElement.on('action', function(evt) {
stdout.println('卡片动作: ' + evt.detail.action);
});
return { card, header, body, footer };
}
// 表单复合组件
class Form extends AdvancedComponent {
function this(element, props = {}) {
super(element, props);
this.fields = new Map();
this.validators = [];
}
function render() {
this.element.className = 'form';
if (this.props.title) {
var titleElement = Element.create('h3', {
class: 'form-title',
text: this.props.title
});
this.element.prepend(titleElement);
}
}
function addField(name, component) {
this.fields.set(name, component);
}
function getFieldValue(name) {
var field = this.fields.get(name);
return field && field.getValue ? field.getValue() : null;
}
function setFieldValue(name, value) {
var field = this.fields.get(name);
if (field && field.setValue) {
field.setValue(value);
}
}
function getFormData() {
var data = {};
for (var [name, field] of this.fields) {
data[name] = this.getFieldValue(name);
}
return data;
}
function validate() {
var isValid = true;
var errors = {};
// 验证各个字段
for (var [name, field] of this.fields) {
if (field.validate && !field.validate()) {
isValid = false;
errors[name] = '字段验证失败';
}
}
// 执行表单级验证器
for (var validator of this.validators) {
var result = validator(this.getFormData());
if (!result.valid) {
isValid = false;
Object.assign(errors, result.errors);
}
}
return { valid: isValid, errors: errors };
}
function addValidator(validator) {
this.validators.push(validator);
}
function submit() {
var validation = this.validate();
if (validation.valid) {
var formData = this.getFormData();
this.emit('submit', {
data: formData,
form: this
});
} else {
this.emit('validation-error', {
errors: validation.errors,
form: this
});
}
return validation.valid;
}
static Field = class FormField extends AdvancedComponent {
function this(element, props = {}) {
super(element, props);
// 注册到父表单
if (this.props.form && this.props.name) {
this.props.form.addField(this.props.name, this);
}
}
function render() {
var { label, name, required, type = 'text' } = this.props;
this.element.className = 'form-field';
var html = '';
if (label) {
html += `<label class="field-label" for="${name}">${label}${required ? ' *' : ''}</label>`;
}
html += `<input type="${type}" id="${name}" name="${name}" class="field-input" ${required ? 'required' : ''} />`;
html += '<div class="field-error"></div>';
this.element.innerHTML = html;
this.inputElement = this.$('.field-input');
}
function getValue() {
return this.inputElement ? this.inputElement.value : '';
}
function setValue(value) {
if (this.inputElement) {
this.inputElement.value = value;
}
}
function validate() {
var value = this.getValue();
var { required, pattern } = this.props;
// 必填验证
if (required && (!value || value.trim() === '')) {
this.setError('此字段为必填项');
return false;
}
// 模式验证
if (pattern && value && !new RegExp(pattern).test(value)) {
this.setError('格式不正确');
return false;
}
this.clearError();
return true;
}
function setError(message) {
this.element.addClass('field-error-state');
var errorElement = this.$('.field-error');
if (errorElement) {
errorElement.innerText = message;
}
}
function clearError() {
this.element.removeClass('field-error-state');
var errorElement = this.$('.field-error');
if (errorElement) {
errorElement.innerText = '';
}
}
};
static SubmitButton = class FormSubmitButton extends AdvancedComponent {
function render() {
var { text = '提交', form } = this.props;
this.element.innerHTML = `<button type="submit" class="form-submit">${text}</button>`;
}
function bindEvents() {
var self = this;
this.addEventListener(this.element, 'click', function(evt) {
evt.preventDefault();
if (self.props.form) {
self.props.form.submit();
}
});
}
};
}
// 使用表单复合组件
function createFormExample() {
var formElement = Element.create('form');
document.body.append(formElement);
var form = new Form(formElement, {
title: '用户注册'
});
// 添加字段
var nameFieldElement = Element.create('div');
formElement.append(nameFieldElement);
var nameField = new Form.Field(nameFieldElement, {
name: 'name',
label: '姓名',
required: true,
form: form
});
var emailFieldElement = Element.create('div');
formElement.append(emailFieldElement);
var emailField = new Form.Field(emailFieldElement, {
name: 'email',
label: '邮箱',
type: 'email',
required: true,
pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
form: form
});
var passwordFieldElement = Element.create('div');
formElement.append(passwordFieldElement);
var passwordField = new Form.Field(passwordFieldElement, {
name: 'password',
label: '密码',
type: 'password',
required: true,
form: form
});
// 添加提交按钮
var submitButtonElement = Element.create('div');
formElement.append(submitButtonElement);
var submitButton = new Form.SubmitButton(submitButtonElement, {
text: '注册',
form: form
});
// 添加表单级验证器
form.addValidator(function(data) {
var errors = {};
if (data.password && data.password.length < 6) {
errors.password = '密码长度至少6位';
}
return {
valid: Object.keys(errors).length === 0,
errors: errors
};
});
// 监听表单事件
formElement.on('submit', function(evt) {
stdout.println('表单提交成功:');
stdout.println(JSON.stringify(evt.detail.data, null, 2));
});
formElement.on('validation-error', function(evt) {
stdout.println('表单验证失败:');
stdout.println(JSON.stringify(evt.detail.errors, null, 2));
});
return { form, nameField, emailField, passwordField, submitButton };
}
7.6 本章总结
核心要点
组件化基础
- 组件的基本结构和生命周期
- 属性传递和状态管理
- 事件绑定和清理
基础组件开发
- 按钮、输入框、模态框等常用组件
- 组件的可复用性和可配置性
- 组件的样式和交互设计
组件通信
- 父子组件通信(属性传递、事件触发)
- 兄弟组件通信(事件总线)
- 跨层级组件通信(全局状态管理)
生命周期管理
- 完整的生命周期钩子
- 资源清理和内存管理
- 错误处理和异常捕获
高级组件模式
- 高阶组件(HOC)模式
- 渲染属性(Render Props)模式
- 组件组合(Compound Components)模式
最佳实践
组件设计原则
- 单一职责:每个组件只负责一个功能
- 可复用性:设计通用的、可配置的组件
- 可组合性:组件应该易于组合和扩展
- 可测试性:组件应该易于单元测试
性能优化
- 实现shouldUpdate方法避免不必要的重渲染
- 合理使用组件缓存和懒加载
- 及时清理事件监听器和定时器
- 避免在render方法中进行复杂计算
代码组织
- 按功能模块组织组件文件
- 使用命名空间避免全局污染
- 建立组件库和设计系统
- 编写清晰的组件文档
错误处理
- 实现错误边界组件
- 提供友好的错误提示
- 记录和上报组件错误
- 实现错误恢复机制
练习题
基础练习
- 创建一个评分组件,支持星级评分和数字评分两种模式
- 实现一个分页组件,支持页码跳转和每页条数设置
- 开发一个标签页组件,支持动态添加和删除标签页
进阶练习
- 创建一个表格组件,支持排序、筛选、分页功能
- 实现一个树形组件,支持节点的展开、折叠、选择
- 开发一个图片上传组件,支持拖拽上传和预览功能
高级练习
- 设计一个可视化图表组件库,支持柱状图、折线图、饼图
- 实现一个富文本编辑器组件,支持格式化和插件扩展
- 创建一个完整的表单构建器,支持拖拽设计和动态验证
下一章预告
在下一章中,我们将学习Sciter的原生API集成,包括: - 文件系统操作 - 网络请求处理 - 系统通知和托盘 - 硬件设备访问 - 第三方库集成
通过原生API的学习,您将能够开发功能更加强大的桌面应用程序。