'use server' - This feature is available in the latest Canary

Canary

'use server'๋Š” React ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ๊ทธ์™€ ํ˜ธํ™˜๋˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋งŒ๋“ค ๋•Œ๋งŒ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

'use server'๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก ์ฝ”๋“œ์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋ฒ„ ์ธก ํ•จ์ˆ˜๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.


๋ ˆํผ๋Ÿฐ์Šค

'use server'

ํ•จ์ˆ˜๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—์„œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Œ์„ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด, ๋น„๋™๊ธฐ ํ•จ์ˆ˜์˜ ๋งจ ์œ„์— 'use server';๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์šฐ๋ฆฌ๋Š” ์ด๋ฅผ Server Actions ์ด๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค.

async function addToCart(data) {
'use server';
// ...
}

Server Action์„ ํด๋ผ์ด์–ธํŠธ์—์„œ ํ˜ธ์ถœํ•˜๋ฉด, ์ง๋ ฌํ™”๋œ ์ธ์ž์˜ ์‚ฌ๋ณธ์„ ํฌํ•จํ•˜๋Š” ์„œ๋ฒ„๋กœ์˜ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. Server Action์ด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ๊ทธ ๊ฐ’์€ ์ง๋ ฌํ™”๋˜๊ณ  ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

ํ•จ์ˆ˜ ๊ฐ๊ฐ์— 'use server'๋ฅผ ํ‘œ๊ธฐํ•˜๋Š” ๋Œ€์‹ , ํŒŒ์ผ์˜ ๋งจ ์œ„์— ์ง€์‹œ์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํŒŒ์ผ์˜ ๋ชจ๋“  export๋ฅผ, ํด๋ผ์ด์–ธํŠธ๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“  ๊ณณ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” Server Action์œผ๋กœ ํ‘œ๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Caveats

  • 'use server'๋Š” ํ•จ์ˆ˜ ๋˜๋Š” ๋ชจ๋“ˆ์˜ ๋งจ ์ฒ˜์Œ์— ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. import๋ฅผ ํฌํ•จํ•œ ๋‹ค๋ฅธ ์ฝ”๋“œ๋ณด๋‹ค ์œ„์— ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(์ง€์‹œ์–ด ์œ„์˜ ์ฃผ์„์€ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค). ๋ฐฑํ‹ฑ์ด ์•„๋‹Œ ๋‹จ์ผ ๋˜๋Š” ์ด์ค‘ ๋”ฐ์˜ดํ‘œ๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • 'use server'๋Š” ์„œ๋ฒ„ ์ธก ํŒŒ์ผ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์ธ Server Action์€ props๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ๊ณต๋˜๋Š” ์ง๋ ฌํ™” ํƒ€์ž…์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.
  • Server Action์„ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ import ํ•˜๊ธฐ ์œ„ํ•ด, ์ง€์‹œ์–ด๋Š” ๋ชจ๋“ˆ ์ˆ˜์ค€์—์„œ ์‚ฌ์šฉ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ์ด ํ•ญ์ƒ ๋น„๋™๊ธฐ์ด๋ฏ€๋กœ 'use server'๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•ญ์ƒ Server Action์˜ ์ธ์ž๋ฅผ ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์ž…๋ ฅ์œผ๋กœ ์ทจ๊ธ‰ํ•˜๊ณ  ๋ชจ๋“  ๋ณ€๊ฒฝ์„ ๊ฒ€ํ† ํ•˜์„ธ์š”. ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ์„ ํ™•์ธํ•˜์„ธ์š”.
  • Server Action์€ transition ์•ˆ์—์„œ ํ˜ธ์ถœ๋˜์–ด์•ผํ•ฉ๋‹ˆ๋‹ค. <form action> ๋˜๋Š” formAction๋กœ ์ „๋‹ฌ๋œ Server Action์€ ์ž๋™์œผ๋กœ transition ๋‚ด์—์„œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
  • Server Action์€ ์„œ๋ฒ„ ์ธก ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” mutation์„ ์œ„ํ•ด ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ ๋ฐ์ดํ„ฐ fetching์—๋Š” ๊ถŒ์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋ฒ„ ์•ก์…˜์„ ๊ตฌํ˜„ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์•ก์…˜์„ ์ฒ˜๋ฆฌํ•˜๋ฉฐ ๋ฐ˜ํ™˜ ๊ฐ’์„ ์บ์‹œํ•  ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค.

๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ

Server Action์— ๋Œ€ํ•œ ์ธ์ˆ˜๋Š” ์™„์ „ํžˆ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ œ์–ด๋ฉ๋‹ˆ๋‹ค. ๋ณด์•ˆ์„ ์œ„ํ•ด ํ•ญ์ƒ ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์ž…๋ ฅ์œผ๋กœ ์ทจ๊ธ‰ํ•˜์—ฌ, ์ธ์ž๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ๊ฒ€์ฆํ•˜๊ณ  ์ด์Šค์ผ€์ดํ”„ ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

Server Action์—์„œ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ํ•ด๋‹น ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ฐœ๋ฐœ์ค‘์ด์—์š”

Server Action์—์„œ ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด, ๊ณ ์œ ํ•œ ๊ฐ’๊ณผ ๊ฐ์ฒด๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๋กœ ์ „๋‹ฌ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์‹คํ—˜์ ์ธ ํ…Œ์ธํŠธ API๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

experimental_taintUniqueValue์™€ experimental_taintObjectReference๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

์ง๋ ฌํ™” ๊ฐ€๋Šฅ ์ธ์ˆ˜์™€ ๋ฐ˜ํ™˜ ๊ฐ’

ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๊ฐ€ ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„ ์ž‘์—…์„ ํ˜ธ์ถœํ•  ๋•Œ ์ „๋‹ฌ๋œ ์ธ์ˆ˜๋Š” ๋ชจ๋‘ ์ง๋ ฌํ™”๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์ง€์›๋˜๋Š” Server Action ์ธ์ž์˜ ํƒ€์ž…์ž…๋‹ˆ๋‹ค.

ํŠนํžˆ ๋‹ค์Œ์€ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • React ์—˜๋ฆฌ๋จผํŠธ ๋˜๋Š” JSX
  • ์ปดํฌ๋„ŒํŠธ ํ•จ์ˆ˜ ๋˜๋Š” Server Action์ด ์•„๋‹Œ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ํฌํ•จํ•˜๋Š” ํ•จ์ˆ˜
  • ํด๋ž˜์Šค
  • ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค์ธ ๊ฐ์ฒด(์–ธ๊ธ‰๋œ ๋‚ด์žฅ ๊ฐ์ฒด ์ œ์™ธ)๋˜๋Š” null ํ”„๋กœํ† ํƒ€์ž…์ด ์žˆ๋Š” ๊ฐœ์ฒด
  • ์ „์—ญ์— ๋“ฑ๋ก๋˜์ง€ ์•Š์€ Symbol, ์˜ˆ. Symbol('my new symbol')

์ง€์›๋˜๋Š” ์ง๋ ฌํ™” ๊ฐ€๋Šฅํ•œ ๋ฐ˜ํ™˜ ๊ฐ’์€ ๊ฒฝ๊ณ„ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์˜ ์ง๋ ฌํ™” ๊ฐ€๋Šฅํ•œ props์™€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ๋ฒ•

Server Action ํ˜•์‹

Server Action์˜ ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š”, ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” Server Function์„ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์—์„œ HTML form ์—˜๋ฆฌ๋จผํŠธ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ mutation์„ ์ œ์ถœํ•˜๋Š” ์ „ํ†ต์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. React ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋กœ, React๋Š” forms์—์„œ Server Action์— ๋Œ€ํ•œ ์ตœ์ƒ์˜ ์ง€์›์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” ์–‘์‹์ด ์žˆ์Šต๋‹ˆ๋‹ค.

// App.js

async function requestUsername(formData) {
'use server';
const username = formData.get('username');
// ...
}

