跳到主要内容

数据获取

数据获取

数据获取模式

顺序获取和并行

  • 顺序数据获取
    • 一个组件的请求依赖于另一组件的请求结果;
    • 按顺序依次获取,产生瀑布流效应;
    • 所有请求完成后显示 UI;
  • 并行数据获取
    • 同时发起多个请求并同时加载数据;
    • 所有请求完成会显示 UI;

避免瀑布流

preload
  • 一种 API 风格,不必将 promise 层层传递;
import { getItem } from "@/utils/get-item";
export const preload = (id: string) => {
void getItem(id);
};
export default async function Item({ id }: { id: string }) {
const result = await getItem(id);
// ...
}

import Item, { preload, checkIsAvailable } from "@/components/Item";
export default async function Page({
params: { id },
}: {
params: { id: string };
}) {
// starting loading item data
preload(id);
// perform another asynchronous task
const isAvailable = await checkIsAvailable();
return isAvailable ? <Item id={id} /> : null;
}

fetch

  • 使用 fetch api 获取数据;
async function getData() {
const res = await fetch("https://api.example.com/...");
if (!res.ok) {
throw new Error("Failed to fetch data");
}
return res.json();
}

export default async function Page() {
const data = await getData();

return <main></main>;
}

服务器操作

服务器操作

  • 使用 use server 指令标记;
  • 在服务器执行的异步函数;
  • 虽标记为服务器操作,但依旧可以在客户端中复用;

服务器组件

  • 函数级别或模块级别使用 use server 指令;
// Server Component
export default function Page() {
// Server Action
async function create() {
'use server'

// ...
}

return (
// ...
)
}

客户端组件

客户端组件
  • 只能在模块级别使用 use client 指令;
  • 定义该模块的所有函数为客户端操作;
"use client";

export async function create() {
// ...
}
传递服务器操作
  • 标记为服务器操作的函数可以作为属性传递给客户端组件;
"use client";

export default function ClientComponent({ updateItem }) {
return <form action={updateItem}>{/* ... */}</form>;
}

最佳实践

Forms

基本操作
export default function Page() {
async function createInvoice(formData: FormData) {
"use server";

const rawFormData = {
customerId: formData.get("customerId"),
amount: formData.get("amount"),
status: formData.get("status"),
};

// mutate data
// revalidate cache
}

return <form action={createInvoice}>...</form>;
}
传递额外参数
  • 使用 bind() 方法;
// client-Component.ts
"use client";
import { updateUser } from "./actions";

export function UserProfile({ userId }: { userId: string }) {
const updateUserWithId = updateUser.bind(null, userId);

return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
);
}

// action.ts
("use server");
export async function updateUser(userId, formData) {
// ...
}
错误处理
  • 返回错误对象;
  • 使用 react 的 useFormStatus hook 处理;
  • 实验性行为,只能应用于 ssr,以后再看;
表单状态
  • 使用 react 的 useFormStatus hook;
  • 获取表单状态;
  • 实验性行为,只能应用于 ssr,以后再看;
更新 UI
  • 使用 react 的 useOptimistic hook;
  • 在服务器操作完成之间更新 UI;
  • 实验性行为,只能应用于 ssr,以后再看;

非表单

event handler
  • 使用 event handler 触发服务器操作;
"use client";

import { incrementLike } from "./actions";
import { useState } from "react";

export default function LikeButton({ initialLikes }: { initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes);

return (
<button
onClick={async () => {
// 触发服务器操作
const updatedLikes = await incrementLike();
setLikes(updatedLikes);
}}
>
Like
</button>
);
}
useEffect
  • 使用 useEffect 触发服务器操作;
"use client";
import { incrementViews } from "./actions";
import { useState, useEffect } from "react";

export default function ViewCount({ initialViews }: { initialViews: number }) {
const [views, setViews] = useState(initialViews);
useEffect(() => {
const updateViews = async () => {
const updatedViews = await incrementViews();
setViews(updatedViews);
};

updateViews();
}, []);
return <p>Total Views: {views}</p>;
}

错误处理

  • next 一旦报错,被最近层级的 error.js 捕获;
  • 推荐使用 try/catch 发送语义性错误;
"use server";

export async function createTodo(prevState: any, formData: FormData) {
try {
// Mutate data
} catch (e) {
throw new Error("Failed to create task");
}
}

安全操作

统一秘钥
  • 使用 process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY 同一配置秘钥;
CSRF
  • next 统一使用 POST 方法避免 CSRF 攻击;
  • 额外将 Origin 和 Host 比较,保证同一主机才可以调用服务器操作;
  • 可使用 serverActions.allowedOrigins 配置安全来源数组;
/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ["my-proxy.com"],
},
},
};
敏感数据
  • 使用 react 的 taintObjectReference 和 taintUniqueValue hook;
  • 避免敏感值传递给客户端;
  • 实验性行为,只能应用于 ssr,以后再学;