React์ ์คํ์ API์ธ experimental_useFormStatus ํ ์ ์ฌ์ฉํ์ฌ ํผ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ฐ์ํํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. ์ค์ ์์ ๋ฅผ ํตํด ๊ตฌํ, ์ด์ , ๊ณ ๊ธ ์ฌ์ฉ๋ฒ์ ๋ฐฐ์ธ ์ ์์ต๋๋ค.
React experimental_useFormStatus ๊ตฌํ: ํฅ์๋ ํผ ์ํ ๊ด๋ฆฌ
๋์์์ด ๋ฐ์ ํ๋ React ์ํ๊ณ๋ ๊ฐ๋ฐ์ ๊ฒฝํ๊ณผ ์ ํ๋ฆฌ์ผ์ด์
์ฑ๋ฅ์ ๊ฐ์ ํ๊ธฐ ์ํ ๋๊ตฌ์ ๊ธฐ์ ์ ์ง์์ ์ผ๋ก ์ ๋ณด์ด๊ณ ์์ต๋๋ค. ๊ทธ์ค ํ๋์ธ ์คํ์ ๊ธฐ๋ฅ experimental_useFormStatus ํ
์ ํนํ ์๋ฒ ์ก์
(server actions) ๋ฐ ์ ์ง์ ํฅ์(progressive enhancement) ์๋๋ฆฌ์ค์์ ํผ ์ํ ๊ด๋ฆฌ๋ฅผ ๋จ์ํํ๋๋ก ์ค๊ณ๋์์ต๋๋ค. ์ด ์ข
ํฉ ๊ฐ์ด๋์์๋ experimental_useFormStatus ํ
์ ์์ธํ ์ดํด๋ณด๊ณ , ํจ๊ณผ์ ์ธ ํ์ฉ์ ์ํ ์ค์ฉ์ ์ธ ์์ ์ ํต์ฐฐ๋ ฅ์ ์ ๊ณตํฉ๋๋ค.
experimental_useFormStatus๋ ๋ฌด์์ธ๊ฐ?
experimental_useFormStatus ํ
์ React ํ์ด ๋์
ํ ์คํ์ API๋ก, ํนํ ์๋ฒ ์ก์
์ ์ฌ์ฉํ ๋ ํผ ์ ์ถ ์ํ๋ฅผ ๋ ๊ฐ๋จํ๊ฒ ์ถ์ ํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. ์ด ํ
์ด ๋ฑ์ฅํ๊ธฐ ์ ์๋ ํผ์ ์ฌ๋ฌ ์ํ(์ ํด, ์ ์ถ ์ค, ์ฑ๊ณต, ์ค๋ฅ)๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด ๋ณต์กํ ์ํ ๊ด๋ฆฌ ๋ก์ง์ด ํ์ํ ๊ฒฝ์ฐ๊ฐ ๋ง์์ต๋๋ค. experimental_useFormStatus๋ ์ด๋ฌํ ๋ณต์ก์ฑ์ ์๋น ๋ถ๋ถ์ ์ถ์ํํ์ฌ ํผ ์ ์ถ ์ํ๋ฅผ ๋ชจ๋ํฐ๋งํ๊ณ ์ด์ ๋ฐ์ํ๋ ๊ฐ๋จํ๊ณ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ ๊ฒ์ ๋ชฉํ๋ก ํฉ๋๋ค.
์ฃผ์ ์ด์ :
- ๊ฐ์ํ๋ ์ํ ๊ด๋ฆฌ: ํผ ์ ์ถ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐ ํ์ํ ์์ฉ๊ตฌ ์ฝ๋๋ฅผ ์ค์ฌ์ค๋๋ค.
- ํฅ์๋ ์ฌ์ฉ์ ๊ฒฝํ: ํผ์ ์ํ์ ๋ฐ๋ผ ๋ ๋ฐ์์ ์ธ UI ์ ๋ฐ์ดํธ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
- ๊ฐ์ ๋ ์ฝ๋ ๊ฐ๋ ์ฑ: ํผ ๊ด๋ จ ์ฝ๋๋ฅผ ๋ ์ฝ๊ฒ ์ดํดํ๊ณ ์ ์ง๋ณด์ํ ์ ์๋๋ก ๋ง๋ญ๋๋ค.
- ์๋ฒ ์ก์ ๊ณผ์ ์ํํ ํตํฉ: ํนํ React ์๋ฒ ์ปดํฌ๋ํธ ๋ฐ ์๋ฒ ์ก์ ๊ณผ ์ ์๋ํ๋๋ก ์ค๊ณ๋์์ต๋๋ค.
๊ธฐ๋ณธ ๊ตฌํ
experimental_useFormStatus์ ๊ธฐ๋ณธ ๊ตฌํ์ ์ค๋ช
ํ๊ธฐ ์ํด ๊ฐ๋จํ ๋ฌธ์ ์์ ์์ ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ด ์์์ ์ฌ์ฉ์์ ์ด๋ฆ, ์ด๋ฉ์ผ, ๋ฉ์์ง๋ฅผ ์์งํ ๋ค์ ์๋ฒ ์ก์
์ ์ฌ์ฉํ์ฌ ์ ์ถํฉ๋๋ค.
์ฌ์ ์ค๋น ์ฌํญ
์ฝ๋๋ฅผ ์ดํด๋ณด๊ธฐ ์ ์ ๋ค์ ์ฌํญ์ด ํฌํจ๋ React ํ๋ก์ ํธ๊ฐ ์ค์ ๋์ด ์๋์ง ํ์ธํ์ธ์.
- ์คํ์ API๋ฅผ ์ง์ํ๋ React ๋ฒ์ (ํ์ํ ๋ฒ์ ์ React ๊ณต์ ๋ฌธ์์์ ํ์ธํ์ธ์).
- React ์๋ฒ ์ปดํฌ๋ํธ ํ์ฑํ (์ผ๋ฐ์ ์ผ๋ก Next.js๋ Remix์ ๊ฐ์ ํ๋ ์์ํฌ์์ ์ฌ์ฉ๋จ).
์์ : ๊ฐ๋จํ ๋ฌธ์ ์์
๋ค์์ ๊ธฐ๋ณธ์ ์ธ ๋ฌธ์ ์์ ์ปดํฌ๋ํธ์ ๋๋ค.
```jsx // app/actions.js (์๋ฒ ์ก์ ) 'use server' export async function submitContactForm(formData) { // ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ์ถ ๋๋ API ์์ฒญ ์๋ฎฌ๋ ์ด์ await new Promise(resolve => setTimeout(resolve, 2000)); const name = formData.get('name'); const email = formData.get('email'); const message = formData.get('message'); if (!name || !email || !message) { return { success: false, message: '๋ชจ๋ ํ๋๋ ํ์์ ๋๋ค.' }; } try { // ์ค์ API ํธ์ถ ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ์ผ๋ก ๋์ฒด console.log('ํผ ์ ์ถ๋จ:', { name, email, message }); return { success: true, message: 'ํผ์ด ์ฑ๊ณต์ ์ผ๋ก ์ ์ถ๋์์ต๋๋ค!' }; } catch (error) { console.error('ํผ ์ ์ถ ์ค๋ฅ:', error); return { success: false, message: 'ํผ ์ ์ถ์ ์คํจํ์ต๋๋ค.' }; } } // app/components/ContactForm.jsx (ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ) 'use client' import { experimental_useFormStatus as useFormStatus } from 'react' import { submitContactForm } from '../actions' function SubmitButton() { const { pending } = useFormStatus() return ( ) } export default function ContactForm() { return ( ); } ```์ค๋ช
- ์๋ฒ ์ก์
(
app/actions.js): ์ด ํ์ผ์ ์๋ฒ ์ก์ ์ธsubmitContactFormํจ์๋ฅผ ์ ์ํฉ๋๋ค. ํผ ์ ์ถ์ ๋น๋๊ธฐ์ ํน์ฑ์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด 2์ด ์ง์ฐ์ผ๋ก API ์์ฒญ์ ์๋ฎฌ๋ ์ด์ ํฉ๋๋ค. ๋ํ ๊ธฐ๋ณธ์ ์ธ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ์ค๋ฅ ์ฒ๋ฆฌ๋ ๋ด๋นํฉ๋๋ค. - ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ (
app/components/ContactForm.jsx): ์ด ํ์ผ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์ธContactForm์ปดํฌ๋ํธ๋ฅผ ์ ์ํฉ๋๋ค.experimental_useFormStatusํ ๊ณผsubmitContactForm์ก์ ์ ๊ฐ์ ธ์ต๋๋ค. useFormStatus์ฌ์ฉ๋ฒ:SubmitButton์ปดํฌ๋ํธ ๋ด๋ถ์์useFormStatus๊ฐ ํธ์ถ๋ฉ๋๋ค. ์ด ํ ์ ํผ์ ์ ์ถ ์ํ์ ๋ํ ์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค.pending์์ฑ:useFormStatus๊ฐ ๋ฐํํ๋pending์์ฑ์ ํผ์ด ํ์ฌ ์ ์ถ ์ค์ธ์ง ์ฌ๋ถ๋ฅผ ๋ํ๋ ๋๋ค. ์ด ์์ฑ์ ์ ์ถ ๋ฒํผ์ ๋นํ์ฑํํ๊ณ "์ ์ถ ์ค..." ๋ฉ์์ง๋ฅผ ํ์ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.- ํผ ๋ฐ์ธ๋ฉ:
form์์์action์์ฑ์submitContactForm์๋ฒ ์ก์ ์ ๋ฐ์ธ๋ฉ๋ฉ๋๋ค. ์ด๋ ํผ์ด ์ ์ถ๋ ๋ React๊ฐ ํด๋น ์๋ฒ ์ก์ ์ ํธ์ถํ๋๋ก ์ง์ํฉ๋๋ค.
๊ณ ๊ธ ์ฌ์ฉ๋ฒ ๋ฐ ๊ณ ๋ ค ์ฌํญ
์ฑ๊ณต ๋ฐ ์ค๋ฅ ์ํ ์ฒ๋ฆฌ
experimental_useFormStatus๋ ์ ์ถ ์ํ ์ถ์ ์ ๋จ์ํํ์ง๋ง, ์ฑ๊ณต ๋ฐ ์ค๋ฅ ์ํ๋ ๋ช
์์ ์ผ๋ก ์ฒ๋ฆฌํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์๋ฒ ์ก์
์ ์ฑ๊ณต ๋๋ ์คํจ๋ฅผ ๋ํ๋ด๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ ์ ์์ผ๋ฉฐ, ์ด๋ฅผ ์ฌ์ฉํ์ฌ UI๋ฅผ ์ ์ ํ๊ฒ ์
๋ฐ์ดํธํ ์ ์์ต๋๋ค.
์์ :
```jsx // app/components/ContactForm.jsx (์์ ๋จ) 'use client' import { experimental_useFormStatus as useFormStatus } from 'react' import { submitContactForm } from '../actions' import { useState } from 'react'; function SubmitButton() { const { pending } = useFormStatus() return ( ) } export default function ContactForm() { const [message, setMessage] = useState(null); async function handleSubmit(formData) { const result = await submitContactForm(formData); setMessage(result); } return ({message.message}
)}์ค๋ช :
- ๋ฉ์์ง ์ํ: ์๋ฒ ์ก์
์์ ๋ฐํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ๊ธฐ ์ํด
message์ํ ๋ณ์๊ฐ ๋์ ๋์์ต๋๋ค. - ๊ฒฐ๊ณผ ์ฒ๋ฆฌ: ํผ์ด ์ ์ถ๋ ํ,
handleSubmitํจ์๋ ์๋ฒ ์ก์ ์ ๊ฒฐ๊ณผ๋กmessage์ํ๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค. - ๋ฉ์์ง ํ์: ์ปดํฌ๋ํธ๋ ๊ฒฐ๊ณผ์
success์์ฑ์ ๋ฐ๋ผ ๋ฉ์์ง๋ฅผ ํ์ํ๋ฉฐ, ์ฑ๊ณต ๋ฐ ์ค๋ฅ ์ํ์ ๋ํด ๋ค๋ฅธ CSS ํด๋์ค๋ฅผ ์ ์ฉํฉ๋๋ค.
์ ์ง์ ํฅ์ (Progressive Enhancement)
experimental_useFormStatus๋ ์ ์ง์ ํฅ์ ์๋๋ฆฌ์ค์์ ํนํ ์ ์ฉํฉ๋๋ค. ํ์ค HTML ํผ์ React๋ก ์ ์ง์ ์ผ๋ก ํฅ์์ํค๋ฉด, JavaScript๊ฐ ์คํจํ๋๋ผ๋ ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ ์ ์งํ๋ฉด์ ๋ ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ต๋๋ค.
์์ :
๊ธฐ๋ณธ HTML ํผ์ผ๋ก ์์ํฉ๋๋ค:
```html ```๊ทธ๋ฐ ๋ค์ React์ experimental_useFormStatus๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ง์ ์ผ๋ก ํฅ์์ํฌ ์ ์์ต๋๋ค.
์ค๋ช :
- ์ด๊ธฐ HTML ํผ:
public/contact.htmlํ์ผ์๋ JavaScript ์์ด๋ ์๋ํ๋ ํ์ค HTML ํผ์ด ํฌํจ๋์ด ์์ต๋๋ค. - ์ ์ง์ ํฅ์:
EnhancedContactForm์ปดํฌ๋ํธ๋ HTML ํผ์ ์ ์ง์ ์ผ๋ก ํฅ์์ํต๋๋ค. JavaScript๊ฐ ํ์ฑํ๋๋ฉด React๊ฐ ์ ์ดํ์ฌ ๋ ํ๋ถํ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค. useFormStateํ : ํผ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ ์๋ฒ ์ก์ ์ ํผ์ ๋ฐ์ธ๋ฉํ๊ธฐ ์ํดuseFormState๋ฅผ ์ฌ์ฉํฉ๋๋ค.-
state:useFormState์์ ๋ฐํ๋state๋ ์ด์ ์๋ฒ ์ก์ ์ ๋ฐํ ๊ฐ์ ๋ด๊ณ ์์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ์ฑ๊ณต ๋๋ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
๊ตญ์ ํ ๊ณ ๋ ค ์ฌํญ
์ ์ธ๊ณ ์ฌ์ฉ์๋ฅผ ๋์์ผ๋ก ํผ์ ๊ตฌํํ ๋๋ ๋ค์๊ณผ ๊ฐ์ ์ฌ๋ฌ ๊ตญ์ ํ ๊ณ ๋ ค ์ฌํญ์ด ์์ต๋๋ค.
- ํ์งํ: ํผ ๋ ์ด๋ธ, ๋ฉ์์ง, ์ค๋ฅ ๋ฉ์์ง๊ฐ ๋ค๋ฅธ ์ธ์ด๋ก ํ์งํ๋์๋์ง ํ์ธํ์ธ์. i18next์ ๊ฐ์ ๋๊ตฌ๊ฐ ๋ฒ์ญ ๊ด๋ฆฌ์ ๋์์ด ๋ ์ ์์ต๋๋ค.
- ๋ ์ง ๋ฐ ์ซ์ ํ์: ์ฌ์ฉ์์ ๋ก์ผ์ผ์ ๋ฐ๋ผ ๋ ์ง ๋ฐ ์ซ์ ํ์์ ์ฒ๋ฆฌํ์ธ์.
Intl์ด๋moment.js(ํ์ฌ๋ ๋ ๊ฑฐ์๋ก ๊ฐ์ฃผ๋จ)์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ ์ง์ ์ซ์๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ํ์ํํ์ธ์. - ์ฃผ์ ํ์: ๊ตญ๊ฐ๋ง๋ค ์ฃผ์ ํ์์ด ๋ค๋ฆ ๋๋ค. ์ฌ๋ฌ ์ฃผ์ ํ์์ ์ง์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ฌ์ฉ์์ ์์น์ ๋ฐ๋ผ ๋ง์ถคํ ํผ ํ๋๋ฅผ ๋ง๋๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
- ์ ํ๋ฒํธ ์ ํจ์ฑ ๊ฒ์ฌ: ๊ตญ์ ํ์ค์ ๋ฐ๋ผ ์ ํ๋ฒํธ์ ์ ํจ์ฑ์ ๊ฒ์ฌํ์ธ์.
libphonenumber-js์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋์์ด ๋ ์ ์์ต๋๋ค. - ์ค๋ฅธ์ชฝ์์ ์ผ์ชฝ(RTL) ์ง์: ํผ ๋ ์ด์์์ด ์๋์ด๋ ํ๋ธ๋ฆฌ์ด์ ๊ฐ์ RTL ์ธ์ด๋ฅผ ์ง์ํ๋์ง ํ์ธํ์ธ์. ๋ ๋์ RTL ์ง์์ ์ํด CSS ๋
ผ๋ฆฌ์ ์์ฑ(์:
margin-left๋์margin-inline-start)์ ์ฌ์ฉํ์ธ์. - ์ ๊ทผ์ฑ: WCAG(์น ์ฝํ ์ธ ์ ๊ทผ์ฑ ๊ฐ์ด๋๋ผ์ธ)๋ฅผ ์ค์ํ์ฌ ์ฌ์ฉ์์ ์์น์ ๊ด๊ณ์์ด ์ฅ์ ๊ฐ ์๋ ์ฌ๋๋ค๋ ํผ์ ์ฌ์ฉํ ์ ์๋๋ก ํ์ธ์.
์์ : ํ์งํ๋ ํผ ๋ ์ด๋ธ
```jsx // i18n/locales/en.json { "contactForm": { "nameLabel": "์ด๋ฆ", "emailLabel": "์ด๋ฉ์ผ", "messageLabel": "๋ฉ์์ง", "submitButton": "์ ์ถ", "successMessage": "ํผ์ด ์ฑ๊ณต์ ์ผ๋ก ์ ์ถ๋์์ต๋๋ค!", "errorMessage": "ํผ ์ ์ถ์ ์คํจํ์ต๋๋ค." } } // i18n/locales/fr.json { "contactForm": { "nameLabel": "์ด๋ฆ", "emailLabel": "์ด๋ฉ์ผ", "messageLabel": "๋ฉ์์ง", "submitButton": "์ ์ถ", "successMessage": "ํผ์ด ์ฑ๊ณต์ ์ผ๋ก ์ ์ถ๋์์ต๋๋ค!", "errorMessage": "ํผ ์ ์ถ์ ์คํจํ์ต๋๋ค." } } // app/components/LocalizedContactForm.jsx 'use client' import { useTranslation } from 'react-i18next'; import { experimental_useFormStatus as useFormStatus } from 'react' import { submitContactForm } from '../actions' import { useState } from 'react'; function SubmitButton() { const { pending } = useFormStatus() const { t } = useTranslation(); return ( ) } export default function LocalizedContactForm() { const { t } = useTranslation(); const [message, setMessage] = useState(null); async function handleSubmit(formData) { const result = await submitContactForm(formData); setMessage(result); } return ({message.message}
)}์ค๋ช :
- ๋ฒ์ญ ํ์ผ: ์ด ์์ ๋
react-i18next๋ฅผ ์ฌ์ฉํ์ฌ ๋ฒ์ญ์ ๊ด๋ฆฌํฉ๋๋ค. ๋ณ๋์ JSON ํ์ผ์ ๋ค๋ฅธ ์ธ์ด์ ๋ํ ๋ฒ์ญ์ด ํฌํจ๋์ด ์์ต๋๋ค. useTranslationํ :useTranslationํ ์ ๋ฒ์ญ ํจ์(t)์ ๋ํ ์ ๊ทผ์ ์ ๊ณตํ๋ฉฐ, ์ด๋ ํ์งํ๋ ๋ฌธ์์ด์ ๊ฐ์ ธ์ค๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.- ํ์งํ๋ ๋ ์ด๋ธ: ํผ ๋ ์ด๋ธ๊ณผ ๋ฒํผ ํ
์คํธ๋
tํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒ์๋๋ฏ๋ก ์ฌ์ฉ์๊ฐ ์ ํธํ๋ ์ธ์ด๋ก ํ์๋ฉ๋๋ค.
์ ๊ทผ์ฑ ๊ณ ๋ ค ์ฌํญ
์ฅ์ ๊ฐ ์๋ ์ฌ์ฉ์๋ฅผ ํฌํจํ ๋ชจ๋ ์ฌ์ฉ์๊ฐ ํผ์ ์ ๊ทผํ ์ ์๋๋ก ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ๋ค์์ ์ฃผ์ ์ ๊ทผ์ฑ ๊ณ ๋ ค ์ฌํญ์ ๋๋ค.
- ์๋งจํฑ HTML:
<label>,<input>,<textarea>,<button>๊ณผ ๊ฐ์ ์๋งจํฑ HTML ์์๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉํ์ธ์. - ๋ ์ด๋ธ:
<label>์for์์ฑ๊ณผ ํผ ์ปจํธ๋กค์id์์ฑ์ ์ฌ์ฉํ์ฌ ๋ ์ด๋ธ์ ํผ ์ปจํธ๋กค๊ณผ ์ฐ๊ฒฐํ์ธ์. - ARIA ์์ฑ: ๋ณด์กฐ ๊ธฐ์ ์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ARIA ์์ฑ์ ์ฌ์ฉํ์ธ์. ์๋ฅผ ๋ค์ด,
aria-describedby๋ฅผ ์ฌ์ฉํ์ฌ ํผ ์ปจํธ๋กค์ ์ค๋ช ์ ์ฐ๊ฒฐํ์ธ์. - ์ค๋ฅ ์ฒ๋ฆฌ: ์ค๋ฅ๋ฅผ ๋ช
ํํ๊ฒ ํ์ํ๊ณ ์ ์ฉํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ ๊ณตํ์ธ์.
aria-invalid์ ๊ฐ์ ARIA ์์ฑ์ ์ฌ์ฉํ์ฌ ์ ํจํ์ง ์์ ํผ ์ปจํธ๋กค์ ํ์ํ์ธ์. - ํค๋ณด๋ ๋ด๋น๊ฒ์ด์
: ์ฌ์ฉ์๊ฐ ํค๋ณด๋๋ฅผ ์ฌ์ฉํ์ฌ ํผ์ ํ์ํ ์ ์๋๋ก ํ์ธ์. ํ์ํ ๊ฒฝ์ฐ
tabindex์์ฑ์ ์ฌ์ฉํ์ฌ ํฌ์ปค์ค ์์๋ฅผ ์ ์ดํ์ธ์. - ์์ ๋๋น: ํ ์คํธ์ ๋ฐฐ๊ฒฝ์ ๊ฐ์ ์ถฉ๋ถํ ์์ ๋๋น๋ฅผ ํ๋ณดํ์ธ์.
- ํผ ๊ตฌ์กฐ: ๋ช
ํํ๊ณ ์ผ๊ด๋ ํผ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ์ธ์.
<fieldset>๊ณผ<legend>์์๋ฅผ ์ฌ์ฉํ์ฌ ๊ด๋ จ๋ ํผ ์ปจํธ๋กค์ ๊ทธ๋ฃนํํ์ธ์.
์์ : ์ ๊ทผ์ฑ ์๋ ์ค๋ฅ ์ฒ๋ฆฌ
```jsx // app/components/AccessibleContactForm.jsx 'use client' import { experimental_useFormStatus as useFormStatus } from 'react' import { submitContactForm } from '../actions' import { useState } from 'react'; function SubmitButton() { const { pending } = useFormStatus() return ( ) } export default function AccessibleContactForm() { const [message, setMessage] = useState(null); const [errors, setErrors] = useState({}); async function handleSubmit(formData) { // ๊ธฐ๋ณธ์ ์ธ ํด๋ผ์ด์ธํธ ์ธก ์ ํจ์ฑ ๊ฒ์ฌ const newErrors = {}; if (!formData.get('name')) { newErrors.name = '์ด๋ฆ์ ํ์์ ๋๋ค.'; } if (!formData.get('email')) { newErrors.email = '์ด๋ฉ์ผ์ ํ์์ ๋๋ค.'; } if (!formData.get('message')) { newErrors.message = '๋ฉ์์ง๋ ํ์์ ๋๋ค.'; } if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } setErrors({}); // ์ด์ ์ค๋ฅ ์ง์ฐ๊ธฐ const result = await submitContactForm(formData); setMessage(result); } return ({message.message}
)}์ค๋ช :
- ์ค๋ฅ ์ํ: ์ปดํฌ๋ํธ๋ ์ ํจ์ฑ ๊ฒ์ฌ ์ค๋ฅ๋ฅผ ์ถ์ ํ๊ธฐ ์ํด
errors์ํ๋ฅผ ์ ์งํฉ๋๋ค. - ํด๋ผ์ด์ธํธ ์ธก ์ ํจ์ฑ ๊ฒ์ฌ:
handleSubmitํจ์๋ ํผ์ ์ ์ถํ๊ธฐ ์ ์ ๊ธฐ๋ณธ์ ์ธ ํด๋ผ์ด์ธํธ ์ธก ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํํฉ๋๋ค. - ARIA ์์ฑ: ํน์ ํผ ์ปจํธ๋กค์ ์ค๋ฅ๊ฐ ์๋ ๊ฒฝ์ฐ
aria-invalid์์ฑ์ดtrue๋ก ์ค์ ๋ฉ๋๋ค.aria-describedby์์ฑ์ ํผ ์ปจํธ๋กค์ ์ค๋ฅ ๋ฉ์์ง์ ์ฐ๊ฒฐํฉ๋๋ค. - ์ค๋ฅ ๋ฉ์์ง: ์ค๋ฅ ๋ฉ์์ง๋ ํด๋น ํผ ์ปจํธ๋กค ์์ ํ์๋ฉ๋๋ค.
์ ์ฌ์ ๊ณผ์ ๋ฐ ํ๊ณ
- ์คํ์ ์ํ: ์คํ์ API์ด๋ฏ๋ก
experimental_useFormStatus๋ ํฅํ React ๋ฒ์ ์์ ๋ณ๊ฒฝ๋๊ฑฐ๋ ์ ๊ฑฐ๋ ์ ์์ต๋๋ค. React์ ๊ณต์ ๋ฌธ์๋ฅผ ์ต์ ์ํ๋ก ์ ์งํ๊ณ ํ์ํ ๊ฒฝ์ฐ ์ฝ๋๋ฅผ ์์ ํ ์ค๋น๋ฅผ ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. - ์ ํ๋ ๋ฒ์: ์ด ํ ์ ์ฃผ๋ก ์ ์ถ ์ํ ์ถ์ ์ ์ค์ ์ ๋๋ฉฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ๊ฐ์ ํฌ๊ด์ ์ธ ํผ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ง๋ ์์ต๋๋ค. ์ด๋ฌํ ์ธก๋ฉด์ ์ํด์๋ ์ถ๊ฐ์ ์ธ ๋ก์ง์ ๊ตฌํํด์ผ ํ ์ ์์ต๋๋ค.
- ์๋ฒ ์ก์ ์์กด์ฑ: ์ด ํ ์ ์๋ฒ ์ก์ ๊ณผ ํจ๊ป ์๋ํ๋๋ก ์ค๊ณ๋์์ผ๋ฏ๋ก ๋ชจ๋ ์ฌ์ฉ ์ฌ๋ก์ ์ ํฉํ์ง ์์ ์ ์์ต๋๋ค. ์๋ฒ ์ก์ ์ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ ํผ ์ํ ๊ด๋ฆฌ๋ฅผ ์ํ ๋ค๋ฅธ ์๋ฃจ์ ์ ์ฐพ์์ผ ํ ์ ์์ต๋๋ค.
๊ฒฐ๋ก
experimental_useFormStatus ํ
์ React์์ ํผ ์ ์ถ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐ ์์ด, ํนํ ์๋ฒ ์ก์
๋ฐ ์ ์ง์ ํฅ์๊ณผ ํจ๊ป ์์
ํ ๋ ์๋นํ ๊ฐ์ ์ ์ ๊ณตํฉ๋๋ค. ์ํ ๊ด๋ฆฌ๋ฅผ ๋จ์ํํ๊ณ ๋ช
ํํ๊ณ ๊ฐ๊ฒฐํ API๋ฅผ ์ ๊ณตํจ์ผ๋ก์จ ๊ฐ๋ฐ์ ๊ฒฝํ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ชจ๋ ํฅ์์ํต๋๋ค. ๊ทธ๋ฌ๋ ์คํ์ ์ธ ํน์ฑ์ ๊ณ ๋ คํ ๋, ํฅํ React ๋ฒ์ ์ ์
๋ฐ์ดํธ ๋ฐ ์ ์ฌ์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ํ ์ ๋ณด๋ฅผ ๊ณ์ ํ์ธํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ด ํ
์ ์ด์ , ํ๊ณ, ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ดํดํจ์ผ๋ก์จ React ์ ํ๋ฆฌ์ผ์ด์
์์ ๋ ๊ฐ๋ ฅํ๊ณ ์ฌ์ฉ์ ์นํ์ ์ธ ํผ์ ๊ตฌ์ถํ๊ธฐ ์ํด experimental_useFormStatus๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ ์ ์์ต๋๋ค. ์ ์ธ๊ณ ์ฌ์ฉ์๋ฅผ ์ํ ํฌ์ฉ์ ์ธ ํผ์ ๋ง๋ค๊ธฐ ์ํด ๊ตญ์ ํ ๋ฐ ์ ๊ทผ์ฑ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๊ณ ๋ คํ๋ ๊ฒ์ ์์ง ๋ง์ธ์.