本文将继续构建一个功能更完善的用户管理界面。我们来逐步添加真实 Web 应用常见的交互功能:刷新按钮、表单提交添加用户、加载状态管理。
一、更新 index.html —— 添加按钮和表单
将原来的 index.html 替换为以下内容:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TS + Vite 示例 - 增强版</title>
<style>
/* 简单样式,让界面更清晰 */
body {
font-family: sans-serif;
max-width: 600px;
margin: 2rem auto;
padding: 0 1rem;
}
button, input {
margin: 0.5rem 0;
padding: 0.5rem;
}
.error {
color: red;
}
.loading {
color: gray;
font-style: italic;
}
</style>
</head>
<body>
<div id="app">
<h1>用户管理</h1>
<!-- 刷新按钮 -->
<button id="refresh-btn" type="button">刷新列表</button>
<!-- 添加用户表单 -->
<h2>添加新用户</h2>
<form id="add-user-form">
<div>
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required />
</div>
<div>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required />
</div>
<button type="submit">提交</button>
</form>
<!-- 用户列表容器 -->
<h2>用户列表</h2>
<ul id="user-list">
<li>加载中...</li>
</ul>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
变化说明:
- 新增了“刷新列表”按钮(
#refresh-btn)
- 新增了表单,包含姓名和邮箱输入框,用于添加用户
- 添加了简单的 CSS 样式,提升视觉效果
- 保留原有的用户列表容器
#user-list
二、扩展 src/main.ts —— 增加交互逻辑
将原来的 main.ts 替换为以下代码(在原有基础上扩展,保留原有功能并新增):
/**
* TypeScript 增强版示例:
* 1. 获取并展示用户列表(原有)
* 2. 刷新按钮重新加载列表
* 3. 表单提交添加新用户(模拟 POST 请求)
* 4. 加载状态与错误处理
*/
// ---------- 类型定义 ----------
interface User {
id: number;
name: string;
email: string;
// 实际 API 还有其他字段,我们只关注这三个
}
// 用于创建新用户的类型(id 由后端生成)
interface NewUser {
name: string;
email: string;
}
// ---------- DOM 元素获取 ----------
const userListElement = document.getElementById('user-list') as HTMLUListElement;
const refreshBtn = document.getElementById('refresh-btn') as HTMLButtonElement;
const addUserForm = document.getElementById('add-user-form') as HTMLFormElement;
const nameInput = document.getElementById('name') as HTMLInputElement;
const emailInput = document.getElementById('email') as HTMLInputElement;
// ---------- 辅助函数:显示加载/错误状态 ----------
function setLoading(loading: boolean): void {
if (loading) {
userListElement.innerHTML = '<li class="loading">加载中...</li>';
}
}
function showError(message: string): void {
userListElement.innerHTML = `<li class="error">错误:${message}</li>`;
}
// ---------- 获取用户列表(原有,稍作改造以支持加载状态) ----------
async function fetchUsers(): Promise<User[]> {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json() as User[];
return users;
}
// ---------- 渲染用户列表 ----------
function renderUserList(users: User[]): void {
if (!users.length) {
userListElement.innerHTML = '<li>暂无用户</li>';
return;
}
userListElement.innerHTML = ''; // 清空
users.forEach(user => {
const li = document.createElement('li');
li.textContent = `${user.name} (${user.email})`;
userListElement.appendChild(li);
});
}
// ---------- 核心:加载用户并渲染 ----------
async function loadUsers(): Promise<void> {
setLoading(true);
try {
const users = await fetchUsers();
renderUserList(users);
} catch (error) {
showError(error instanceof Error ? error.message : '未知错误');
console.error(error);
} finally {
// 可以不用清除加载状态,因为渲染函数已经替换了内容
}
}
// ---------- 添加新用户 ----------
async function addUser(newUser: NewUser): Promise<User> {
// 注意:jsonplaceholder 的 POST 请求不会真的存储,但会返回一个模拟的创建结果
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newUser),
});
if (!response.ok) {
throw new Error(`添加失败:${response.status}`);
}
const createdUser = await response.json() as User;
return createdUser;
}
// ---------- 处理表单提交 ----------
async function handleAddUser(event: Event): Promise<void> {
event.preventDefault(); // 阻止默认提交行为
// 获取表单数据并校验
const name = nameInput.value.trim();
const email = emailInput.value.trim();
if (!name || !email) {
alert('请填写姓名和邮箱');
return;
}
// 临时禁用提交按钮,防止重复提交
const submitBtn = addUserForm.querySelector('button[type="submit"]') as HTMLButtonElement;
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = '提交中...';
try {
// 调用添加 API
const newUser = await addUser({ name, email });
// 添加成功后,清空表单
nameInput.value = '';
emailInput.value = '';
// 重新加载用户列表(显示最新数据)
await loadUsers();
// 可选:显示成功提示
console.log('添加成功:', newUser);
} catch (error) {
alert(error instanceof Error ? error.message : '添加失败');
console.error(error);
} finally {
// 恢复按钮状态
submitBtn.disabled = false;
submitBtn.textContent = originalText || '提交';
}
}
// ---------- 绑定事件 ----------
refreshBtn.addEventListener('click', loadUsers);
addUserForm.addEventListener('submit', handleAddUser);
// ---------- 初始化:加载用户 ----------
loadUsers();
三、代码详解(新增部分)
1. DOM 元素获取与类型断言
const refreshBtn = document.getElementById('refresh-btn') as HTMLButtonElement;
const addUserForm = document.getElementById('add-user-form') as HTMLFormElement;
const nameInput = document.getElementById('name') as HTMLInputElement;
const emailInput = document.getElementById('email') as HTMLInputElement;
- 使用
as 断言告诉 TypeScript 这些元素的具体类型,后续可以安全调用 .value、.disabled 等属性。
2. 加载与错误状态辅助函数
setLoading() 和 showError() 简化了状态管理。
- 在
loadUsers 中调用 setLoading(true) 显示“加载中...”,成功或失败后会被替换掉。
3. 添加用户函数
async function addUser(newUser: NewUser): Promise<User>
- 发送 POST 请求,
Content-Type: application/json,body 为 JSON 字符串。
- 返回类型
Promise<User>,表示后端返回创建后的用户(包含 id)。
4. 表单处理函数
event.preventDefault() 防止页面刷新。
- 获取输入值并简单校验。
- 禁用提交按钮并显示“提交中...”,防止重复点击。
- 调用
addUser,成功后清空表单、重新加载列表。
- 使用
try...catch 捕获错误,弹出提示。
finally 中恢复按钮状态。
5. 事件绑定与初始化
- 刷新按钮绑定
loadUsers。
- 表单绑定
handleAddUser。
- 最后调用
loadUsers() 显示初始数据。
四、运行与测试
- 启动开发服务器(如果未启动):
npm run dev
- 打开浏览器,你会看到:
- 初始加载用户列表
- 点击“刷新列表”按钮,重新加载
- 填写表单,点击“提交”,会向 JSONPlaceholder 发送 POST 请求(虽然数据不会持久保存,但会返回模拟的新用户),然后列表会刷新并显示新添加的用户(注意 JSONPlaceholder 实际返回的 id 是 11,但列表刷新后可能不会包含它,因为后端并没有真实存储,这里仅演示流程。如果要真实持久化,需要配合自己的后端)
- 可以尝试提交空数据,会弹出提示;网络错误时也会显示错误。

五、TypeScript 类型安全带来的好处
- 当你编写
addUser 时,参数类型 NewUser 确保只能传入 { name, email } 对象,避免传错字段。
- 在
handleAddUser 中,nameInput.value 被推断为 string,可以安全调用 .trim()。
- 使用
as 断言时,我们清楚知道元素存在且类型正确,减少了运行时错误。这正是现代 HTML/CSS/JS 开发中结合静态类型检查的优势所在。
六、总结
你现在已经拥有了一个 带有完整交互的 TypeScript Web 应用雏形,包含:
- 数据获取(GET)
- 数据创建(POST)
- 用户界面更新
- 加载状态和错误处理
- 表单验证
所有代码都有清晰的类型注释,并且完全可以在 Vite 开发服务器上运行。继续基于这个模板,你可以根据自己的需求添加更多功能,比如编辑、删除用户,或者使用状态管理库。在 云栈社区 你还可以找到更多关于前端工程化和实战项目的讨论与资源。
|