[Next.js] Server Actions

파이리·2023년 8월 2일
0

Next.js

목록 보기
18/18
post-thumbnail

서버 액션은 React 액션을 기반으로 구축한 Next.js의 알파 기능입니다. 서버 측 데이터 변경, 클라이언트 측 자바스크립트 감소, 점진적으로 개선된 폼을 지원합니다. 서버 컴포넌트 내부에서 정의하거나 클라이언트 컴포넌트에서 호출할 수 있습니다.

서버 컴포넌트에서

import { cookies } from 'next/headers'
 
// Server action defined inside a Server Component
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>
  )
}

클라이언트 컴포넌트에서

// app/action.js
'use server'
 
export async function addItem(data) {
  const cartId = cookies().get('cartId')?.value
  await saveToDb({ cartId, data })
}
// app/add-to-cart.js
'use client'
 
import { addItem } from './actions.js'
 
// Server Action being called inside a Client Component
export default function AddToCart({ productId }) {
  return (
    <form action={addItem}>
      <button type="submit">Add to Cart</button>
    </form>
  )
}

Good to Know

  • 서버 액션을 사용하면 React experimental 채널이 실행됩니다.
  • React Actions, useOptimistic, useFormStatus는 Next.js 또는 React Server 컴포넌트 전용 기능이 아닙니다.
  • Next.js는 revalidateTagrevalidatePath와 같은 데이터 변이 API를 추가하는 것을 포함해 React 액션을 Next.js 라우터, 번들러 및 캐싱 시스템에 통합합니다.

실험용 serverActions 플래그를 활성화아여 Next.js 프로젝트에서 서버 액션을 활성화할 수 있습니다.

module.exports = {
  experimental: {
    serverActions: true,
  },
}

생성

서버 액션을 두 곳에서 정의할 수 있습니다.

  • 이를 사용하는 컴포넌트 내부 (서버 컴포넌트만 해당)
  • 분리된 서버 액션 파일에 정의하고, 클라이언트 컴포넌트로 가져옵니다. 한 파일에 여러 액션을 정의할 수 있습니다.

서버 컴포넌트에서

함수 본문 상단에 'use server' 지시문을 사용하여 비동기 함수를 정의하여 서버 액션을 생성합니다. 이 함수는 직렬화 가능한 인자와 React 서버 컴포넌트 프로토콜에 기반한 직렬화 가능한 반환값을 가져야 합니다.

export default function ServerComponent() {
  async function myAction() {
    'use server'
    // ...
  }
}

클라이언트 컴포넌트에서

클라이언트 컴포넌트 내에서 서버 액션을 사용하는 경우, 파일 상단에 'use server' 지시문을 사용하는 별도의 파일에 액션을 생성합니다. 그리고 서버 액션을 클라이언트 파일로 가져옵니다.

// app/actions.js
'use server'
 
export async function myAction() {
  // ...
}
// app/client-component.js
'use client'
 
import { myAction } from './actions'
 
export default function ClientComponent() {
  return (
    <form action={myAction}>
      <button type="submit">Add to Cart</button>
    </form>
  )
}

호출

다음 방법을 사용하여 서버 액션을 호출할 수 있습니다.

  • action 사용 : React의 action 프로퍼티를 사용하면 <form> 엘리먼트에서 서버 액션을 호출할 수 있습니다.
  • formAction 사용 : React의 formAction 프로퍼티를 사용하면 <form>에서 <button>, <input type='submit'>, <input type='image'> 요소를 처리할 수 있습니다.
  • startTransition을 사용한 사용자 정의 호출 : startTransition 을 사용하여 action 또는 formAction을 사용하지 않고 서버 액션을 호출합니다. 이 방법은 프로그래시브 향상을 비활성화합니다.

action

React action 프로퍼티를 사용해 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>
  )
}

formAction

formAction 프로퍼티를 사용하여 button 또는, input type='submit', input type='image' 와 같은 요소에 대한 폼 액션을 처리할 수 있습니다. formAction 프로퍼티는 form 요소의 action보다 우선 시 됩니다.

export default function Form() {
  async function handleSubmit() {
    'use server'
    // ...
  }
 
  async function submitImage() {
    'use server'
    // ...
  }
 
  return (
    <form action={handleSubmit}>
      <input type="text" name="name" />
      <input type="image" formAction={submitImage} />
      <button type="submit">Submit</button>
    </form>
  )
}

startTransiton을 사용한 사용자 지정 호출

