6.1 数据绑定概述
6.1.1 什么是数据绑定
数据绑定是现代前端框架的核心特性,它建立了数据模型与视图之间的自动同步机制。在 Sciter-JS 中,我们可以实现强大的数据绑定系统。
graph TB
A[数据模型] <--> B[数据绑定层]
B <--> C[视图层]
D[用户交互] --> C
C --> E[事件处理]
E --> A
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e8
6.1.2 数据绑定类型
// 单向数据绑定
class OneWayBinding {
constructor(source, target, property) {
this.source = source;
this.target = target;
this.property = property;
this.setupBinding();
}
setupBinding() {
// 监听源数据变化
Object.defineProperty(this.source, this.property, {
get() {
return this._value;
},
set(newValue) {
this._value = newValue;
this.updateTarget(newValue);
}
});
}
updateTarget(value) {
if (this.target.tagName === 'INPUT') {
this.target.value = value;
} else {
this.target.textContent = value;
}
}
}
// 双向数据绑定
class TwoWayBinding extends OneWayBinding {
setupBinding() {
super.setupBinding();
this.setupTargetListener();
}
setupTargetListener() {
if (this.target.tagName === 'INPUT') {
this.target.addEventListener('input', (event) => {
this.source[this.property] = event.target.value;
});
}
}
}
6.2 响应式数据系统
6.2.1 响应式对象实现
// 响应式系统核心
class ReactiveSystem {
constructor() {
this.observers = new Map();
this.currentObserver = null;
}
// 创建响应式对象
reactive(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
return new Proxy(obj, {
get: (target, key) => {
this.track(target, key);
const value = target[key];
return typeof value === 'object' ? this.reactive(value) : value;
},
set: (target, key, value) => {
const oldValue = target[key];
target[key] = value;
if (oldValue !== value) {
this.trigger(target, key);
}
return true;
}
});
}
// 依赖收集
track(target, key) {
if (!this.currentObserver) return;
const targetMap = this.observers.get(target) || new Map();
const deps = targetMap.get(key) || new Set();
deps.add(this.currentObserver);
targetMap.set(key, deps);
this.observers.set(target, targetMap);
}
// 触发更新
trigger(target, key) {
const targetMap = this.observers.get(target);
if (!targetMap) return;
const deps = targetMap.get(key);
if (deps) {
deps.forEach(observer => observer());
}
}
// 监听器
watch(fn) {
this.currentObserver = fn;
fn();
this.currentObserver = null;
}
// 计算属性
computed(fn) {
let value;
let dirty = true;
const computedFn = () => {
if (dirty) {
value = fn();
dirty = false;
}
return value;
};
this.watch(() => {
dirty = true;
});
return computedFn;
}
}
// 全局响应式系统实例
const reactiveSystem = new ReactiveSystem();
6.2.2 响应式数据使用示例
// 创建响应式数据
const state = reactiveSystem.reactive({
user: {
name: 'John',
age: 25,
email: 'john@example.com'
},
todos: [
{ id: 1, text: '学习 Sciter-JS', completed: false },
{ id: 2, text: '构建应用', completed: true }
],
filter: 'all'
});
// 计算属性
const filteredTodos = reactiveSystem.computed(() => {
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;
}
});
// 监听数据变化
reactiveSystem.watch(() => {
console.log('用户名变化:', state.user.name);
});
reactiveSystem.watch(() => {
console.log('过滤后的 todos:', filteredTodos());
});
// 数据变化会自动触发监听器
state.user.name = 'Jane'; // 输出: 用户名变化: Jane
state.filter = 'active'; // 输出: 过滤后的 todos: [...]
6.3 数据绑定指令系统
6.3.1 指令解析器
// 指令解析器
class DirectiveParser {
constructor(reactiveSystem) {
this.reactiveSystem = reactiveSystem;
this.directives = new Map();
this.setupBuiltinDirectives();
}
// 注册指令
register(name, handler) {
this.directives.set(name, handler);
}
// 解析元素上的指令
parse(element, context) {
const attributes = Array.from(element.attributes);
attributes.forEach(attr => {
if (attr.name.startsWith('v-')) {
const directiveName = attr.name.substring(2);
const expression = attr.value;
this.applyDirective(element, directiveName, expression, context);
}
});
// 递归处理子元素
Array.from(element.children).forEach(child => {
this.parse(child, context);
});
}
// 应用指令
applyDirective(element, name, expression, context) {
const directive = this.directives.get(name);
if (directive) {
directive(element, expression, context, this.reactiveSystem);
}
}
// 内置指令
setupBuiltinDirectives() {
// v-text 指令
this.register('text', (element, expression, context, reactive) => {
reactive.watch(() => {
const value = this.evaluateExpression(expression, context);
element.textContent = value;
});
});
// v-html 指令
this.register('html', (element, expression, context, reactive) => {
reactive.watch(() => {
const value = this.evaluateExpression(expression, context);
element.innerHTML = value;
});
});
// v-model 指令
this.register('model', (element, expression, context, reactive) => {
// 双向绑定
reactive.watch(() => {
const value = this.evaluateExpression(expression, context);
if (element.value !== value) {
element.value = value;
}
});
element.addEventListener('input', (event) => {
this.setProperty(expression, context, event.target.value);
});
});
// v-show 指令
this.register('show', (element, expression, context, reactive) => {
reactive.watch(() => {
const value = this.evaluateExpression(expression, context);
element.style.display = value ? '' : 'none';
});
});
// v-if 指令
this.register('if', (element, expression, context, reactive) => {
const placeholder = document.createComment(`v-if: ${expression}`);
element.parentNode.insertBefore(placeholder, element);
reactive.watch(() => {
const value = this.evaluateExpression(expression, context);
if (value) {
if (!element.parentNode) {
placeholder.parentNode.insertBefore(element, placeholder.nextSibling);
}
} else {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
}
});
});
// v-for 指令
this.register('for', (element, expression, context, reactive) => {
const [itemName, listExpression] = expression.split(' in ');
const placeholder = document.createComment(`v-for: ${expression}`);
element.parentNode.insertBefore(placeholder, element);
element.parentNode.removeChild(element);
let renderedElements = [];
reactive.watch(() => {
const list = this.evaluateExpression(listExpression.trim(), context);
// 清除之前渲染的元素
renderedElements.forEach(el => {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
});
renderedElements = [];
// 渲染新元素
if (Array.isArray(list)) {
list.forEach((item, index) => {
const clonedElement = element.cloneNode(true);
const itemContext = {
...context,
[itemName.trim()]: item,
$index: index
};
placeholder.parentNode.insertBefore(clonedElement, placeholder.nextSibling);
this.parse(clonedElement, itemContext);
renderedElements.push(clonedElement);
});
}
});
});
// v-on 指令(事件绑定)
this.register('on', (element, expression, context, reactive) => {
const [eventName, handlerExpression] = expression.split(':');
element.addEventListener(eventName, (event) => {
const handler = this.evaluateExpression(handlerExpression, context);
if (typeof handler === 'function') {
handler(event);
}
});
});
}
// 表达式求值
evaluateExpression(expression, context) {
try {
const func = new Function(...Object.keys(context), `return ${expression}`);
return func(...Object.values(context));
} catch (error) {
console.error('表达式求值错误:', expression, error);
return undefined;
}
}
// 设置属性值
setProperty(expression, context, value) {
try {
const func = new Function(...Object.keys(context), 'value', `${expression} = value`);
func(...Object.values(context), value);
} catch (error) {
console.error('属性设置错误:', expression, error);
}
}
}
6.3.2 数据绑定组件
// 数据绑定组件基类
class DataBindingComponent extends Component {
constructor(element, props = {}) {
super(element, props);
this.directiveParser = new DirectiveParser(reactiveSystem);
this.setupDataBinding();
}
setupDataBinding() {
// 创建响应式数据
this.data = reactiveSystem.reactive(this.getData());
// 设置计算属性
this.setupComputed();
// 设置监听器
this.setupWatchers();
// 解析模板指令
this.parseTemplate();
}
getData() {
return {};
}
setupComputed() {
// 子类可以重写此方法设置计算属性
}
setupWatchers() {
// 子类可以重写此方法设置监听器
}
parseTemplate() {
const context = {
...this.data,
...this.methods,
$props: this.props
};
this.directiveParser.parse(this.element, context);
}
get methods() {
return {};
}
}
6.4 全局状态管理
6.4.1 状态管理器设计
// 全局状态管理器
class StateManager {
constructor() {
this.state = reactiveSystem.reactive({});
this.mutations = new Map();
this.actions = new Map();
this.getters = new Map();
this.modules = new Map();
this.subscribers = new Set();
}
// 注册模块
registerModule(name, module) {
this.modules.set(name, module);
// 合并状态
if (module.state) {
this.state[name] = reactiveSystem.reactive(module.state);
}
// 注册 mutations
if (module.mutations) {
Object.entries(module.mutations).forEach(([key, mutation]) => {
this.mutations.set(`${name}/${key}`, mutation);
});
}
// 注册 actions
if (module.actions) {
Object.entries(module.actions).forEach(([key, action]) => {
this.actions.set(`${name}/${key}`, action);
});
}
// 注册 getters
if (module.getters) {
Object.entries(module.getters).forEach(([key, getter]) => {
this.getters.set(`${name}/${key}`, getter);
});
}
}
// 提交 mutation
commit(type, payload) {
const mutation = this.mutations.get(type);
if (mutation) {
mutation(this.state, payload);
this.notifySubscribers({ type: 'mutation', mutation: type, payload });
} else {
console.error(`未知的 mutation: ${type}`);
}
}
// 分发 action
async dispatch(type, payload) {
const action = this.actions.get(type);
if (action) {
const context = {
state: this.state,
commit: this.commit.bind(this),
dispatch: this.dispatch.bind(this),
getters: this.getGetters()
};
const result = await action(context, payload);
this.notifySubscribers({ type: 'action', action: type, payload });
return result;
} else {
console.error(`未知的 action: ${type}`);
}
}
// 获取 getter 值
getGetters() {
const getters = {};
this.getters.forEach((getter, key) => {
Object.defineProperty(getters, key, {
get: () => getter(this.state, getters)
});
});
return getters;
}
// 订阅状态变化
subscribe(callback) {
this.subscribers.add(callback);
// 返回取消订阅函数
return () => {
this.subscribers.delete(callback);
};
}
// 通知订阅者
notifySubscribers(event) {
this.subscribers.forEach(callback => {
try {
callback(event);
} catch (error) {
console.error('订阅者回调错误:', error);
}
});
}
// 获取状态快照
getSnapshot() {
return JSON.parse(JSON.stringify(this.state));
}
// 恢复状态
restoreSnapshot(snapshot) {
Object.assign(this.state, snapshot);
}
}
// 全局状态管理器实例
const store = new StateManager();
6.4.2 状态模块示例
// 用户模块
const userModule = {
state: {
currentUser: null,
isLoggedIn: false,
permissions: []
},
mutations: {
SET_USER(state, user) {
state.user.currentUser = user;
state.user.isLoggedIn = !!user;
},
SET_PERMISSIONS(state, permissions) {
state.user.permissions = permissions;
},
LOGOUT(state) {
state.user.currentUser = null;
state.user.isLoggedIn = false;
state.user.permissions = [];
}
},
actions: {
async login({ commit }, credentials) {
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
const user = await response.json();
commit('user/SET_USER', user);
commit('user/SET_PERMISSIONS', user.permissions);
return user;
} catch (error) {
console.error('登录失败:', error);
throw error;
}
},
async logout({ commit }) {
try {
await fetch('/api/logout', { method: 'POST' });
commit('user/LOGOUT');
} catch (error) {
console.error('登出失败:', error);
}
}
},
getters: {
isAdmin: (state) => {
return state.user.permissions.includes('admin');
},
userName: (state) => {
return state.user.currentUser?.name || '游客';
}
}
};
// Todo 模块
const todoModule = {
state: {
todos: [],
filter: 'all'
},
mutations: {
ADD_TODO(state, todo) {
state.todos.todos.push({
id: Date.now(),
text: todo.text,
completed: false,
createdAt: new Date()
});
},
TOGGLE_TODO(state, id) {
const todo = state.todos.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
},
DELETE_TODO(state, id) {
const index = state.todos.todos.findIndex(t => t.id === id);
if (index > -1) {
state.todos.todos.splice(index, 1);
}
},
SET_FILTER(state, filter) {
state.todos.filter = filter;
}
},
actions: {
async loadTodos({ commit }) {
try {
const response = await fetch('/api/todos');
const todos = await response.json();
todos.forEach(todo => {
commit('todos/ADD_TODO', todo);
});
} catch (error) {
console.error('加载 todos 失败:', error);
}
},
async saveTodo({ commit }, todo) {
try {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(todo)
});
const savedTodo = await response.json();
commit('todos/ADD_TODO', savedTodo);
return savedTodo;
} catch (error) {
console.error('保存 todo 失败:', error);
throw error;
}
}
},
getters: {
filteredTodos: (state) => {
switch (state.todos.filter) {
case 'active':
return state.todos.todos.filter(todo => !todo.completed);
case 'completed':
return state.todos.todos.filter(todo => todo.completed);
default:
return state.todos.todos;
}
},
todoStats: (state) => {
const total = state.todos.todos.length;
const completed = state.todos.todos.filter(todo => todo.completed).length;
const active = total - completed;
return { total, completed, active };
}
}
};
// 注册模块
store.registerModule('user', userModule);
store.registerModule('todos', todoModule);
6.5 状态持久化
6.5.1 本地存储插件
// 状态持久化插件
class PersistencePlugin {
constructor(options = {}) {
this.key = options.key || 'sciter-app-state';
this.storage = options.storage || localStorage;
this.paths = options.paths || []; // 需要持久化的状态路径
this.serialize = options.serialize || JSON.stringify;
this.deserialize = options.deserialize || JSON.parse;
}
// 安装插件
install(store) {
// 恢复状态
this.restoreState(store);
// 订阅状态变化
store.subscribe((event) => {
this.saveState(store);
});
}
// 保存状态
saveState(store) {
try {
const state = store.getSnapshot();
const persistedState = this.filterState(state);
const serialized = this.serialize(persistedState);
this.storage.setItem(this.key, serialized);
} catch (error) {
console.error('状态保存失败:', error);
}
}
// 恢复状态
restoreState(store) {
try {
const serialized = this.storage.getItem(this.key);
if (serialized) {
const state = this.deserialize(serialized);
store.restoreSnapshot(state);
}
} catch (error) {
console.error('状态恢复失败:', error);
}
}
// 过滤需要持久化的状态
filterState(state) {
if (this.paths.length === 0) {
return state;
}
const filtered = {};
this.paths.forEach(path => {
const value = this.getNestedValue(state, path);
if (value !== undefined) {
this.setNestedValue(filtered, path, value);
}
});
return filtered;
}
// 获取嵌套值
getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => {
return current && current[key];
}, obj);
}
// 设置嵌套值
setNestedValue(obj, path, value) {
const keys = path.split('.');
const lastKey = keys.pop();
const target = keys.reduce((current, key) => {
if (!current[key]) {
current[key] = {};
}
return current[key];
}, obj);
target[lastKey] = value;
}
}
// 使用持久化插件
const persistencePlugin = new PersistencePlugin({
key: 'my-app-state',
paths: ['user.currentUser', 'todos.todos'], // 只持久化用户信息和 todos
storage: localStorage
});
persistencePlugin.install(store);
6.5.2 状态同步插件
// 状态同步插件(多标签页同步)
class StateSyncPlugin {
constructor(options = {}) {
this.channel = options.channel || 'state-sync';
this.debounceTime = options.debounceTime || 100;
this.syncTimeout = null;
}
install(store) {
// 监听其他标签页的状态变化
window.addEventListener('storage', (event) => {
if (event.key === this.channel && event.newValue) {
try {
const syncData = JSON.parse(event.newValue);
this.applySyncData(store, syncData);
} catch (error) {
console.error('状态同步失败:', error);
}
}
});
// 监听本地状态变化
store.subscribe((event) => {
this.scheduleBroadcast(store, event);
});
}
// 调度广播(防抖)
scheduleBroadcast(store, event) {
clearTimeout(this.syncTimeout);
this.syncTimeout = setTimeout(() => {
this.broadcastState(store, event);
}, this.debounceTime);
}
// 广播状态变化
broadcastState(store, event) {
try {
const syncData = {
timestamp: Date.now(),
event: event,
state: store.getSnapshot()
};
localStorage.setItem(this.channel, JSON.stringify(syncData));
// 立即清除,避免影响持久化
setTimeout(() => {
localStorage.removeItem(this.channel);
}, 50);
} catch (error) {
console.error('状态广播失败:', error);
}
}
// 应用同步数据
applySyncData(store, syncData) {
// 避免循环同步
if (Date.now() - syncData.timestamp > 5000) {
return;
}
store.restoreSnapshot(syncData.state);
}
}
// 使用状态同步插件
const stateSyncPlugin = new StateSyncPlugin({
channel: 'my-app-sync',
debounceTime: 200
});
stateSyncPlugin.install(store);
6.6 实践练习
6.6.1 创建一个完整的数据绑定应用
<!-- HTML 模板 -->
<div id="app">
<header class="app-header">
<h1 v-text="title"></h1>
<div class="user-info" v-if="user.isLoggedIn">
<span v-text="'欢迎, ' + user.name"></span>
<button v-on="click:logout">登出</button>
</div>
<div class="login-form" v-if="!user.isLoggedIn">
<input v-model="loginForm.username" placeholder="用户名">
<input v-model="loginForm.password" type="password" placeholder="密码">
<button v-on="click:login">登录</button>
</div>
</header>
<main class="app-main" v-show="user.isLoggedIn">
<div class="todo-input">
<input v-model="newTodoText" placeholder="添加新任务..."
v-on="keydown:handleKeyDown">
<button v-on="click:addTodo">添加</button>
</div>
<div class="todo-filters">
<button v-for="filter in filters"
v-on="click:setFilter"
v-text="filter.label"
:class="{ active: currentFilter === filter.value }">
</button>
</div>
<div class="todo-list">
<div v-for="todo in filteredTodos" class="todo-item">
<input type="checkbox" v-model="todo.completed">
<span v-text="todo.text"
:class="{ completed: todo.completed }"></span>
<button v-on="click:deleteTodo" :data-id="todo.id">删除</button>
</div>
</div>
<div class="todo-stats">
<span v-text="'总计: ' + todoStats.total"></span>
<span v-text="'未完成: ' + todoStats.active"></span>
<span v-text="'已完成: ' + todoStats.completed"></span>
</div>
</main>
</div>
// 应用组件
class TodoApp extends DataBindingComponent {
getData() {
return {
title: 'Todo 应用',
user: {
isLoggedIn: false,
name: '',
id: null
},
loginForm: {
username: '',
password: ''
},
todos: [],
newTodoText: '',
currentFilter: 'all',
filters: [
{ value: 'all', label: '全部' },
{ value: 'active', label: '未完成' },
{ value: 'completed', label: '已完成' }
]
};
}
setupComputed() {
// 过滤后的 todos
this.filteredTodos = reactiveSystem.computed(() => {
switch (this.data.currentFilter) {
case 'active':
return this.data.todos.filter(todo => !todo.completed);
case 'completed':
return this.data.todos.filter(todo => todo.completed);
default:
return this.data.todos;
}
});
// todo 统计
this.todoStats = reactiveSystem.computed(() => {
const total = this.data.todos.length;
const completed = this.data.todos.filter(todo => todo.completed).length;
const active = total - completed;
return { total, completed, active };
});
}
setupWatchers() {
// 监听用户登录状态
reactiveSystem.watch(() => {
if (this.data.user.isLoggedIn) {
this.loadTodos();
}
});
// 监听 todos 变化,自动保存
reactiveSystem.watch(() => {
if (this.data.user.isLoggedIn && this.data.todos.length > 0) {
this.saveTodos();
}
});
}
get methods() {
return {
login: this.login.bind(this),
logout: this.logout.bind(this),
addTodo: this.addTodo.bind(this),
deleteTodo: this.deleteTodo.bind(this),
setFilter: this.setFilter.bind(this),
handleKeyDown: this.handleKeyDown.bind(this),
filteredTodos: this.filteredTodos,
todoStats: this.todoStats
};
}
async login() {
try {
// 模拟登录 API 调用
const response = await this.mockLogin(
this.data.loginForm.username,
this.data.loginForm.password
);
this.data.user = {
isLoggedIn: true,
name: response.name,
id: response.id
};
// 清空登录表单
this.data.loginForm = { username: '', password: '' };
} catch (error) {
alert('登录失败: ' + error.message);
}
}
logout() {
this.data.user = {
isLoggedIn: false,
name: '',
id: null
};
this.data.todos = [];
}
addTodo() {
const text = this.data.newTodoText.trim();
if (text) {
this.data.todos.push({
id: Date.now(),
text: text,
completed: false,
createdAt: new Date()
});
this.data.newTodoText = '';
}
}
deleteTodo(event) {
const id = parseInt(event.target.dataset.id);
const index = this.data.todos.findIndex(todo => todo.id === id);
if (index > -1) {
this.data.todos.splice(index, 1);
}
}
setFilter(event) {
const filter = event.target.dataset.filter;
this.data.currentFilter = filter;
}
handleKeyDown(event) {
if (event.key === 'Enter') {
this.addTodo();
}
}
// 模拟 API 方法
async mockLogin(username, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (username === 'admin' && password === '123456') {
resolve({ id: 1, name: '管理员' });
} else {
reject(new Error('用户名或密码错误'));
}
}, 1000);
});
}
async loadTodos() {
// 模拟加载 todos
setTimeout(() => {
this.data.todos = [
{ id: 1, text: '学习 Sciter-JS', completed: false },
{ id: 2, text: '构建应用', completed: true }
];
}, 500);
}
async saveTodos() {
// 模拟保存 todos
console.log('保存 todos:', this.data.todos);
}
}
// 初始化应用
const appElement = document.getElementById('app');
const app = new TodoApp(appElement);
6.7 本章小结
6.7.1 核心概念
- 响应式系统:自动追踪依赖和触发更新的数据系统
- 数据绑定:数据与视图之间的自动同步机制
- 状态管理:集中式的应用状态管理方案
- 状态持久化:状态的本地存储和恢复
6.7.2 技术要点
- Proxy 响应式:使用 Proxy 实现数据劫持和依赖收集
- 指令系统:声明式的数据绑定指令
- 模块化状态:按功能模块组织状态管理
- 插件系统:可扩展的状态管理插件
6.7.3 最佳实践
- 合理设计状态结构,避免过度嵌套
- 使用计算属性处理派生数据
- 通过 mutations 修改状态,保持数据流的可预测性
- 适当使用状态持久化,提升用户体验
6.7.4 下一章预告
下一章将学习 网络请求与数据处理,包括: - HTTP 客户端封装 - 请求拦截和响应处理 - 数据缓存策略 - 错误处理和重试机制
通过本章的学习,你已经掌握了完整的数据绑定和状态管理技能,能够构建复杂的数据驱动应用。