เหตุใดการกระทำของเซิร์ฟเวอร์ Next.js จึงเปลี่ยนแปลงเกม!

ฉันไม่รู้เกี่ยวกับคุณ แต่ทุกครั้งที่มีการประกาศเปิดตัว Next.js ใหม่ ฉันตื่นเต้นมากที่จะได้เห็นคุณลักษณะใหม่ๆ ที่ได้รับการเผยแพร่ ทุกวันนี้ Vercel อยู่ในขั้นตอนล็อคด้วย React ในการย้ายไปยังเซิร์ฟเวอร์ให้ได้มากที่สุด ซึ่งน่าตื่นเต้นมากที่ได้เห็น! สัปดาห์ที่ผ่านมาVercel ได้ประกาศเปิดตัว Next.js 13.4ซึ่งมีกระบวนทัศน์ใหม่สำหรับการอัปเดตข้อมูลบนเซิร์ฟเวอร์ที่เรียกว่า Server Actions
Server Actions ออกแบบมาเพื่อเปิดใช้งานการอัปเดตข้อมูลฝั่งเซิร์ฟเวอร์ ลด JavaScript ฝั่งไคลเอ็นต์ และจัดเตรียมรูปแบบที่ได้รับการปรับปรุงอย่างต่อเนื่อง ในบทความนี้ เราจะเจาะลึกถึงพื้นฐานของ Server Actions สำรวจวิธีสร้างและเรียกใช้งาน และหารือเกี่ยวกับการปรับปรุงและประโยชน์ต่างๆ ที่พวกเขานำมาสู่แอปพลิเคชัน Next.js ของคุณ
ℹ️ โปรดทราบว่าตัวอย่างเหล่า นี้นำมาจากเว็บไซต์เอกสาร Next.js โดยตรง ดูตัวอย่างด้านล่างของตัวช่วยสร้างแบบฟอร์มหลายขั้นตอน
เปิดใช้งานการดำเนินการของเซิร์ฟเวอร์
ในการเปิดใช้งาน Server Actions ในโครงการ Next.js คุณต้องตั้งค่าserverActions
แฟล็กทดลองในnext.config.js
ไฟล์ของคุณ:
const nextConfig = {
experimental: {
serverActions: true,
},
};
การสร้าง Server Action นั้นง่ายพอๆ กับการสร้างฟังก์ชั่นแบบอะซิงโครนัสด้วยคำสั่ง "use server" ที่ด้านบนสุดของตัวฟังก์ชั่น ฟังก์ชันควรมีอาร์กิวเมนต์ที่ทำให้เป็นอนุกรมได้และค่าส่งคืนที่ทำให้เป็นอนุกรมได้ โดยยึดตามโปรโตคอล React Server Components นี่คือตัวอย่าง:
async function myAction() {
"use server"
// Your server action logic here
}
คุณยังสามารถใช้คำสั่ง "ใช้เซิร์ฟเวอร์" ระดับบนสุดที่ด้านบนของไฟล์ ซึ่งจะมีประโยชน์หากคุณมีไฟล์เดียวที่ส่งออกการดำเนินการของเซิร์ฟเวอร์หลายรายการ และจำเป็นหากคุณนำเข้าการดำเนินการของเซิร์ฟเวอร์ในคอมโพเนนต์ของไคลเอ็นต์
"use server"
export async function myAction() {
// Your server action logic here
}
ตามเว็บไซต์เอกสาร Next.js มีหลายวิธีในการเรียกใช้ Server Actions:
- การใช้
action
อุปกรณ์ประกอบฉาก:action
อุปกรณ์ประกอบฉากของ React ช่วยให้คุณสามารถเรียกใช้การดำเนินการของเซิร์ฟเวอร์ใน<form>
องค์ประกอบได้
export default function AddToCart({ productId }) {
async function addItem(data) {
'use server'
const cartId = cookies().get('cartId')?.value;
await saveToDb({ cartId, data });
}
return (
<form action={addItem}>
<button type="submit">Add to Cart</button>
</form>
)
}
export default function Form() {
async function handleSubmit() {
"use server"
// Your server action logic here
}
async function submitImage() {
"use server"
// Your server action logic here
}
return (
<form action={handleSubmit}>
<input type="text" name="name" />
<input type="image" formAction={submitImage} />
<button type="submit">Submit</button>
</form>
)
}
'use client'
import { experimental_useOptimistic as useOptimistic } from 'react'
import { send } from './_actions.js'
export function Thread({ messages }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [...state, { message: newMessage, sending: true }],
)
const formRef = useRef();
return (
<div>
{optimisticMessages.map((m) => (
<div>
{m.message}
{m.sending ? 'Sending...' : ''}
</div>
))}
<form
action={async (formData) => {
const message = formData.get('message')
formRef.current.reset()
addOptimisticMessage(message)
await send(message)
}}
ref={formRef}
>
<input type="text" name="message" />
</form>
</div>
)
}
- ถ้าการดำเนินการของไคลเอนต์ถูกส่งไปยัง a
<form>
แบบฟอร์มจะยังคงโต้ตอบ แต่การดำเนินการจะอยู่ในคิวจนกว่าแบบฟอร์มจะไฮเดรท ให้<form>
ความสำคัญกับ Selective Hydration จึงเกิดขึ้นอย่างรวดเร็ว
// src/components/MyForm.js
'use client'
import { useState } from 'react'
import { handleSubmit } from './actions.js'
export default function MyForm({ myAction }) {
const [input, setInput] = useState();
return (
<form action={handleSubmit} onChange={(e) => setInput(e.target.value)}>
...
</form>
)
}
มารวมเข้าด้วยกันเพื่อสร้างโฟลว์การลงทะเบียนวิซาร์ดตัวอย่างโดยใช้การดำเนินการของเซิร์ฟเวอร์เพื่อบันทึกข้อมูลในแต่ละขั้นตอนของโฟลว์
ก่อนอื่น เราจะเพิ่มหน้า:
// app/register/page.tsx
import RegistrationForm from 'src/components/RegistrationForm'
export default function Register() {
return (
<div>
<h1>Registration</h1>
<RegistrationForm />
</div>
)
}
// components/RegistrationForm.tsx
'use client'
import React, { useState } from 'react';
import {
savePersonalInformation,
saveContactInformation,
saveAccountDetails
} from '../../actions'
import PersonalInformationStep from './PersonalInformationStep'
import ContactInformationStep from './ContactInformationStep'
import AccountDetailsStep from './AccountDetailsStep'
const WIZARD_STEPS = [
{
Component: PersonalInformationStep,
action: savePersonalInformation,
},
{
Component: ContactInformationStep,
action: saveContactInformation,
},
{
Component: AccountDetailsStep,
action: saveAccountDetails,
},
]
export default function RegistrationForm() {
const [step, setStep] = useState(0);
const { Component, action } = WIZARD_STEPS[step]
const handleSubmit = async (data: FormData) => {
await action(data);
if (step < WIZARD_STEPS.length - 1) {
setStep((currentStep) => currentStep + 1)
} else {
// Submit multi-step wizard form and navigate
}
}
return (
<Component onSubmit={handleSubmit} />
)
}
// actions/index.ts
'use server'
import { cookies } from 'next/headers'
import prisma from 'prisma'
import { hashPassword } from 'src/utils/auth'
export async function savePersonalInformation(data: FormData) {
const user = await prisma.user.create({
data: {
firstName: data.get('firstName').toString(),
lastName: data.get('lastName').toString(),
},
})
cookies().set('userId', user.id)
}
export async function saveContactInformation(data: FormData) {
// Save contact information to the database
const id = cookies().get('userId')?.value
await prisma.user.update({
where: { id },
data: {
email: data.get('email').toString(),
phone: data.get('phone').toString(),
},
})
}
export async function saveAccountDetails(data: FormData) {
// Save account details
const id = cookies().get('userId')?.value
const password = await hashPassword(
data.get('password').toString()
);
await prisma.user.update({
where: { id },
data: {
username: data.get('username').toString(),
password,
},
})
}
// components/WizardStep.tsx
import React from 'react'
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
type Props = {
title: string;
onSubmit: (data: FormData) => Promise<void>
children: React.ReactNode
}
export default function FormStep({ title, onSubmit, children }: Props) {
const { pending } = useFormStatus();
return (
<form
action={async (formData) => {
await onSubmit(formData);
}}
>
<h2>{title}</h2>
{children}
<button
type="submit"
className={pending ? 'button-submitting' : 'button'}
>
Next
</button>
</form>
)
}
// ./PersonalInformationStep.tsx
import React from 'react'
import WizardStep from '../WizardStep'
type Props = {
onSubmit: (data: FormData) => Promise<void>
}
export default function PersonalInformationStep({ onSubmit }: Props) {
return (
<WizardStep title="Personal Information" onSubmit={onSubmit}>
<label htmlFor="firstName">First Name:</label>
<input type="text" id="firstName" name="firstName" required />
<label htmlFor="lastName">Last Name:</label>
<input type="text" id="lastName" name="lastName" required />
</WizardStep>
)
}
เหตุใดการกระทำของเซิร์ฟเวอร์จึงเปลี่ยนแปลงเกม
ก่อนที่เราจะสรุป เรามาใช้เวลาสักครู่เพื่อพูดคุยกันว่าทำไม Next.js Server Actions จึงเป็นส่วนเสริมที่ทรงพลังสำหรับระบบนิเวศของ Next.js และวิธีที่พวกมันพร้อมที่จะกำหนดแนวทางปฏิบัติที่ดีที่สุดสำหรับการพัฒนาเว็บใหม่
- JavaScript ฝั่งไคลเอ็นต์ที่ลดลง: Next.js และ React กำลังเคลื่อนที่ไปในทิศทางของการย่อขนาดบันเดิลฝั่งไคลเอ็นต์ให้เล็กที่สุดโดยการถ่ายลอจิกไปยังเซิร์ฟเวอร์ Next.js ช่วยให้เราสามารถย้ายโค้ดจำนวนมากไปยังเซิร์ฟเวอร์ได้ แต่เรายังคงสร้างแบบฟอร์มบนไคลเอ็นต์เป็นส่วนใหญ่ ที่เปลี่ยนไปตามการกระทำของเซิร์ฟเวอร์!
- ปรับปรุงประสบการณ์ผู้ใช้: ด้วยคุณสมบัติต่างๆ เช่น การอัปเดตในแง่ดีและการเพิ่มประสิทธิภาพแบบก้าวหน้า นักพัฒนาสามารถสร้างแอปพลิเคชันที่ให้ความรู้สึกตอบสนองและราบรื่นยิ่งขึ้น ผู้ใช้ไม่จำเป็นต้องรอการตอบกลับของเซิร์ฟเวอร์อีกต่อไปเพื่อดูผลกระทบของการกระทำของพวกเขา และแบบฟอร์มยังคงโต้ตอบได้แม้ไม่มี JavaScript ซึ่งนำไปสู่ประสบการณ์ผู้ใช้โดยรวมที่ดีขึ้นก่อนที่ไคลเอ็นต์จะไฮเดรต
- ประสบการณ์ที่ยอดเยี่ยมสำหรับนักพัฒนาซอฟต์แวร์: Server Actions ทำให้การถ่ายโอนการอัปเดตข้อมูลไปยังเซิร์ฟเวอร์เป็นไปอย่างราบรื่น ซึ่งก่อนหน้านี้จำเป็นต้องใช้ API API ที่ Next.js และ React มอบให้ทำให้ง่ายต่อการใช้งาน
โดยสรุป Next.js Server Actions เป็นคุณลักษณะที่เปลี่ยนแปลงเกมที่มีศักยภาพในการปฏิวัติวิธีที่นักพัฒนาเว็บจัดการกับการเปลี่ยนแปลงข้อมูลฝั่งเซิร์ฟเวอร์และการจัดการแบบฟอร์ม ด้วยการนำเสนอประสบการณ์การพัฒนาที่ตอบสนอง ปรับขนาดได้ และมีความคล่องตัวมากขึ้น Server Actions จะกลายเป็นเครื่องมือที่ขาดไม่ได้สำหรับการพัฒนาเว็บสมัยใหม่