action 또는 formAction을 사용하지 않고 서버 액션을 호출할 수도 있습니다. 이 작업은 useTransition 훅에서 제공하는 startTransition을 사용하여 수행할 수 있으며, 이는 form, button 또는 inputs에서 서버 액션을 사용하려는 경우에 유용할 수 있습니다.

'use client'
 
import { useTransition } from 'react'
import { addItem } from '../actions'
 
function ExampleClientComponent({ id }) {
  let [isPending, startTransition] = useTransition()
 
  return (
    <button onClick={() => startTransition(() => addItem(id))}>
      Add To Cart
    </button>
  )
}
'use server'
 
export async function addItem(id) {
  await addItemToDb(id)
  // Marks all product pages for revalidating
  revalidatePath('/product/[id]')
}

startTransiton 없이 사용자 지정 호출

Server Mutations을 수행하지 않을 경우 다른 함수처럼 함수를 직접 프로퍼티로 전달할 수 있습니다.

import kv from '@vercel/kv'
import LikeButton from './like-button'
 
export default function Page({ params }: { params: { id: string } }) {
  async function increment() {
    'use server'
    await kv.incr(`post:id:${params.id}`)
  }
 
  return <LikeButton increment={increment} />
}
'use client'
 
export default function LikeButton({
  increment,
}: {
  increment: () => Promise<void>
}) {
  return (
    <button
      onClick={async () => {
        await increment()
      }}
    >
      Like
    </button>
  )
}

향상된 기능

실험적 useOptimistic

실험적 useOptimistic 훅은 애플리케이션에서 낙관적 업데이트를 구현하는 방법을 제공합니다. 낙관적 업데이트는 앱의 반응성을 높여 사용자 경험을 향상시키는 기술입니다.

서버 액션이 호출되면 응답을 기다리지 예상 결과를 반환하여 UI가 즉시 업데이트 됩니다.

'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>
  )
}

실험적 useFormStatus

실험적 useFormStatus 훅을 Form Action 내에서 사용할 수 있으며 pending 프로퍼티를 제공합니다.

'use client'
 
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
 
function Submit() {
  const { pending } = useFormStatus()
 
  return (
    <input
      type="submit"
      className={pending ? 'button-pending' : 'button-normal'}
      disabled={pending}
    >
      Submit
    </input>
  )
}

점진적 향상

점진적 향상 기능을 사용하면 자바스크립트가 없거나 자바스크립트가 비활성된 상태에서도 <form>이 제대로 작동할 수 있습니다. 이를 통해 사용자는 form의 JavaScript가 아직 로드되지 않았거나 로드에 실패하더라도 양식과 상호 작용하고 데이터를 제출할 수 있습니다.

서버 Form Action과 클라이언트 Form Action 모두 두 가지 전략 중 하나를 사용하여 점진적 향상을 지원합니다.

  • 서버 액션이 <form>에 직접 전달되는 경우, 자바스크립트가 비활성화되어 있어도 form은 대화형입니다.

  • 클라이언트 액션이 <form>으로 전달되는 경우 form은 여전히 대화형이지만 양식이 하이드레이트될 때까지 액션이 대기열에 배치됩니다. <form>은 Selective Hydration으로 우선 순위가 지정되므로 빠르게 처리됩니다.

'use client'
 
import { useState } from 'react'
import { handleSubmit } from './actions.js'
 
export default function ExampleClientComponent({ myAction }) {
  const [input, setInput] = useState()
 
  return (
    <form action={handleSubmit} onChange={(e) => setInput(e.target.value)}>
      {/* ... */}
    </form>
  )
}

두 경우 모두 form은 하이드레이션이 발생하기 전에 대화형입니다. 서버 액션은 클라이언트 자바스크립트에 의존하지 않는다는 추가적인 이점이 있지만, 원하는 경우 상호 작용을 유지하면서 클라이언트 액션으로 추가 동작을 구성할 수 있습니다.

크기 제한

기본적으로 서버 액션으로 전송되는 요청 본문의 최대 크기는 1MB입니다. 이렇게 하면 대량의 데이터가 서버로 전송되어 구문 분석에 많은 리소스를 소모하는 것을 방지할 수 있습니다.

그러나 실험적인 serverActionsBodySizeLimit 옵션을 사용하여 이 제한을 구성할 수 있습니다. 이 옵션은 바이트 수 또는 바이트에서 지원하는 모든 문자열 형식을 할 수 있습니다

module.exports = {
  experimental: {
    serverActions: true,
    serverActionsBodySizeLimit: '2mb',
  },
}
profile
프론트엔드 개발자

0개의 댓글