综合练习:迷你用户名片墙
这是什么:本章把全章串成一个小应用:从接口请求用户列表 → 渲染名片墙 → 搜索框实时过滤 → 加载态与错误态齐全。成品就是一个小型「用户管理页」的原型。
为什么用:真实项目的前端日常工作,八成是「请求列表 + 展示 + 按条件过滤」的变体。把这 50 行代码亲手跑通,AJAX 就算真正入门了。
如何用:数据来自 JSONPlaceholder 的 /users 接口(10 个假用户)。把完整页面存成 index.html 用本地服务器打开,或直接贴进前端在线运行场。
完整代码
把下面内容保存为 index.html(依赖网络请求,建议用 python -m http.server 起本地服务器打开):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>迷你用户名片墙</title>
<style>
body { font-family: sans-serif; max-width: 720px; margin: 24px auto; }
#search { padding: 8px 12px; width: 240px; }
.wall { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-top: 16px; }
.card { border: 1px solid #ddd; border-radius: 8px; padding: 12px; }
.card h3 { margin: 0 0 4px; }
.card p { margin: 2px 0; color: #666; font-size: 14px; }
</style>
</head>
<body>
<input id="search" placeholder="输入名字或城市过滤…">
<div id="wall" class="wall">加载中…</div>
<script>
let users = [] // 全量数据缓存:过滤在本地做,不重复请求
async function loadUsers() {
let wall = document.getElementById('wall')
wall.textContent = '加载中…' // ① 加载态
try {
let res = await fetch('https://jsonplaceholder.typicode.com/users')
if (!res.ok) throw new Error('HTTP ' + res.status) // ② 状态码自查
users = await res.json()
render(users)
} catch (err) {
wall.textContent = '加载失败:' + err.message // ③ 错误态
}
}
function render(list) {
document.getElementById('wall').innerHTML = list.map(u => `
<div class="card">
<h3>${u.name}</h3>
<p>@${u.username}</p>
<p>📧 ${u.email}</p>
<p>🏙 ${u.address.city}</p>
</div>`).join('') || '<p>没有匹配的用户</p>'
}
// 搜索过滤:输入变化时,从缓存里筛,不再发请求
document.getElementById('search').addEventListener('input', function () {
let kw = this.value.trim().toLowerCase()
let hit = users.filter(u =>
u.name.toLowerCase().includes(kw) || u.address.city.toLowerCase().includes(kw)
)
render(hit)
})
loadUsers()
</script>
</body>
</html>
你会看到:一排用户名片(姓名、昵称、邮箱、城市);在搜索框输入「Leanne」只剩一张名片,输入「城市名的一部分」也能过滤;断网刷新则显示「加载失败:…」。
逐段中文精讲
let users = [](数据缓存):请求一次后把全量数据存在变量里。此后搜索过滤只操作内存,不再发请求——这是「一次请求、多次使用」的基本功。loadUsers()(请求区):第 5 章的三件套全齐——加载中…占位、res.ok自查、try/catch 兜错误态。空数组时 filter 得到空数组,render 里的|| '<p>没有匹配的用户</p>'兜住「查无此人」。render()(渲染区):第 3 章的 map + join 黄金组合;u.address.city是嵌套取值——JSON 教程的知识点在这里会师。addEventListener('input', …):input 事件在每次键入都触发,配合filter实现实时过滤;toLowerCase()让大小写不敏感,体验更好。
动手改
- 给名片加一行公司名(提示:数据在
u.company.name字段里) - 加一个「城市下拉框」:用
new Set(users.map(u => u.address.city))生成不重复的城市列表,选中后过滤 - 进阶:点击名片时再请求
/users/{id}/posts,在名片下方展开这个用户的文章列表
学完这章去哪
恭喜!你已经打通了「请求数据 → 渲染界面 → 交互过滤」的完整链路——这就是前端与后端对话的全部基本功。
不过你也许注意到了:数据一变,render 就要整段重写。现代框架(Vue、React)把「数据变 → 界面自动变」自动化了,学完它们,你会发现今天的 render 函数被框架悄悄接手了。
下一站:Vue 教程 🐸