6.1 数据绑定概述
Sciter数据绑定机制
Sciter提供了强大的数据绑定功能,允许将JavaScript对象与DOM元素进行双向绑定。这种机制使得数据变化能够自动反映到界面上,同时界面的变化也能自动更新数据模型。
数据绑定特性
- 双向绑定:数据到视图,视图到数据的自动同步
- 响应式更新:数据变化时自动更新相关的DOM元素
- 表达式支持:支持复杂的绑定表达式
- 条件绑定:基于条件的动态绑定
- 列表绑定:数组数据的自动渲染
- 事件绑定:将DOM事件绑定到数据模型方法
绑定语法
<!-- 文本绑定 -->
<span>{user.name}</span>
<div>{user.age} 岁</div>
<!-- 属性绑定 -->
<img src="{user.avatar}" alt="{user.name}" />
<input value="{user.email}" />
<!-- 条件绑定 -->
<div class="{user.isActive ? 'active' : 'inactive'}">
{user.isActive ? '在线' : '离线'}
</div>
<!-- 循环绑定 -->
<ul>
<li :each="item in items">{item.name}</li>
</ul>
<!-- 事件绑定 -->
<button onclick="{this.handleClick}">点击</button>
6.2 基础数据绑定
简单数据绑定
1. 文本和属性绑定
// 数据模型
var userModel = {
name: "张三",
email: "zhangsan@example.com",
age: 25,
avatar: "avatar.jpg",
isActive: true,
profile: {
bio: "软件开发工程师",
location: "北京",
website: "https://example.com"
}
};
// 绑定数据到DOM
function bindUserData() {
var container = $("#user-profile");
// 设置数据上下文
container.data = userModel;
// 手动更新绑定(如果需要)
container.update();
}
// HTML模板
/*
<div id="user-profile">
<div class="user-header">
<img src="{avatar}" alt="{name}" class="avatar" />
<h2>{name}</h2>
<span class="status {isActive ? 'online' : 'offline'}">
{isActive ? '在线' : '离线'}
</span>
</div>
<div class="user-info">
<p><strong>邮箱:</strong> {email}</p>
<p><strong>年龄:</strong> {age} 岁</p>
<p><strong>简介:</strong> {profile.bio}</p>
<p><strong>位置:</strong> {profile.location}</p>
<p><strong>网站:</strong>
<a href="{profile.website}" target="_blank">{profile.website}</a>
</p>
</div>
</div>
*/
// 更新数据
function updateUserData(newData) {
Object.assign(userModel, newData);
// 触发更新
var container = $("#user-profile");
container.update();
stdout.println("用户数据已更新");
}
// 使用示例
bindUserData();
// 更新用户状态
setTimeout(function() {
updateUserData({
isActive: false,
age: 26
});
}, 3000);
2. 表单数据绑定
// 表单数据模型
var formModel = {
user: {
firstName: "",
lastName: "",
email: "",
phone: "",
gender: "male",
birthDate: "",
country: "CN",
city: "",
interests: [],
newsletter: false,
terms: false
},
// 验证规则
validation: {
firstName: { required: true, minLength: 2 },
lastName: { required: true, minLength: 2 },
email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
phone: { pattern: /^\d{11}$/ },
terms: { required: true }
},
// 错误信息
errors: {},
// 表单状态
isSubmitting: false,
isValid: false
};
// 表单绑定管理器
class FormBinding {
function this(formElement, model) {
this.form = formElement;
this.model = model;
this.watchers = [];
this.init();
}
function init() {
this.bindFormElements();
this.setupValidation();
this.setupEvents();
}
function bindFormElements() {
var self = this;
// 绑定输入字段
this.form.$$('input, textarea, select').forEach(function(element) {
var name = element.name;
if (!name) return;
// 设置初始值
self.setElementValue(element, self.getModelValue(name));
// 监听变化
element.on('input change', function() {
self.updateModelFromElement(element);
});
});
// 绑定复选框组
this.form.$$('input[type="checkbox"][data-array]').forEach(function(checkbox) {
var arrayName = checkbox.getAttribute('data-array');
var value = checkbox.value;
checkbox.on('change', function() {
self.updateArrayValue(arrayName, value, this.checked);
});
});
}
function setElementValue(element, value) {
switch (element.type) {
case 'checkbox':
element.checked = !!value;
break;
case 'radio':
element.checked = element.value === value;
break;
default:
element.value = value || '';
}
}
function getModelValue(path) {
var parts = path.split('.');
var value = this.model;
for (var part of parts) {
value = value && value[part];
}
return value;
}
function setModelValue(path, value) {
var parts = path.split('.');
var obj = this.model;
for (var i = 0; i < parts.length - 1; i++) {
if (!obj[parts[i]]) {
obj[parts[i]] = {};
}
obj = obj[parts[i]];
}
obj[parts[parts.length - 1]] = value;
// 触发验证
this.validateField(path);
// 通知观察者
this.notifyWatchers(path, value);
}
function updateModelFromElement(element) {
var name = element.name;
var value;
switch (element.type) {
case 'checkbox':
value = element.checked;
break;
case 'number':
value = parseFloat(element.value) || 0;
break;
case 'date':
value = element.value;
break;
default:
value = element.value;
}
this.setModelValue(name, value);
}
function updateArrayValue(arrayName, value, checked) {
var array = this.getModelValue(arrayName) || [];
var index = array.indexOf(value);
if (checked && index === -1) {
array.push(value);
} else if (!checked && index >= 0) {
array.splice(index, 1);
}
this.setModelValue(arrayName, array);
}
function setupValidation() {
var self = this;
// 实时验证
this.form.on('input', 'input, textarea, select', function() {
var name = this.name;
if (name) {
self.validateField(name);
}
});
// 失去焦点验证
this.form.on('blur', 'input, textarea, select', function() {
var name = this.name;
if (name) {
self.validateField(name);
self.updateFieldUI(this, name);
}
});
}
function validateField(fieldName) {
var value = this.getModelValue(fieldName);
var rules = this.model.validation[fieldName];
if (!rules) return true;
var errors = [];
// 必填验证
if (rules.required && (!value || value.toString().trim() === '')) {
errors.push('此字段为必填项');
}
// 长度验证
if (value && rules.minLength && value.toString().length < rules.minLength) {
errors.push(`最少需要${rules.minLength}个字符`);
}
if (value && rules.maxLength && value.toString().length > rules.maxLength) {
errors.push(`最多允许${rules.maxLength}个字符`);
}
// 模式验证
if (value && rules.pattern && !rules.pattern.test(value.toString())) {
errors.push('格式不正确');
}
// 自定义验证
if (rules.validator && typeof rules.validator === 'function') {
var customError = rules.validator(value, this.model);
if (customError) {
errors.push(customError);
}
}
// 更新错误信息
if (errors.length > 0) {
this.model.errors[fieldName] = errors[0];
} else {
delete this.model.errors[fieldName];
}
return errors.length === 0;
}
function updateFieldUI(element, fieldName) {
var hasError = this.model.errors[fieldName];
var container = element.closest('.form-group');
if (hasError) {
element.addClass('error');
if (container) {
container.addClass('has-error');
// 显示错误信息
var errorElement = container.$('.error-message');
if (!errorElement) {
errorElement = Element.create('div', {
class: 'error-message',
text: hasError
});
container.append(errorElement);
} else {
errorElement.innerText = hasError;
}
}
} else {
element.removeClass('error');
if (container) {
container.removeClass('has-error');
var errorElement = container.$('.error-message');
if (errorElement) {
errorElement.remove();
}
}
}
}
function setupEvents() {
var self = this;
// 表单提交
this.form.on('submit', function(evt) {
evt.preventDefault();
self.handleSubmit();
});
// 重置表单
this.form.on('reset', function(evt) {
self.handleReset();
});
}
function handleSubmit() {
// 验证所有字段
var isValid = this.validateAll();
if (isValid) {
this.model.isSubmitting = true;
this.updateSubmitButton();
// 提交数据
this.submitForm()
.then(function(result) {
stdout.println('表单提交成功');
self.handleSubmitSuccess(result);
})
.catch(function(error) {
stderr.println('表单提交失败: ' + error.message);
self.handleSubmitError(error);
})
.finally(function() {
self.model.isSubmitting = false;
self.updateSubmitButton();
});
} else {
stdout.println('表单验证失败');
this.focusFirstError();
}
}
function validateAll() {
var isValid = true;
for (var fieldName in this.model.validation) {
if (!this.validateField(fieldName)) {
isValid = false;
}
}
this.model.isValid = isValid;
return isValid;
}
function submitForm() {
return new Promise(function(resolve, reject) {
// 模拟异步提交
setTimeout(function() {
if (Math.random() > 0.1) { // 90% 成功率
resolve({ id: Date.now(), message: '提交成功' });
} else {
reject(new Error('服务器错误'));
}
}, 2000);
});
}
function handleSubmitSuccess(result) {
// 显示成功消息
this.showMessage('表单提交成功!', 'success');
// 重置表单(可选)
// this.handleReset();
}
function handleSubmitError(error) {
this.showMessage('提交失败:' + error.message, 'error');
}
function handleReset() {
// 重置模型数据
for (var key in this.model.user) {
if (typeof this.model.user[key] === 'string') {
this.model.user[key] = '';
} else if (typeof this.model.user[key] === 'boolean') {
this.model.user[key] = false;
} else if (Array.isArray(this.model.user[key])) {
this.model.user[key] = [];
}
}
// 清除错误
this.model.errors = {};
// 更新UI
this.form.$$('input, textarea, select').forEach(function(element) {
element.removeClass('error');
var container = element.closest('.form-group');
if (container) {
container.removeClass('has-error');
var errorElement = container.$('.error-message');
if (errorElement) {
errorElement.remove();
}
}
});
stdout.println('表单已重置');
}
function focusFirstError() {
for (var fieldName in this.model.errors) {
var element = this.form.$(`[name="${fieldName}"]`);
if (element) {
element.focus();
break;
}
}
}
function updateSubmitButton() {
var submitButton = this.form.$('button[type="submit"]');
if (submitButton) {
submitButton.disabled = this.model.isSubmitting;
submitButton.innerText = this.model.isSubmitting ? '提交中...' : '提交';
}
}
function showMessage(message, type) {
var messageElement = Element.create('div', {
class: `message message-${type}`,
text: message
});
this.form.insertBefore(messageElement, this.form.firstChild);
// 自动隐藏
setTimeout(function() {
messageElement.remove();
}, 5000);
}
// 添加观察者
function watch(path, callback) {
this.watchers.push({ path: path, callback: callback });
}
// 通知观察者
function notifyWatchers(path, value) {
for (var watcher of this.watchers) {
if (watcher.path === path) {
watcher.callback(value, path);
}
}
}
// 获取表单数据
function getData() {
return JSON.parse(JSON.stringify(this.model.user));
}
// 设置表单数据
function setData(data) {
Object.assign(this.model.user, data);
// 更新UI
this.form.$$('input, textarea, select').forEach(function(element) {
var name = element.name;
if (name) {
self.setElementValue(element, self.getModelValue(name));
}
});
}
}
// 使用示例
function setupFormBinding() {
var form = $('#user-form');
var formBinding = new FormBinding(form, formModel);
// 监听字段变化
formBinding.watch('user.email', function(value) {
stdout.println('邮箱改变: ' + value);
});
formBinding.watch('user.firstName', function(value) {
stdout.println('名字改变: ' + value);
});
return formBinding;
}
// HTML表单模板
/*
<form id="user-form">
<div class="form-group">
<label for="firstName">名字 *</label>
<input type="text" id="firstName" name="user.firstName" required />
</div>
<div class="form-group">
<label for="lastName">姓氏 *</label>
<input type="text" id="lastName" name="user.lastName" required />
</div>
<div class="form-group">
<label for="email">邮箱 *</label>
<input type="email" id="email" name="user.email" required />
</div>
<div class="form-group">
<label for="phone">电话</label>
<input type="tel" id="phone" name="user.phone" />
</div>
<div class="form-group">
<label>性别</label>
<label><input type="radio" name="user.gender" value="male" checked /> 男</label>
<label><input type="radio" name="user.gender" value="female" /> 女</label>
</div>
<div class="form-group">
<label for="birthDate">出生日期</label>
<input type="date" id="birthDate" name="user.birthDate" />
</div>
<div class="form-group">
<label for="country">国家</label>
<select id="country" name="user.country">
<option value="CN">中国</option>
<option value="US">美国</option>
<option value="UK">英国</option>
</select>
</div>
<div class="form-group">
<label>兴趣爱好</label>
<label><input type="checkbox" data-array="user.interests" value="reading" /> 阅读</label>
<label><input type="checkbox" data-array="user.interests" value="music" /> 音乐</label>
<label><input type="checkbox" data-array="user.interests" value="sports" /> 运动</label>
<label><input type="checkbox" data-array="user.interests" value="travel" /> 旅行</label>
</div>
<div class="form-group">
<label><input type="checkbox" name="user.newsletter" /> 订阅新闻邮件</label>
</div>
<div class="form-group">
<label><input type="checkbox" name="user.terms" required /> 同意服务条款 *</label>
</div>
<div class="form-actions">
<button type="submit">提交</button>
<button type="reset">重置</button>
</div>
</form>
*/
6.3 高级数据绑定
列表数据绑定
1. 动态列表渲染
// 列表数据模型
var listModel = {
items: [
{ id: 1, name: "任务1", completed: false, priority: "high", dueDate: "2024-01-15" },
{ id: 2, name: "任务2", completed: true, priority: "medium", dueDate: "2024-01-10" },
{ id: 3, name: "任务3", completed: false, priority: "low", dueDate: "2024-01-20" }
],
filter: "all", // all, active, completed
sortBy: "name", // name, priority, dueDate
sortOrder: "asc" // asc, desc
};
// 列表绑定管理器
class ListBinding {
function this(container, model, itemTemplate) {
this.container = container;
this.model = model;
this.itemTemplate = itemTemplate;
this.renderedItems = new Map();
this.init();
}
function init() {
this.render();
this.setupEvents();
}
function render() {
// 获取过滤和排序后的数据
var filteredItems = this.getFilteredItems();
var sortedItems = this.getSortedItems(filteredItems);
// 清空容器
this.container.innerHTML = '';
this.renderedItems.clear();
// 渲染项目
for (var item of sortedItems) {
var element = this.renderItem(item);
this.container.append(element);
this.renderedItems.set(item.id, element);
}
stdout.println(`渲染了 ${sortedItems.length} 个项目`);
}
function renderItem(item) {
// 克隆模板
var element = this.itemTemplate.cloneNode(true);
element.removeAttribute('id');
element.setAttribute('data-item-id', item.id);
// 绑定数据
this.bindItemData(element, item);
// 设置事件
this.setupItemEvents(element, item);
return element;
}
function bindItemData(element, item) {
// 绑定文本内容
var nameElement = element.$('.item-name');
if (nameElement) {
nameElement.innerText = item.name;
}
var dueDateElement = element.$('.item-due-date');
if (dueDateElement) {
dueDateElement.innerText = this.formatDate(item.dueDate);
}
// 绑定属性
var checkbox = element.$('.item-checkbox');
if (checkbox) {
checkbox.checked = item.completed;
}
// 绑定样式类
element.className = `list-item priority-${item.priority} ${item.completed ? 'completed' : ''}`;
// 绑定优先级
var priorityElement = element.$('.item-priority');
if (priorityElement) {
priorityElement.innerText = this.getPriorityText(item.priority);
priorityElement.className = `item-priority priority-${item.priority}`;
}
}
function setupItemEvents(element, item) {
var self = this;
// 完成状态切换
var checkbox = element.$('.item-checkbox');
if (checkbox) {
checkbox.on('change', function() {
self.toggleItemCompleted(item.id, this.checked);
});
}
// 编辑项目
var editButton = element.$('.item-edit');
if (editButton) {
editButton.on('click', function() {
self.editItem(item.id);
});
}
// 删除项目
var deleteButton = element.$('.item-delete');
if (deleteButton) {
deleteButton.on('click', function() {
self.deleteItem(item.id);
});
}
// 双击编辑
element.on('dblclick', function() {
self.editItem(item.id);
});
}
function getFilteredItems() {
switch (this.model.filter) {
case 'active':
return this.model.items.filter(item => !item.completed);
case 'completed':
return this.model.items.filter(item => item.completed);
default:
return this.model.items;
}
}
function getSortedItems(items) {
var sortBy = this.model.sortBy;
var sortOrder = this.model.sortOrder;
return items.slice().sort(function(a, b) {
var aValue = a[sortBy];
var bValue = b[sortBy];
// 处理不同数据类型
if (sortBy === 'dueDate') {
aValue = new Date(aValue);
bValue = new Date(bValue);
} else if (sortBy === 'priority') {
var priorityOrder = { 'high': 3, 'medium': 2, 'low': 1 };
aValue = priorityOrder[aValue] || 0;
bValue = priorityOrder[bValue] || 0;
}
var result;
if (aValue < bValue) {
result = -1;
} else if (aValue > bValue) {
result = 1;
} else {
result = 0;
}
return sortOrder === 'desc' ? -result : result;
});
}
function addItem(itemData) {
var newItem = Object.assign({
id: Date.now(),
completed: false,
priority: 'medium',
dueDate: new Date().toISOString().split('T')[0]
}, itemData);
this.model.items.push(newItem);
this.render();
stdout.println('添加项目: ' + newItem.name);
return newItem;
}
function updateItem(itemId, updates) {
var item = this.model.items.find(item => item.id === itemId);
if (item) {
Object.assign(item, updates);
this.render();
stdout.println('更新项目: ' + item.name);
}
}
function deleteItem(itemId) {
var index = this.model.items.findIndex(item => item.id === itemId);
if (index >= 0) {
var item = this.model.items[index];
this.model.items.splice(index, 1);
this.render();
stdout.println('删除项目: ' + item.name);
}
}
function toggleItemCompleted(itemId, completed) {
this.updateItem(itemId, { completed: completed });
}
function editItem(itemId) {
var item = this.model.items.find(item => item.id === itemId);
if (item) {
this.showEditDialog(item);
}
}
function showEditDialog(item) {
// 创建编辑对话框
var dialog = Element.create('div', {
class: 'edit-dialog',
html: `
<div class="dialog-content">
<h3>编辑任务</h3>
<form class="edit-form">
<div class="form-group">
<label>任务名称:</label>
<input type="text" name="name" value="${item.name}" />
</div>
<div class="form-group">
<label>优先级:</label>
<select name="priority">
<option value="low" ${item.priority === 'low' ? 'selected' : ''}>低</option>
<option value="medium" ${item.priority === 'medium' ? 'selected' : ''}>中</option>
<option value="high" ${item.priority === 'high' ? 'selected' : ''}>高</option>
</select>
</div>
<div class="form-group">
<label>截止日期:</label>
<input type="date" name="dueDate" value="${item.dueDate}" />
</div>
<div class="form-actions">
<button type="submit">保存</button>
<button type="button" class="cancel">取消</button>
</div>
</form>
</div>
`
});
document.body.append(dialog);
var self = this;
var form = dialog.$('.edit-form');
// 处理表单提交
form.on('submit', function(evt) {
evt.preventDefault();
var formData = new FormData(this);
var updates = {
name: formData.get('name'),
priority: formData.get('priority'),
dueDate: formData.get('dueDate')
};
self.updateItem(item.id, updates);
dialog.remove();
});
// 处理取消
dialog.$('.cancel').on('click', function() {
dialog.remove();
});
// 点击外部关闭
dialog.on('click', function(evt) {
if (evt.target === this) {
this.remove();
}
});
}
function setFilter(filter) {
this.model.filter = filter;
this.render();
stdout.println('设置过滤器: ' + filter);
}
function setSorting(sortBy, sortOrder) {
this.model.sortBy = sortBy;
this.model.sortOrder = sortOrder;
this.render();
stdout.println(`设置排序: ${sortBy} ${sortOrder}`);
}
// 辅助方法
function formatDate(dateString) {
var date = new Date(dateString);
return date.toLocaleDateString('zh-CN');
}
function getPriorityText(priority) {
switch (priority) {
case 'high': return '高';
case 'medium': return '中';
case 'low': return '低';
default: return '未知';
}
}
// 获取统计信息
function getStats() {
var total = this.model.items.length;
var completed = this.model.items.filter(item => item.completed).length;
var active = total - completed;
return { total, completed, active };
}
}
// 使用示例
function setupListBinding() {
var container = $('#task-list');
var template = $('#task-item-template');
var listBinding = new ListBinding(container, listModel, template);
// 设置控制按钮
setupListControls(listBinding);
return listBinding;
}
function setupListControls(listBinding) {
// 过滤按钮
$('#filter-all').on('click', function() {
listBinding.setFilter('all');
updateFilterButtons('all');
});
$('#filter-active').on('click', function() {
listBinding.setFilter('active');
updateFilterButtons('active');
});
$('#filter-completed').on('click', function() {
listBinding.setFilter('completed');
updateFilterButtons('completed');
});
// 排序控制
$('#sort-by').on('change', function() {
listBinding.setSorting(this.value, listModel.sortOrder);
});
$('#sort-order').on('change', function() {
listBinding.setSorting(listModel.sortBy, this.value);
});
// 添加任务
$('#add-task-form').on('submit', function(evt) {
evt.preventDefault();
var formData = new FormData(this);
var taskData = {
name: formData.get('name'),
priority: formData.get('priority') || 'medium',
dueDate: formData.get('dueDate') || new Date().toISOString().split('T')[0]
};
listBinding.addItem(taskData);
this.reset();
});
// 更新统计信息
function updateStats() {
var stats = listBinding.getStats();
$('#stats-total').innerText = stats.total;
$('#stats-completed').innerText = stats.completed;
$('#stats-active').innerText = stats.active;
}
// 定期更新统计
setInterval(updateStats, 1000);
updateStats();
}
function updateFilterButtons(activeFilter) {
$$('.filter-button').forEach(function(button) {
button.removeClass('active');
});
$('#filter-' + activeFilter).addClass('active');
}
// HTML模板
/*
<div class="task-manager">
<!-- 控制面板 -->
<div class="controls">
<form id="add-task-form">
<input type="text" name="name" placeholder="任务名称" required />
<select name="priority">
<option value="low">低优先级</option>
<option value="medium" selected>中优先级</option>
<option value="high">高优先级</option>
</select>
<input type="date" name="dueDate" />
<button type="submit">添加</button>
</form>
<div class="filters">
<button id="filter-all" class="filter-button active">全部</button>
<button id="filter-active" class="filter-button">进行中</button>
<button id="filter-completed" class="filter-button">已完成</button>
</div>
<div class="sorting">
<select id="sort-by">
<option value="name">按名称</option>
<option value="priority">按优先级</option>
<option value="dueDate">按日期</option>
</select>
<select id="sort-order">
<option value="asc">升序</option>
<option value="desc">降序</option>
</select>
</div>
<div class="stats">
<span>总计: <span id="stats-total">0</span></span>
<span>已完成: <span id="stats-completed">0</span></span>
<span>进行中: <span id="stats-active">0</span></span>
</div>
</div>
<!-- 任务列表 -->
<div id="task-list" class="task-list"></div>
<!-- 任务项模板 -->
<div id="task-item-template" class="list-item" style="display: none;">
<input type="checkbox" class="item-checkbox" />
<div class="item-content">
<div class="item-name"></div>
<div class="item-meta">
<span class="item-priority"></span>
<span class="item-due-date"></span>
</div>
</div>
<div class="item-actions">
<button class="item-edit">编辑</button>
<button class="item-delete">删除</button>
</div>
</div>
</div>
*/
6.4 状态管理
集中式状态管理
1. 状态管理器实现
// 状态管理器
class StateManager {
function this(initialState = {}) {
this.state = this.deepClone(initialState);
this.listeners = [];
this.middleware = [];
this.history = [];
this.historyIndex = -1;
this.maxHistorySize = 50;
// 保存初始状态
this.saveToHistory(this.state, 'INIT');
}
// 获取当前状态
function getState() {
return this.deepClone(this.state);
}
// 获取状态的某个部分
function getStateSlice(path) {
var parts = path.split('.');
var value = this.state;
for (var part of parts) {
value = value && value[part];
}
return this.deepClone(value);
}
// 设置状态
function setState(newState, actionType = 'SET_STATE') {
var oldState = this.deepClone(this.state);
// 应用中间件
var action = {
type: actionType,
payload: newState,
oldState: oldState,
newState: newState
};
for (var middleware of this.middleware) {
action = middleware(action) || action;
}
// 更新状态
this.state = this.deepClone(action.newState);
// 保存到历史
this.saveToHistory(this.state, actionType);
// 通知监听器
this.notifyListeners(oldState, this.state, actionType);
stdout.println(`状态更新: ${actionType}`);
}
// 更新状态的某个部分
function updateState(path, value, actionType = 'UPDATE_STATE') {
var newState = this.deepClone(this.state);
this.setNestedValue(newState, path, value);
this.setState(newState, actionType);
}
// 合并状态
function mergeState(updates, actionType = 'MERGE_STATE') {
var newState = this.deepMerge(this.state, updates);
this.setState(newState, actionType);
}
// 添加监听器
function subscribe(listener, path = null) {
var subscription = {
id: Date.now() + Math.random(),
listener: listener,
path: path
};
this.listeners.push(subscription);
// 返回取消订阅函数
var self = this;
return function unsubscribe() {
var index = self.listeners.findIndex(l => l.id === subscription.id);
if (index >= 0) {
self.listeners.splice(index, 1);
}
};
}
// 通知监听器
function notifyListeners(oldState, newState, actionType) {
for (var subscription of this.listeners) {
try {
if (subscription.path) {
// 检查指定路径是否发生变化
var oldValue = this.getNestedValue(oldState, subscription.path);
var newValue = this.getNestedValue(newState, subscription.path);
if (!this.deepEqual(oldValue, newValue)) {
subscription.listener(newValue, oldValue, actionType);
}
} else {
// 监听整个状态
subscription.listener(newState, oldState, actionType);
}
} catch (error) {
stderr.println(`监听器错误: ${error.message}`);
}
}
}
// 添加中间件
function use(middleware) {
this.middleware.push(middleware);
}
// 撤销
function undo() {
if (this.historyIndex > 0) {
this.historyIndex--;
this.state = this.deepClone(this.history[this.historyIndex].state);
this.notifyListeners({}, this.state, 'UNDO');
stdout.println('撤销操作');
return true;
}
return false;
}
// 重做
function redo() {
if (this.historyIndex < this.history.length - 1) {
this.historyIndex++;
this.state = this.deepClone(this.history[this.historyIndex].state);
this.notifyListeners({}, this.state, 'REDO');
stdout.println('重做操作');
return true;
}
return false;
}
// 保存到历史
function saveToHistory(state, actionType) {
// 移除当前位置之后的历史
this.history = this.history.slice(0, this.historyIndex + 1);
// 添加新状态
this.history.push({
state: this.deepClone(state),
actionType: actionType,
timestamp: Date.now()
});
// 限制历史大小
if (this.history.length > this.maxHistorySize) {
this.history.shift();
} else {
this.historyIndex++;
}
}
// 获取历史信息
function getHistory() {
return this.history.map(function(entry, index) {
return {
index: index,
actionType: entry.actionType,
timestamp: entry.timestamp,
isCurrent: index === this.historyIndex
};
}.bind(this));
}
// 跳转到历史状态
function jumpToHistory(index) {
if (index >= 0 && index < this.history.length) {
this.historyIndex = index;
this.state = this.deepClone(this.history[index].state);
this.notifyListeners({}, this.state, 'JUMP_TO_HISTORY');
stdout.println(`跳转到历史状态: ${index}`);
return true;
}
return false;
}
// 重置状态
function reset() {
if (this.history.length > 0) {
this.jumpToHistory(0);
}
}
// 清除历史
function clearHistory() {
this.history = [{
state: this.deepClone(this.state),
actionType: 'CLEAR_HISTORY',
timestamp: Date.now()
}];
this.historyIndex = 0;
}
// 辅助方法
function 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));
}
var cloned = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = this.deepClone(obj[key]);
}
}
return cloned;
}
function deepEqual(a, b) {
if (a === b) return true;
if (a === null || b === null) return false;
if (typeof a !== typeof b) return false;
if (typeof a === 'object') {
if (Array.isArray(a) !== Array.isArray(b)) return false;
var keysA = Object.keys(a);
var keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (var key of keysA) {
if (!keysB.includes(key)) return false;
if (!this.deepEqual(a[key], b[key])) return false;
}
return true;
}
return false;
}
function deepMerge(target, source) {
var result = this.deepClone(target);
for (var key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
result[key] = this.deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
}
return result;
}
function getNestedValue(obj, path) {
var parts = path.split('.');
var value = obj;
for (var part of parts) {
value = value && value[part];
}
return value;
}
function setNestedValue(obj, path, value) {
var parts = path.split('.');
var current = obj;
for (var i = 0; i < parts.length - 1; i++) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
current[parts[parts.length - 1]] = value;
}
}
// 创建全局状态管理器
var appState = new StateManager({
user: {
id: null,
name: '',
email: '',
isLoggedIn: false,
preferences: {
theme: 'light',
language: 'zh-CN',
notifications: true
}
},
ui: {
loading: false,
sidebarOpen: true,
currentPage: 'dashboard',
modal: {
isOpen: false,
type: null,
data: null
}
},
data: {
tasks: [],
projects: [],
notifications: []
}
});
// 添加日志中间件
appState.use(function(action) {
stdout.println(`[StateManager] ${action.type}: ${JSON.stringify(action.payload)}`);
return action;
});
// 添加验证中间件
appState.use(function(action) {
// 验证用户状态
if (action.newState.user && action.newState.user.email) {
var emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(action.newState.user.email)) {
stderr.println('无效的邮箱地址');
// 可以阻止状态更新
// return null;
}
}
return action;
});
// 状态管理操作
namespace StateActions {
// 用户操作
function loginUser(userData) {
appState.mergeState({
user: {
id: userData.id,
name: userData.name,
email: userData.email,
isLoggedIn: true
}
}, 'LOGIN_USER');
}
function logoutUser() {
appState.updateState('user', {
id: null,
name: '',
email: '',
isLoggedIn: false
}, 'LOGOUT_USER');
}
function updateUserPreferences(preferences) {
appState.updateState('user.preferences', preferences, 'UPDATE_PREFERENCES');
}
// UI操作
function setLoading(loading) {
appState.updateState('ui.loading', loading, 'SET_LOADING');
}
function toggleSidebar() {
var currentState = appState.getStateSlice('ui.sidebarOpen');
appState.updateState('ui.sidebarOpen', !currentState, 'TOGGLE_SIDEBAR');
}
function navigateTo(page) {
appState.updateState('ui.currentPage', page, 'NAVIGATE_TO');
}
function openModal(type, data) {
appState.updateState('ui.modal', {
isOpen: true,
type: type,
data: data
}, 'OPEN_MODAL');
}
function closeModal() {
appState.updateState('ui.modal', {
isOpen: false,
type: null,
data: null
}, 'CLOSE_MODAL');
}
// 数据操作
function addTask(task) {
var tasks = appState.getStateSlice('data.tasks') || [];
tasks.push(Object.assign({
id: Date.now(),
createdAt: new Date().toISOString()
}, task));
appState.updateState('data.tasks', tasks, 'ADD_TASK');
}
function updateTask(taskId, updates) {
var tasks = appState.getStateSlice('data.tasks') || [];
var taskIndex = tasks.findIndex(task => task.id === taskId);
if (taskIndex >= 0) {
tasks[taskIndex] = Object.assign(tasks[taskIndex], updates);
appState.updateState('data.tasks', tasks, 'UPDATE_TASK');
}
}
function deleteTask(taskId) {
var tasks = appState.getStateSlice('data.tasks') || [];
var filteredTasks = tasks.filter(task => task.id !== taskId);
appState.updateState('data.tasks', filteredTasks, 'DELETE_TASK');
}
function addNotification(notification) {
var notifications = appState.getStateSlice('data.notifications') || [];
notifications.unshift(Object.assign({
id: Date.now(),
timestamp: new Date().toISOString(),
read: false
}, notification));
// 限制通知数量
if (notifications.length > 50) {
notifications = notifications.slice(0, 50);
}
appState.updateState('data.notifications', notifications, 'ADD_NOTIFICATION');
}
// 导出函数
self.loginUser = loginUser;
self.logoutUser = logoutUser;
self.updateUserPreferences = updateUserPreferences;
self.setLoading = setLoading;
self.toggleSidebar = toggleSidebar;
self.navigateTo = navigateTo;
self.openModal = openModal;
self.closeModal = closeModal;
self.addTask = addTask;
self.updateTask = updateTask;
self.deleteTask = deleteTask;
self.addNotification = addNotification;
}
// 状态订阅示例
function setupStateSubscriptions() {
// 监听用户登录状态
appState.subscribe(function(isLoggedIn, oldValue) {
stdout.println(`用户登录状态改变: ${oldValue} -> ${isLoggedIn}`);
if (isLoggedIn) {
// 用户登录后的操作
loadUserData();
showWelcomeMessage();
} else {
// 用户登出后的操作
clearUserData();
redirectToLogin();
}
}, 'user.isLoggedIn');
// 监听主题变化
appState.subscribe(function(theme) {
stdout.println(`主题改变: ${theme}`);
applyTheme(theme);
}, 'user.preferences.theme');
// 监听加载状态
appState.subscribe(function(loading) {
var loadingElement = $('#loading-indicator');
if (loadingElement) {
loadingElement.style.display = loading ? 'block' : 'none';
}
}, 'ui.loading');
// 监听侧边栏状态
appState.subscribe(function(sidebarOpen) {
var sidebar = $('#sidebar');
var content = $('#main-content');
if (sidebar) {
sidebar.style.display = sidebarOpen ? 'block' : 'none';
}
if (content) {
content.style.marginLeft = sidebarOpen ? '250px' : '0';
}
}, 'ui.sidebarOpen');
// 监听模态框状态
appState.subscribe(function(modal) {
if (modal.isOpen) {
showModal(modal.type, modal.data);
} else {
hideModal();
}
}, 'ui.modal');
// 监听任务列表变化
appState.subscribe(function(tasks) {
stdout.println(`任务列表更新: ${tasks.length} 个任务`);
updateTaskList(tasks);
}, 'data.tasks');
// 监听通知变化
appState.subscribe(function(notifications) {
var unreadCount = notifications.filter(n => !n.read).length;
updateNotificationBadge(unreadCount);
// 显示最新通知
if (notifications.length > 0 && !notifications[0].read) {
showNotificationToast(notifications[0]);
}
}, 'data.notifications');
}
// 辅助函数
function loadUserData() {
StateActions.setLoading(true);
// 模拟异步加载
setTimeout(function() {
StateActions.addTask({
name: '欢迎任务',
description: '欢迎使用我们的应用!',
completed: false
});
StateActions.addNotification({
type: 'welcome',
title: '欢迎',
message: '欢迎回来!'
});
StateActions.setLoading(false);
}, 1000);
}
function clearUserData() {
appState.updateState('data', {
tasks: [],
projects: [],
notifications: []
}, 'CLEAR_USER_DATA');
}
function applyTheme(theme) {
document.body.className = document.body.className.replace(/theme-\w+/g, '');
document.body.addClass('theme-' + theme);
}
function showModal(type, data) {
var modal = $('#modal');
if (modal) {
modal.style.display = 'block';
// 根据类型显示不同内容
}
function hideModal() {
var modal = $('#modal');
if (modal) {
modal.style.display = 'none';
}
}
function updateTaskList(tasks) {
var taskList = $('#task-list');
if (taskList) {
// 重新渲染任务列表
taskList.innerHTML = '';
for (var task of tasks) {
var taskElement = Element.create('div', {
class: 'task-item',
html: `
<input type="checkbox" ${task.completed ? 'checked' : ''}
onchange="StateActions.updateTask(${task.id}, {completed: this.checked})" />
<span class="task-name ${task.completed ? 'completed' : ''}">${task.name}</span>
<button onclick="StateActions.deleteTask(${task.id})">删除</button>
`
});
taskList.append(taskElement);
}
}
}
function updateNotificationBadge(count) {
var badge = $('#notification-badge');
if (badge) {
badge.innerText = count > 0 ? count.toString() : '';
badge.style.display = count > 0 ? 'inline' : 'none';
}
}
function showNotificationToast(notification) {
var toast = Element.create('div', {
class: 'notification-toast',
html: `
<div class="toast-title">${notification.title}</div>
<div class="toast-message">${notification.message}</div>
`
});
document.body.append(toast);
// 自动隐藏
setTimeout(function() {
toast.remove();
}, 3000);
}
function showWelcomeMessage() {
StateActions.addNotification({
type: 'info',
title: '欢迎',
message: '欢迎使用Sciter应用!'
});
}
function redirectToLogin() {
StateActions.navigateTo('login');
}
// 初始化状态管理
function initializeStateManagement() {
setupStateSubscriptions();
// 模拟用户登录
setTimeout(function() {
StateActions.loginUser({
id: 1,
name: '张三',
email: 'zhangsan@example.com'
});
}, 1000);
}
// 状态调试工具
namespace StateDebugger {
function logCurrentState() {
stdout.println('当前状态:');
stdout.println(JSON.stringify(appState.getState(), null, 2));
}
function logHistory() {
stdout.println('状态历史:');
var history = appState.getHistory();
for (var entry of history) {
stdout.println(`${entry.index}: ${entry.actionType} ${entry.isCurrent ? '(当前)' : ''}`);
}
}
function exportState() {
return JSON.stringify(appState.getState());
}
function importState(stateJson) {
try {
var state = JSON.parse(stateJson);
appState.setState(state, 'IMPORT_STATE');
stdout.println('状态导入成功');
} catch (error) {
stderr.println('状态导入失败: ' + error.message);
}
}
// 导出函数
self.logCurrentState = logCurrentState;
self.logHistory = logHistory;
self.exportState = exportState;
self.importState = importState;
}
6.5 响应式数据绑定
观察者模式实现
// 响应式数据系统
class ReactiveData {
function this(data = {}) {
this.data = {};
this.observers = new Map();
this.computedCache = new Map();
this.computedDeps = new Map();
this.makeReactive(data);
}
function makeReactive(obj, path = '') {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var fullPath = path ? `${path}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null) {
this.data[key] = {};
this.makeReactive(obj[key], fullPath);
} else {
this.defineReactiveProperty(obj, key, obj[key], fullPath);
}
}
}
}
function defineReactiveProperty(obj, key, value, path) {
var self = this;
var internalValue = value;
Object.defineProperty(this.data, key, {
get: function() {
// 收集依赖
if (self.currentComputed) {
self.addDependency(self.currentComputed, path);
}
return internalValue;
},
set: function(newValue) {
if (newValue !== internalValue) {
var oldValue = internalValue;
internalValue = newValue;
// 通知观察者
self.notify(path, newValue, oldValue);
// 更新计算属性
self.updateComputedProperties(path);
}
},
enumerable: true,
configurable: true
});
}
function watch(path, callback) {
if (!this.observers.has(path)) {
this.observers.set(path, []);
}
this.observers.get(path).push(callback);
// 返回取消监听函数
var self = this;
return function unwatch() {
var callbacks = self.observers.get(path);
if (callbacks) {
var index = callbacks.indexOf(callback);
if (index >= 0) {
callbacks.splice(index, 1);
}
}
};
}
function notify(path, newValue, oldValue) {
var callbacks = this.observers.get(path);
if (callbacks) {
for (var callback of callbacks) {
try {
callback(newValue, oldValue, path);
} catch (error) {
stderr.println(`观察者回调错误: ${error.message}`);
}
}
}
}
function computed(name, computeFn) {
var self = this;
// 计算初始值并收集依赖
this.currentComputed = name;
var value = computeFn.call(this.data);
this.currentComputed = null;
this.computedCache.set(name, value);
// 定义计算属性
Object.defineProperty(this.data, name, {
get: function() {
return self.computedCache.get(name);
},
enumerable: true,
configurable: true
});
return value;
}
function addDependency(computedName, path) {
if (!this.computedDeps.has(computedName)) {
this.computedDeps.set(computedName, new Set());
}
this.computedDeps.get(computedName).add(path);
}
function updateComputedProperties(changedPath) {
for (var [computedName, deps] of this.computedDeps) {
if (deps.has(changedPath)) {
this.recomputeProperty(computedName);
}
}
}
function recomputeProperty(name) {
// 这里需要重新计算,但需要保存原始计算函数
// 简化实现,实际应用中需要更复杂的依赖追踪
stdout.println(`重新计算属性: ${name}`);
}
function get(path) {
var parts = path.split('.');
var value = this.data;
for (var part of parts) {
value = value && value[part];
}
return value;
}
function set(path, value) {
var parts = path.split('.');
var obj = this.data;
for (var i = 0; i < parts.length - 1; i++) {
if (!obj[parts[i]]) {
obj[parts[i]] = {};
}
obj = obj[parts[i]];
}
obj[parts[parts.length - 1]] = value;
}
}
// 使用示例
var reactiveData = new ReactiveData({
user: {
firstName: 'John',
lastName: 'Doe',
age: 30
},
settings: {
theme: 'light',
language: 'en'
}
});
// 监听数据变化
reactiveData.watch('user.firstName', function(newValue, oldValue) {
stdout.println(`名字改变: ${oldValue} -> ${newValue}`);
});
// 计算属性
reactiveData.computed('user.fullName', function() {
return this.user.firstName + ' ' + this.user.lastName;
});
// 测试响应式
reactiveData.set('user.firstName', 'Jane');
stdout.println('全名: ' + reactiveData.get('user.fullName'));
6.6 本章总结
核心要点
数据绑定机制
- 双向数据绑定的实现原理
- 绑定语法和表达式支持
- 条件绑定和循环绑定
- 事件绑定和方法调用
表单数据绑定
- 表单元素的数据绑定
- 实时验证和错误处理
- 复杂表单的状态管理
- 表单提交和重置
列表数据绑定
- 动态列表渲染
- 列表项的增删改查
- 过滤和排序功能
- 列表性能优化
状态管理
- 集中式状态管理器
- 状态订阅和通知机制
- 中间件和插件系统
- 状态历史和时间旅行
响应式数据
- 观察者模式实现
- 计算属性和依赖追踪
- 响应式数据的性能优化
- 深度监听和批量更新
最佳实践
数据绑定设计
- 保持数据模型的简洁性
- 避免过深的嵌套结构
- 使用计算属性处理复杂逻辑
- 合理使用条件绑定
状态管理策略
- 按功能模块组织状态
- 使用不可变数据更新
- 实现状态的序列化和恢复
- 添加状态变化的日志记录
性能优化
- 避免频繁的DOM更新
- 使用虚拟滚动处理大列表
- 实现智能的依赖收集
- 批量处理状态更新
调试和测试
- 提供状态调试工具
- 实现状态快照功能
- 编写状态变化的单元测试
- 监控状态管理的性能
练习题
基础练习
- 创建一个用户信息编辑表单,实现双向数据绑定
- 实现一个待办事项列表,支持添加、删除、完成状态切换
- 创建一个购物车组件,计算总价和商品数量
进阶练习
- 实现一个表格组件,支持排序、过滤、分页功能
- 创建一个多步骤表单向导,管理各步骤的状态
- 实现一个聊天应用的消息列表,支持实时更新
高级练习
- 设计一个可撤销/重做的绘图应用
- 实现一个协作编辑器的状态同步机制
- 创建一个复杂的数据可视化仪表板
下一章预告
下一章我们将学习组件化开发,包括: - 组件的设计和实现 - 组件间的通信机制 - 组件的生命周期管理 - 可复用组件库的构建 - 组件的测试和文档