Next.js/React with Zod - Ali Al Reza - Medium

内容

当从不同服务器获取数据时,我们收到的数据可能并不总是符合预期的格式。这可能导致我们应用程序中的错误。因此,在处理外部数据时,要格外谨慎至关重要。

TypeScript 单独并不足以进行模式验证,因此我们需要引入专门的模式验证器,比如 Zod 或 Yup。

在全栈应用程序中,前端和后端都与多个外部数据源进行交互,包括:

前端:

  • 后端的 API 请求
  • 第三方 API 数据
  • 用户输入表单
  • 本地存储
  • URL 数据

后端:

  • 前端的 API 请求
  • 第三方 API 数据
  • 环境变量
  • Webhooks
  • 文件系统
  • 数据库
  • URL

通过实施健壮的模式验证,我们可以确保数据完整性并防止潜在错误,从而使应用程序更可靠和安全。

全栈应用程序中获取数据以及需要模式验证器的清晰理解图片。

为什么 TypeScript 还不够?

我正在使用React/Next.js更好地理解为什么需要在TypeScript旁边使用Zod。假设我们有一个product/page.tsx页面,我们想要从/api/product获取数据。

product/page.tsx

"使用客户端";

import React, { use, useEffect } from "react";

type Product = { name: string; price: number; };

const Product = () => {
useEffect(() => {
fetch("/api/product")
.then((res) => res.json())
.then((data: Product) => {
console.log(data.name.toUpperCase());
});
}, []);

return <div>产品</div>;
};

export default 产品;

/api/product

import { NextResponse } from "next/server";

