前言
在日常开发过程中,相信大家经常能遇到这种页面报错UnCaught TypeError: Cannot read properties。这往往是对后端接口数据进行一系列操作后产生的,比如对属性取值,获取数组长度,转字符串等。和后端对接时明明说好的给我一个数组或者什么类型,结果上线时莫名其妙给一堆null或者原定的属性名变了,结果前端页面啪一下直接挂掉,那有什么办法,只好背下黑锅,怪自己没做好兼容。最近发现一个第三方库——Zod,能够有效缓解类似情况,我们的目标是:出问题但前端不背锅!
复现
模拟一下常出现的问题,先用Node创建一个接口,将属性age设置为null,在js代码中操作age
const express = require("express");
const app = express();
const PORT = 3000;
app.get("/api/user", (req, res) => {
const user = {
age: null,
name: "John",
sex: "Male",
};
res.json(user);
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
import axios from "axios";
import { useRequest } from "ahooks";
interface UserDataType {
age: number;
name: string;
sex: string;
};
export default function Router1() {
const { data } = useRequest(async () => {
const res = await axios.get<UserDataType>("api/user");
return res.data;
});
return (
<div>
<h1>{data?.age.toString()}</h1>
</div>
);
}
可以看到我们已经正确使用TypeScript进行类型检查,取属性值时加上可选链操作符已经是常规操作,但就是在后一步转字符串时忘了加上可选链操作符,导致前端页面直接报错挂掉了。
事实上如果要对数据进行一些复杂转换或者操作,滥用可选链不仅会让代码变得不美观不易阅读,还会在出现问题时难以精准定位错误。那么有没有一种方法能够在不滥用可选链且不让页面挂掉的前提下,还能精准定位问题的所在呢?
Zod
我们可以使用Object Schema对API进行校验,类似于ts的定义。
import axios from "axios";
import { useRequest } from "ahooks";
import { z } from "zod";
interface UserDataType {
age: number;
name: string;
sex: string;
}
export default function Router1() {
const dataResult = z.object({
age: z.number(),
name: z.string(),
sex: z.string(),
});
const { data } = useRequest(async () => {
const res = await axios.get<UserDataType>("api/user");
const parseData = dataResult.parse(res.data);
return parseData;
});
return (
<div>
<h1>{data?.age.toString()}</h1>
</div>
);
}
在这个例子中,通过z.object指定了我们所期待的返回的数据类型,通过parse方法将获得数据转换成我们所需要的格式,我们的 parsedData 现在已经被赋予了正确的类型,并且拥有了一个 Zod 能识别的结构。可能后端接口返回了很多我们不需要的属性,对于parsedData而言他只会关注到我们手动定义且需要的属性。回到例子中来,即使我们拿到的age为null,但仍对其进行了字符串转换,前端页面也不会报错,而是在控制台提示我们的错误信息。虽然这是一个治标不治本的方法,但是在关键时刻还是可以有效避免前端页面崩溃带来的一系列问题,即使数据无法正确展示,但用户交互体验不会受到影响。
当然在同时使用zod和TypeScript时,会有一种我们写了重复代码的感觉,多次对数据类型进行了定义。zod也提供了快速生成ts类型定义的功能,通过工具方法 infer 可以将 schema 转化为 TypeScript 类型声明,也是支持ts静态类型检查的。
type UserDataType = z.infer<typeof dataResult>;
结尾
当然zod远远不止这些功能,他还能通过.default()手动设置默认值或者使用.optional()让某个属性变成可选的等等操作,具体可以参考这里获取更多方法,让你的代码变得更加健壮且优雅。后续如果使用到了其他有意思的功能会继续在这里更新。