从一次"接口背刺"谈 React 防御性编程:避免页面白屏的艺术
在日常的前端开发中,你是否遇到过这样的场景:后端同学悄悄改了一个接口返回结构,然后你的页面瞬间死掉(白屏),控制台报错:TypeError: Cannot read properties of undefined (reading 'length')。
最近我在 Xander Lab 的博客模块开发中就亲历了这一次"背刺"。本文将复盘这一过程,并分享如何通过防御性编程让前端应用更健壮。
1. 案发现场:API 的突然进化
最初,我们的博客列表接口非常简单,直接返回一个数组:
旧接口 (GET /api/blog/posts)
json
[
{ "id": 1, "title": "React 原理" },
{ "id": 2, "title": "TypeScript 进阶" }
]
前端代码写得也很快意:
javascript
const [blogs, setBlogs] = useState([]);
useEffect(() => {
const data = await blogService.getBlogs();
setBlogs(data); // 此时 data 是数组,直接塞进去
}, []);
// 渲染部分
return (
<div>{blogs.length} 篇文章</div> // 完美运行
)
变故发生: 为了支持无限滚动加载,后端升级了接口,引入了分页对象:
新接口 (GET /api/blog/posts)
json
{
"records": [{ "id": 1, "title": "React 原理" }],
"total": 100,
"hasMore": true
}
惨案现场: 由于前端还没来得及同步修改,setBlogs(data) 把一个对象 塞进了本该是数组的状态里。当页面执行 blogs.length 时,由于对象没有 length 属性(或者为 undefined),程序执行到后续逻辑或渲染时瞬间崩溃,用户看到的是一片白屏。
2. 为什么会崩溃?
JavaScript 是弱类型语言。在 React 中,如果你没有使用 TypeScript 进行严格约束,useState([]) 的初始值只是一个建议。当你把一个 Object 赋值给数组状态时,JS 不会报错,但当你调用 .map() 或渲染 .length 时,运行时就会抛出致命错误。
3. 前端防御性编程最佳实践
第一道:数据更新时的"降级处理"
不要完全信任接口返回。即使接口挂了或者格式变了,也要保证 UI 能正常渲染。
javascript
// 更安全的写法
const data = await blogService.getBlogs();
// 即使 data.records 消失了,也给一个空数组,保证渲染逻辑不崩
setBlogs(data?.records || []);
第二道:渲染时的"安全访问"
利用 ES2020 的可选链 (Optional Chaining) 指令 ?.。这是前端开发者的救命稻草。
javascript
// 安全:即使 blogs 是 null,只会显示 0,而不会崩溃
<div>{blogs?.length || 0} 篇文章</div>
第三道:契约标准化(标准 DTO)
在 Xander Lab 中,我们通过后端封装统一的 PageData 结构,确保前后端对"分页"的认知完全一致,从根源上减少此类乌龙。
4. 总结
防御性编程不是不信任队友,而是为了系统的稳定性负责。代码不仅要能跑通,更要能优雅地处理异常。