異步數據變化和處理在現代Web應用中是必要的任務。你可能想要在服務器上執行一個独立的異步函數來進行如將數據保存到數據庫、發送電子郵件、下載PDF、處理圖像等任務。

Next.js为我们提供了Server Actions,这些是异步函数,在服务器上执行。我们可以使用服务器动作来进行服务器上的数据变更,但服务器动作可以从服务器和客户端组件中调用。

服务器动作是处理表单提交的好方法,当表单数据提交时执行操作。在这篇文章中,我们将探讨Next.js服务器动作中处理附加参数的实际用例。

如果你对学习Next.js服务器动作的设计模式和项目构建感兴趣,我为你准备了一个速成课程,你可以在这里找到。

此外,这篇文章也作为视频教程在此处可用:

目录

  1. 为什么你需要传递附加参数?

  2. 带有服务器动作的表单

  3. 如何傳送額外參數

  4. 隱藏字段又要如何呢?

  5. 資源

為什麼你需要傳送額外參數?

當我們在表單提交時執行伺服器動作,伺服器動作會自動獲得表單數據。例如,看看以下的表單:

<form className="p-4 flex" action={updateUser}>
  <Input className="w-1/2 mx-2" type="text" name="name" />
  <Button type="submit">Update User Name</Button>
</form>

這裡,當表單提交時會執行一個稱為updateUser的伺服器動作。updateUser函式將接收到提交的表單數據作為一個參數,可以用來提取表單字段的值。

如您在以下的代碼片段中所見,updateUser函式獲取了一個formData作為參數,我們可以从其中提取name字段的值。

"use server"

export async function updateUser(formData) {
  const name = formData.get('name');
  console.log(name);
}

這種模式雖然涵蓋了大部分基本用途,但您可能需要以程式化方式傳送額外的參數至伺服器動作。這些參數並不等於表單、表單數據或用戶輸入數據的一部分。它們是傳送至伺服器動作以進行進一步計算的程式化傳送值。

為了了解這點,請查看以下伺服器動作碼片段。這是一個我們之前看過的同一伺服器動作,但我們傳送了一個userId額外的參數與平常的formData參數一同傳送。

"use server"

export async function updateUser(userId, formData) {
  const name = formData.get('name');
  console.log(userId);
  console.log(name);
}

userId的值是應用程序内部的某物——並且您不會要求用戶將其作為表單提交的一部分提交。相反,您可能需要以程式化方式將其傳送至伺服器動作以進行進一步計算。

正確,我們正在談論的就是這個用途。既然我們了解為什麼需要它,讓我們來了解如何實現它。但首先,讓我們為它創建一個表單和一个可用的伺服器動作。

具有伺服器动作的表單

在您的Next.js應用程序的app目錄下創建一個actions目錄。現在在actions文件夾下創建一個user.js文件,並使用以下代碼:

"use server"

export async function updateUser(formData) {
  const name = formData.get('name');
  console.log(name);

  // 對於名字可以做任何事情,保存到DB,創建發票,無論什麼!
}

這是您在Next.js中創建伺服器函數的方法。該文件頂部必須有一個”use server”導引,以告訴Next.js這是一個特殊的文件,里面含有一个或多個要在伺服器上執行的高度協程。

然後我們有個服務器動作(異步函數)`updateUser`,並且將`formData`作為參數。在函數定義內,我們提取出`name`值并在控制台打印它。

現在我們將這個服務器動作附加到一個表單上。為了做到這一點,請在項目根目錄下創建一個名為`components`的文件夾。然後在该文件夾中創建一個名為`user-form.jsx`的文件,並放入以下代碼:

import { Input } from "./ui/input"
import { Button } from "./ui/button"

import { updateUser } from "@/app/actions/user"

const UserForm = () => {
  return(
    <form className="p-4 flex" action={updateUser}>
      <Input className="w-1/2 mx-2" type="text" name="name" />
      <Button type="submit">Update User Name</Button>
    </form>
  )
}

export default UserForm;

這是一個簡單的React组件,包含一個表單。該表單有一個叫做`name`的輸入文本字段和一個提交按鈕來提交表單。該表單有一個`action`屬性,其值為服務器動作`updateUser`。現在,當表單用`name`值提交時,服務器動作將作為表單數據的一部分接收,正如我們上面所討論的。

