React ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ

'use server'๋Š” React ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

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


๋ ˆํผ๋Ÿฐ์Šค

'use server'

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

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

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

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

์ฃผ์˜ ์‚ฌํ•ญ

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

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

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

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

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

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

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

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

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

๋‹ค์Œ์€ ์„œ๋ฒ„ ํ•จ์ˆ˜์˜ ์ธ์ˆ˜๋กœ ์ง€์›๋˜๋Š” ํƒ€์ž…์ž…๋‹ˆ๋‹ค.

๋‹จ, ๋‹ค์Œ์€ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

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

์‚ฌ์šฉ๋ฒ•

Server Functions in forms

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

์—ฌ๊ธฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” ํผForm์ด ์žˆ์Šต๋‹ˆ๋‹ค.

// 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>์„ ํ†ตํ•œ ์„œ๋ฒ„ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ด ํผForm์„ ์ œ์ถœํ•˜๋ฉด ์„œ๋ฒ„ ํ•จ์ˆ˜์ธ requestUsername์— ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ํผForm์—์„œ ์„œ๋ฒ„ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ, React๋Š” ํผForm์˜ formData๋ฅผ ์„œ๋ฒ„ ํ•จ์ˆ˜์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

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

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

In the username request form, there might be the chance that a username is not available. requestUsername should tell us if it fails or not.

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

// 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 { useActionState } from 'react';
import requestUsername from './requestUsername';

function UsernameForm() {
const [state, action] = useActionState(requestUsername, null, 'n/a');

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

๋Œ€๋ถ€๋ถ„์˜ Hook๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ useActionState๋Š” ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<form>์™ธ๋ถ€์—์„œ ์„œ๋ฒ„ ํ•จ์ˆ˜ ํ˜ธ์ถœํ•˜๊ธฐ

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

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

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;
}

์„œ๋ฒ„ ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ๊ฐ’์„ ์ฝ์œผ๋ ค๋ฉด ๋ฐ˜ํ™˜๋œ Promise๋ฅผ await ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.