export default function App() {
return (
<form action={requestUsername}>
<input type="text" name="username" />
<button type="submit">Request</button>
</form>
);
}

์˜ˆ์‹œ์—์„œ requestUsername๋Š” <form>๋ฅผ ํ†ตํ•œ Server Action์ด๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ด ์–‘์‹์„ ์ œ์ถœํ•˜๋ฉด ์„œ๋ฒ„ ํ•จ์ˆ˜์ธ requestUsername์— ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์žˆ์Šต๋‹ˆ๋‹ค. form์—์„œ Server Action์„ ํ˜ธ์ถœํ•  ๋•Œ React๋Š” form์˜ FormData๋ฅผ Server Action์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Server Action์„ from action์— ์ „๋‹ฌํ•˜์—ฌ, React๋Š” form์„ ์ ์ง„์  ํ–ฅ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฒˆ๋“ค์ด ๋กœ๋“œ๋˜๊ธฐ ์ „์— ์–‘์‹์„ ์ œ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

form์—์„œ ๋ฐ˜ํ™˜ ๊ฐ’ ์ฒ˜๋ฆฌ

username ์š”์ฒญ form์—์„œ, username์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์„ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. requestUsername์€ ์‹คํŒจ ์—ฌ๋ถ€๋ฅผ ์•Œ๋ ค์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ ์ง„์  ํ–ฅ์ƒ์„ ์ง€์›ํ•˜๋ฉฐ Server Action์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ UI๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ ค๋ฉด, useFormState๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

// requestUsername.js
'use server';

export default async function requestUsername(formData) {
const username = formData.get('username');
if (canRequest(username)) {
// ...
return 'successful';
}
return 'failed';
}
// UsernameForm.js
'use client';

import { useFormState } from 'react-dom';
import requestUsername from './requestUsername';

function UsernameForm() {
const [returnValue, action] = useFormState(requestUsername, 'n/a');

return (
<>
<form action={action}>
<input type="text" name="username" />
<button type="submit">Request</button>
</form>
<p>Last submission request returned: {returnValue}</p>
</>
);
}

๋Œ€๋ถ€๋ถ„์˜ Hook๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ useFormState๋Š” client code์—์„œ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<form>์™ธ๋ถ€์—์„œ Server Action ํ˜ธ์ถœํ•˜๊ธฐ

Server Action์€ ๋…ธ์ถœ๋œ ์„œ๋ฒ„ ์—”๋“œํฌ์ธํŠธ์ด๋ฉฐ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ ์–ด๋””์—์„œ๋‚˜ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

form ์™ธ๋ถ€์—์„œ Server Action์„ ์‚ฌ์šฉํ•  ๋•Œ, transition์—์„œ ์„œ๋ฒ„ ์•ก์…˜์„ ํ˜ธ์ถœํ•˜๋ฉด ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•˜๊ณ , ๋‚™๊ด€์  ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ํ‘œ์‹œํ•˜๋ฉฐ ์˜ˆ๊ธฐ์น˜ ์•Š์€ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Form์€ transition์˜ Server Action์„ ์ž๋™์œผ๋กœ ๋ž˜ํ•‘ํ•ฉ๋‹ˆ๋‹ค.

import incrementLike from './actions';
import { useState, useTransition } from 'react';

function LikeButton() {
const [isPending, startTransition] = useTransition();
const [likeCount, setLikeCount] = useState(0);

const onClick = () => {
startTransition(async () => {
const currentCount = await incrementLike();
setLikeCount(currentCount);
});
};

return (
<>
<p>Total Likes: {likeCount}</p>
<button onClick={onClick} disabled={isPending}>Like</button>;
</>
);
}
// actions.js
'use server';

let likeCount = 0;
export default async function incrementLike() {
likeCount++;
return likeCount;
}

Server Action ๋ฐ˜ํ™˜ ๊ฐ’์„ ์ฝ์œผ๋ ค๋ฉด ๋ฐ˜ํ™˜๋œ promise๋ฅผ await ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.