讓我們來測試一下。為了做到這一點,我們將創建一個Next.js路由和頁面,我們可以在其中使用`UserForm`组件。請在`app`目錄下創建一個名為`extra-args`的文件夾。然後在`app/extra-args`目錄下創建一個名為`page.js`的文件,並放入以下代碼:

import UserForm from "@/components/user-form";

const ExtraArgsDemo = () => {
  return (
    <UserForm />
  )
}

export default ExtraArgsDemo;

這是一個簡單的React组件,我們導入了`UserForm`组件並並在JSX中使用它。現在運行本地服務器並訪問此路徑`localhost:3000/extra-args`。您應該看到带有文本字段和按鈕的表單。

在文本字段內輸入一些文字並點擊按鈕。

現在,您將能夠看到已經在服務器控制台打印了打字文字。為什麼在服務器控制台而不是在瀏覽器控制台呢?這是因為服務器操作在服務器上执行,而不是在客戶端瀏覽器上。

所以,有了這點,我們现在已经建立了一個如下數據流:

頁面 => 表單 => 服務器操作

該頁面有一個表單。當表單提交時,將執行一個服務器操作。服務器操作在服務器控制台上打印表單數據。

現在讓我們增強這些部分,以將額外參數傳遞給服務器操作。

如何傳遞額外參數

我們將一個屬性從頁面傳遞給UserForm组件。我們將傳一個userId並給其值,假設我們以此userId程序化地傳遞給我們的表單和從那裡傳送到服務器操作。

import UserForm from "@/components/user-form";

const ExtraArgsDemo = () => {
  return (
    <UserForm userId={"1234"} />
  )
}

export default ExtraArgsDemo;

UserForm组件中,我們接受userId屬性。現在,我們需要做一些特別的事情,將這個userId傳遞給updateUser服務器操作。

JavaScript有一個神奇的方法叫做bind(),它幫助我們創建一個部分應用函數。透過這種部分應用函數,您可以從另一個函數的预設參數中創建函數。

在我們的案例中,updateUser 函式已經有一個叫做 formData 的參數。現在我們可以使用 bind() 方法來創建一個新的函式,並將 userId 作為額外的參數傳遞。

const updatedUserWithId = updateUser.bind(null, userId);

bind() 方法的第一個參數是您要绑定函式的上下文。上下文處理函式與 this 關鍵詞值的關聯。在我們的案例中,我們可以保持它為 null,因為我們不會更改它。然後,我們傳遞了新的參數 userId。了解一下 bind() 方法既適用於服務器端也適用於客戶端组件是好的。

以下是人改良的 UserForm 组件(user-form.jsx 文件)。請注意,表單動作值現在改為新的函式 updatedUserWithId

import { Input } from "./ui/input"
import { Button } from "./ui/button"

import { updateUser } from "@/app/actions/user"

const UserForm = ({userId}) => {
  const updatedUserWithId = updateUser.bind(null, userId);

  return(
    <form className="p-4 flex" action={updatedUserWithId}>
      <Input className="w-1/2 mx-2" type="text" name="name" />
      <Button type="submit">Update User Name</Button>
    </form>
  )
}

export default UserForm;

現在,服務器動作將接收到 userId 值作為一個參數。我們也把它打印到控制台。

"use server"

export async function updateUser(userId, formData) {
  const name = formData.get('name');
  console.log(userId);
  console.log(name);

  // 對用戶 id 和名稱做任何事情,比如保存到 DB,
  // 创建发票,无论什么!
}

如果你提交带有名称值的表单:

您會看到 userId 和名稱值都記錄在服務器控制台上。太好了!我們從表單數據中記錄了一個值,另一個值是內部傳遞給服務器動作。

所以,我們學到了如何將額外的參數傳遞給服務器動作,與表單數據一起。

那麼,隱藏字段呢?

HTML 支援隱藏型表單字段,用以將用戶端數據傳送到伺服器,而不必接受用戶的輸入。這意味著我們可以利用隱藏字段傳送 userId 值,如下所示:

那麼,我們為什麼要使用 bind() 方法呢?其實,這是因為安全考慮。當你使用隱藏字段傳送數據時,值會成為渲染的 HTML 的一部分,而且它不會被 encoding 編碼。因此,最好以程式化方式處理。

資源

目前就到此为止。你是否享受閱讀這篇文章,並且學到了新东西呢?如果有的話,我很想知道內容是否有幫助。讓我分享一些你可能需要的額外資源:

另外,你也可以透過以下方式跟我聯繫:

期待不久與我的下一篇文章見面。在此之前,請好好照顧自己,並继续保持學習。