export async function GET(request: Request) { const product = { name: "Product 1", price: 100, };

return NextResponse.json(product);

假设有独立的后端和前端团队。他们更改后端的返回类型如此,那么我们的应用程序将崩溃。在运行时,您无法确切知道数据的形状会是什么,因此可能会导致应用程序崩溃。

api/product/route.ts

import { NextResponse } from "next/server";

export async function GET(request: Request) { const product = { id: 1, price: 100, };

return NextResponse.json(product);

在这种情况下,我们的应用可能会崩溃。这就是为什么我们需要在TypeScript旁边使用适当的验证模式。

通过可选链可以防止应用程序崩溃。

product/page.tsx

"使用客户端";

import React, { use, useEffect } from "react";

type Product = {
name: string;
price: number;
};

const Product = () => {
useEffect(() => {
fetch("/api/product")
.then((res) => res.json())
.then((product: Product) => {

    console.log(product?.name?.toUpperCase());  
  });  

}, []);

返回 <div>产品</div>; };

export default 产品;

但如果产品实际上不存在,我们想向用户显示友好的错误消息。这对我们没有帮助,如果我们的返回类型存在,但是不同类型,比如价格是字符串,那么可选链类型根本没有帮助。

api/product/route.ts

import { NextResponse } from "next/server";

export async function GET(request: Request) { const product = { id: 1,

价格:"$100",  

};

返回 NextResponse.json(product);

product/page.tsx

"使用客户端";

import React, { use, useEffect } from "react";

type Product = {
name: string;
price: number;
};

const Product = () => {
useEffect(() => {
fetch('/api/product')
.then((res) => res.json())
.then((product: Product) => {

    console.log(product?.name?.toUpperCase());
            });  

}, []);

返回 <div>产品</div>;
};

export default Product;

然后它会导致我们的应用程序崩溃:

你可以检查数字的类型。像

product/page.tsx

"使用客户端";

import React, { use, useEffect } from "react";

type Product = {
name: string;
price: number;
};

const Product = () => {
useEffect(() => {
fetch("/api/product")
.then((res) => res.json())
.then((product: Product) => {
console.log(product?.name?.toUpperCase());

    if (typeof product?.price === "number") {  
      console.log(product?.price?.toFixed(2));  
    }  
  });  

}, []);

返回 <div>产品</div>; };

export default 产品;

你可以看到这是非常混乱的代码,这不是一个健壮的解决方案。这就是模式验证器发挥作用的地方,其中一个流行的是Zod。还有其他的,比如Yup,它们都有类似的功能,即简单验证数据。

示例 1–2: 用于 API 请求和第三方 API 的 Zod:

yarn add zod or npm i zod

Zod 是运行时依赖项,我们可以在运行时使用。

product/page.tsx

"使用客户端";

import React, { use, useEffect } from "react";
import { z } from "zod";

type Product = {
name: string;
price: number;
};

const productSchema = z.object({ name: z.string(), price: z.number(), });

const Product = () => {
useEffect(() => {
fetch("/api/product")
.then((res) => res.json())
.then((product: Product) => {

    const validatedProduct = productSchema.safeParse(product);
    if (!validatedProduct.success) {  
      console.error(validatedProduct.error.message);  
      return;  
    }
    console.log(validatedProduct.data);  
  });  

}, []);

返回 <div>产品</div>;
};

export default 产品;

api/product/route.ts

import { NextResponse } from "next/server";

export async function GET(request: Request) { const product = { id: 1, price: "$100", };

返回 NextResponse.json(product);

价格不正确,但我们的应用程序没有崩溃。Zod 在控制台中显示了漂亮的错误消息

这里显示问题所在的关键是路径键和值是名称。

如果我们更正返回类型,我们将在控制台中看不到错误。

api/product/route.ts

import { NextResponse } from "next/server";

export async function GET(request: Request) { const product = { name: "Product 1", price: 100, };

返回 NextResponse.json(product);

我们可以将其用作单一真相来源。比如,如果我们需要一个帮助函数,其中需要产品类型,那么我们也可以使用 Zod。

product/page.tsx

"使用客户端";

import React, { use, useEffect } from "react";
import { z } from "zod";

const productSchema = z.object({ name: z.string(), price: z.number(), });

type Product = z.infer<typeof productSchema>;

const getProductPrice = (product: Product) => { return product.price; };

const Product = () => {
useEffect(() => {
fetch("/api/product")
.then((res) => res.json())
.then((product: unknown) => {

    const validatedProduct = productSchema.safeParse(product);
    if (!validatedProduct.success) {  
      console.error(validatedProduct.error.message);  
      return;  
    }
    console.log(validatedProduct.data);  
  });  

}, []);

返回 <div>产品</div>; };

export default 产品;

如果我们更改模式,产品类型将自动更新。

如果我们在自己的后端进行 API 请求,将会有一个更健壮的解决方案,称为 tRPC,这需要更多的设置。因此,如果我控制我们自己的 API 路由,我们可能会想要使用 tRPC。在底层执行类似操作。如果我们无法控制后端,我们可以在应用程序运行之前使用 React QueryZod

示例 3:来自外部来源的表单数据

我们有三个输入字段 email、password 和 confirmPassword,我们可以通过 react-hook-form / formic + zod 运行它。react-hook-form 有助于:

⇒ 表单验证

⇒ 错误和加载状态

⇒ 提高性能并防止不必要的重新渲染

在这里,zod 帮助我们验证模式。要在我们的应用程序中添加 react-hook-form:

Zod 的角度来看,我们需要它的原因是这些表单数据将在我们的后端中使用。因此,我们希望创建一个单一的真相文件,以帮助我们验证数据。当后端接收到数据时,它也可能会验证数据,因为我们不能信任来自客户端的所有内容。因此,如果我们在客户端和后端都验证模式,那将是非常好的。但是 react-hook-form 仅在客户端进行验证,我们无法在后端重用它。然而,如果我们使用 Zod 进行验证,我们可以在客户端和后端都使用该模式。

创建一个名为 lib/types.ts 的文件夹和文件,此架构将同时用于客户端和后端。

import { z } from "zod";

export const signUpSchema = z
.object({

email: z.string().email(),  
password: z  
  .string()  
  .min(10, "密码必须至少为 10 个字符")  
  .max(100),  
confirmPassword: z.string().min(10).max(100),  

})
.refine((data) => data.password === data.confirmPassword, {
message: "密码不匹配",
path: ["confirmPassword"],
});

export type TsignUpSchema = z.infer<typeof signUpSchema>;

创建一个名为: signup/page.tsx 的页面

import FormWithReactHookFormAndZod from "@/components/form-with-rhf-and-zod"; import React from "react";

const SignUp = () => {
return (
<div className="text-black">
<FormWithReactHookFormAndZod />
</div>
);
};

导出默认的注册;

创建一个组件文件夹,将表单布局复制到其中:

components/form-with-rhf-and-zod.tsx

"使用客户端";

import { TsignUpSchema, signUpSchema } from "@/lib/types"; import React from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod";

export default function FormWithReactHookFormAndZod() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
reset,
setError,
} = useForm<TsignUpSchema>({

解析器: zodResolver(signUpSchema),  

};

const onSubmit = async (data: TsignUpSchema) => {  
const response = await fetch("/api/signup", {  
  method: "POST",  
  body: JSON.stringify(data),  
  headers: {  
    "Content-Type": "application/json",  
  },  
});  
const responseData = await response.json();  
if (!response.ok) {  
总结
文章强调了在处理外部数据时要格外小心,因为数据可能不符合预期的格式,导致应用程序出现错误。 TypeScript本身不足以进行模式验证,需要结合专用的模式验证器如Zod或Yup。通过实施强大的模式验证,可以确保数据完整性,预防潜在错误,使应用程序更可靠和安全。文章还提到了在全栈应用程序中,前端和后端与多个外部数据源交互的情况,包括API请求、第三方API数据、用户输入表单、本地存储、URL数据等。最后,通过示例展示了如何使用Zod进行API请求和第三方API的数据验证,以及在表单数据验证中结合react-hook-form和Zod的应用。