1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
| <!DOCTYPE html>
<html>
<head>
<title>MVVM 示例</title>
<style>
.container { margin: 20px; }
.user-info { border: 1px solid #ccc; padding: 10px; margin-top: 10px; }
button { margin-top: 10px; padding: 5px 10px; cursor: pointer; }
.error { color: red; }
.loading { color: #666; }
</style>
</head>
<body>
<div class="container">
<h2>MVVM 架构示例(用户信息展示)</h2>
<!-- View:仅负责 UI 结构,通过“数据绑定”关联 ViewModel -->
<button id="loadUserBtn" :disabled="isLoading">加载用户信息</button>
<div class="user-info" v-if="hasUser">
<p>ID: {{ user.id }}</p>
<p>姓名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
<!-- 双向绑定示例:修改年龄后自动同步到 ViewModel -->
<p>修改年龄: <input type="number" v-model="user.age"></p>
</div>
<div class="loading" v-if="isLoading">加载中...</div>
<div class="error" v-if="hasError">{{ errorMsg }}</div>
</div>
<script>
// 1. Model:管理原始数据和业务逻辑(与 MVC/MVP 一致,不依赖其他层)
const UserModel = {
userData: null, // 存储原始用户数据
error: null, // 存储错误信息
// 模拟从后端获取用户数据(异步操作)
fetchUser: function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 模拟后端返回的原始数据
const mockRawData = { id: 1, name: "张三", age: 30 };
this.userData = mockRawData; // 存储原始数据
this.error = null;
resolve(mockRawData); // 成功时返回数据
} catch (err) {
this.error = "数据加载失败";
reject(new Error(this.error)); // 失败时返回错误
}
}, 800); // 模拟网络延迟
});
},
// 更新用户年龄(业务逻辑:年龄不能小于 0)
updateUserAge: function (newAge) {
if (newAge < 0) {
this.error = "年龄不能为负数";
return false;
}
this.userData.age = newAge;
this.error = null;
return true;
},
// 获取当前用户数据
getUser: function () {
return this.userData;
},
// 获取错误信息
getError: function () {
return this.error;
}
};
// 2. ViewModel:核心中间层,实现数据绑定和逻辑处理,不依赖 View 具体实现
const UserViewModel = {
// 可观察数据(供 View 绑定,数据变化时自动通知 View 更新)
state: {
isLoading: false, // 加载状态
hasUser: false, // 是否有用户数据
hasError: false, // 是否有错误
errorMsg: "", // 错误信息
user: { id: "", name: "", age: 0 } // 供 View 绑定的用户数据(已转换为 UI 友好格式)
},
// 初始化:关联 Model,监听数据变化
init: function () {
this.model = UserModel;
// 绑定 View 事件(通过 DOM 事件委托,避免 ViewModel 直接操作 View)
this.bindViewEvents();
},
// 绑定 View 事件(如按钮点击)
bindViewEvents: function () {
const loadBtn = document.getElementById("loadUserBtn");
loadBtn.addEventListener("click", () => this.loadUser());
},
// 加载用户数据(核心逻辑,调用 Model 并更新 ViewModel 状态)
loadUser: function () {
// 1. 更新状态:显示加载中
this.setState({
isLoading: true,
hasUser: false,
hasError: false
});
// 2. 调用 Model 获取数据
this.model.fetchUser()
.then((rawUser) => {
// 3. 数据转换(Model 原始数据 → View 可展示数据,此处简单映射,复杂场景可做格式处理)
const uiUser = {
id: rawUser.id,
name: rawUser.name,
age: rawUser.age
};
// 4. 更新状态:显示用户数据
this.setState({
isLoading: false,
hasUser: true,
user: uiUser
});
// 5. 监听用户年龄修改(双向绑定核心:View 输入 → ViewModel → Model)
this.watchUserAgeChange();
})
.catch((err) => {
// 6. 更新状态:显示错误
this.setState({
isLoading: false,
hasError: true,
errorMsg: this.model.getError()
});
});
},
// 监听年龄输入变化(双向绑定:View 输入同步到 Model)
watchUserAgeChange: function () {
const ageInput = document.querySelector('input[type="number"]');
ageInput.addEventListener("input", (e) => {
const newAge = parseInt(e.target.value) || 0;
// 1. 调用 Model 更新业务数据
const updateSuccess = this.model.updateUserAge(newAge);
// 2. 同步更新 ViewModel 状态(错误信息或年龄)
if (updateSuccess) {
this.setState({
user: { ...this.state.user, age: newAge },
hasError: false,
errorMsg: ""
});
} else {
this.setState({
hasError: true,
errorMsg: this.model.getError()
});
}
});
},
// 更新 ViewModel 状态(触发 View 重新渲染)
setState: function (newState) {
// 合并新状态到当前状态
Object.assign(this.state, newState);
// 通知 View 更新(核心:状态变化 → View 自动刷新)
this.updateView();
},
// 更新 View(根据 ViewModel 状态渲染 UI,模拟框架的“数据驱动视图”)
updateView: function () {
const { isLoading, hasUser, hasError, errorMsg, user } = this.state;
// 控制“加载中”显示/隐藏
document.querySelector(".loading").style.display = isLoading ? "block" : "none";
// 控制“用户信息”显示/隐藏
document.querySelector(".user-info").style.display = hasUser ? "block" : "none";
// 控制“错误信息”显示/隐藏
document.querySelector(".error").style.display = hasError ? "block" : "none";
// 渲染错误信息
document.querySelector(".error").textContent = errorMsg;
// 渲染用户数据(若有数据)
if (hasUser) {
document.querySelector(".user-info p:nth-child(1)").textContent = `ID: ${user.id}`;
document.querySelector(".user-info p:nth-child(2)").textContent = `姓名: ${user.name}`;
document.querySelector(".user-info p:nth-child(3)").textContent = `年龄: ${user.age}`;
// 同步年龄输入框的值(双向绑定的“View 同步”)
document.querySelector('input[type="number"]').value = user.age;
}
// 控制按钮禁用状态
document.getElementById("loadUserBtn").disabled = isLoading;
}
};
// 3. 启动应用:仅初始化 ViewModel,View 由 ViewModel 自动渲染
UserViewModel.init();
</script>
</body>
</html>
|