Next.js学习笔记3 前后端通信:server action 和 trpc

756 阅读2分钟

server action

server action是一种完全执行于服务端的函数.但它可以被任一组件类型直接导入和调用,就像普通函数一样.尽管写法上完全一致,但服务端组件是直接调用此函数,而客户端组件是通过网络请求,使服务端调用此函数并返回请求结果.server action 必须是异步函数.

写法

在文件顶部使用'use server'表明它会导出server action.如果是服务端组件,还可以采用内联的写法:

export const ServerComp = ()=>{
    const serverAction = ()=>{
        'use server'
    }
    return (
        //...
    )
}

客户端组件则必须导入server action.

作用

server action可以被用于表单(form标签)的action属性.服务端组件也可以使用server action为元素设置事件,或为客户端组件传递参数(即使它不是简单对象).客户端组件可以简单地把它视作网络请求.

注意

server action的参数必须是严格的简单对象.像这样是不行的

// server action
async function Action(){
    // ...
}
// 组件
<button onClick={Action}></button>

即使server action的类型声明里不接受参数 但next还是会检测实际提供给它的参数并报错.这个错误在生产环境中也会立刻使页面崩溃. 正确的写法是:

<button onClick={()=>Action()}></button>

但这种写法只能用于客户端 服务端是不允许设置事件的

trpc

server action可以替代前后端的网络请求.但Next14之前 这种写法并不支持,此时应当使用trpc避免直接的网络请求.具体写法也可以看这里

构建trpc

src/lib/trpc/trpc.ts

构建trpc对象

import { initTRPC } from '@trpc/server'

export const { procedure, router } = initTRPC.create()

src/lib/trpc/routes/example.ts

可以执行的服务端函数 使用zod构建

import { z } from "zod";
import { procedure } from "../trpc";

export const example = procedure
    .input(z.object({ message: z.union([z.string(), z.number()]) }))
    .query(async opts => {
        return opts.input.message
    })

src/lib/trpc/index.ts

注册服务端函数 导出可以使用的对象

import { router } from "./trpc"
import { example } from "./routes/example"

export const TRPC = router({
  example,
})

src/app/api/trpc/[trpc]/route.ts

实际进行网络请求的部分

import { TRPC } from "@/lib/trpc";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: "/api/trpc", // 这个与当前路由有关
    req,
    router: TRPC,
    createContext: () => ({}),
  });
export { handler as GET, handler as POST };

src/utils/trpc.ts

创建trpc句柄 仅限客户端组件使用

import { TRPC } from "@/lib/trpc";
import { createTRPCReact } from "@trpc/react-query";

export const trpc = createTRPCReact<typeof TRPC>()

使用

上下文

提供trpc上下文 应当在root layout中使用这个组件包裹子组件

'use client'

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
import { ReactNode, useState } from "react";
import { trpc } from '@/utils/trpc'

type Props = {
    children: ReactNode
}
export function TRPCContext(props: Props) {
    const [queryClient] = useState(() => new QueryClient());
    const [trpcClient] = useState(() =>
        trpc.createClient({
            links: [
                httpBatchLink({
                    url: '/api/trpc',
                }),
            ],
        })
    )
    return (
        <trpc.Provider client={trpcClient} queryClient={queryClient}>
            <QueryClientProvider client={queryClient}>
                {props.children}
            </QueryClientProvider>
        </trpc.Provider>
    );
}

客户端组件中

import { trpc } from '@/utils/trpc'

// 组件内
const res = trpc.example.useQuery({ message: 'trpc example' })