๐ 04. Form & Input ์์ ์ ๋ณต: ์ ๋ฐ ์๋ฒ ๋ฏฟ์ง ๋ง์ธ์
๐ ๊ฐ์
form method, input type, label, ์ ํจ์ฑ ๊ฒ์ฌ(Constraint Validation), React Hook Form๊น์ง โ ํผ ๊ฐ๋ฐ์ ๋ชจ๋ ๊ฒ์ ์๋ฆฌ๋ถํฐ ์ค์ ๊น์ง ๋ค๋ฃน๋๋ค.
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 20๋ถ / ํต์ฌ ํํธ๋ง: 12๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
[form ๊ธฐ๋ณธ ๊ตฌ์กฐ] โ [input type ์ด์ ๋ฆฌ] โ [label & ์ ๊ทผ์ฑ] โ [Constraint Validation] โ [React์์์ ํผ]
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
-
<form>์method,action,enctype์์ฑ์ด ์ธ์ ์ด๋ป๊ฒ ์ฐ์ด๋์ง ์ค๋ช ํ ์ ์๋ค. - ์ํฉ์ ๋ง๋
input type์ ์ ํํ์ฌ ๋ธ๋ผ์ฐ์ ๊ธฐ๋ณธ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ์ฉํ ์ ์๋ค. -
<label>๊ณผ<input>์ ์ฌ๋ฐ๋ฅธ ์ฐ๊ฒฐ ๋ฐฉ๋ฒ๊ณผ ์ ๊ทผ์ฑ ์ด์ ๋ฅผ ์ค๋ช ํ ์ ์๋ค. - HTML5 Constraint Validation๊ณผ React ํผ์ ๊ด๊ณ๋ฅผ ์ดํดํ๋ค.
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ๐ฃ ์์ฒ ( ์ ์ ): "์ํธ ๋, ํ์๊ฐ์ ํผ ๋ง๋ค์๋๋ฐ์. input ๋ง ๋ช ๊ฐ ๋ฃ๊ณ ๋ฒํผ ๋ฌ์์ด์. ๊ทผ๋ฐ ํ ์คํธํด๋ณด๋๊น ์๋ฌด ๊ฐ์ด๋ ๋ค ๋ค์ด๊ฐ์. ์ด๋ฉ์ผ ์นธ์ 'aaa' ๋ผ๊ณ ์จ๋ ์ ์ถ๋๊ณ , ๋น๋ฐ๋ฒํธ๊ฐ 3๊ธ์์ฌ๋ ์ ์ถ๋ผ์. JavaScript๋ก ์ผ์ผ์ด ๋ค ๊ฒ์ฌํด์ผ ํ๋์? ์ฝ๋๊ฐ ๋๋ฌด ๊ธธ์ด์ง ๊ฒ ๊ฐ์์์."
- ๐ฆ ์ํธ ( ๋ฆฌ๋ ): "์
๋ ฅ type ์ ์ ๋๋ก ์ ์ก์ผ๋ฉด ๊ทธ๋ ์ฃ .
<input type='email'>ํ๋๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฉ์ผ ํ์์ธ์ง ๋จผ์ ๊ฒ์ฌํด์ค์. ๊ทธ๋ฐ๋ฐ ๋ ์ค์ํ ๊ฒ ์์ด์ โ HTML ์ ํจ์ฑ ๊ฒ์ฌ๋ ๋ณด์กฐ ์๋จ์ผ ๋ฟ, ์๋ฒ ๊ฒ์ฌ๋ ์ ๋ ์๋ตํ๋ฉด ์ ๋ผ์. ํด๋ผ์ด์ธํธ ์ฝ๋๋ ๊ฐ๋ฐ์ ๋๊ตฌ๋ก ์ผ๋ง๋ ์ง ์ฐํํ ์ ์๊ฑฐ๋ ์."
๐ค ์ ์์์ผ ํ๋๊ฐ
์์๋ค ์ปค๋ฎค๋ํฐ ํ์๊ฐ์ ์คํ ์ฒซ๋ . ์์(PM)๊ฐ ๊ธํ๊ฒ ์ฌ๋ ๋ฉ์์ง๋ฅผ ๋ ๋ ธ๋ค.
๐ ์์(PM): "์์ฒ ๋, DB์ ์ด๋ฉ์ผ ์๋ ์ฌ์ฉ์๊ฐ ์๋ฐฑ ๋ช ๋ค์ด์ ์์ด์. ๋น๋ฐ๋ฒํธ๊ฐ '1' ์ธ ๊ณ์ ๋ ์๊ณ ์. ์ด๋ป๊ฒ ๋ ๊ฑฐ์์???"
์ด์ ๋ ๊ฐ๋จํ๋ค. ์์ฒ ์ด์ ํผ:
<!-- โ ์์ฒ ์ด์ ์ต์ด ํ์๊ฐ์
ํผ -->
<form>
<input placeholder="์ด๋ฉ์ผ" />
<input placeholder="๋น๋ฐ๋ฒํธ" />
<button>๊ฐ์
ํ๊ธฐ</button>
</form>type์์ โ ์ด๋ฉ์ผ ํ์ ๊ฒ์ฌ ์์required์์ โ ๋น ๊ฐ ์ ์ถ ๊ฐ๋ฅminlength์์ โ "1" ๊ธ์ ๋น๋ฐ๋ฒํธ ๊ฐ๋ฅname์์ โ ์๋ฒ์์ ์ด๋ค ํ๋์ธ์ง ์ ์ ์์
๐๏ธ 1. form ๊ธฐ๋ณธ ๊ตฌ์กฐ์ ํต์ฌ ์์ฑ
<form
action="/api/signup" <!-- ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ URL (์๋ต ์ ํ์ฌ URL) -->
method="post" <!-- HTTP ๋ฉ์๋: get | post | dialog -->
enctype="application/x-www-form-urlencoded" <!-- ํ์ผ ์
๋ก๋ ์ multipart/form-data -->
novalidate <!-- ๋ธ๋ผ์ฐ์ ๊ธฐ๋ณธ ์ ํจ์ฑ ๊ฒ์ฌ ๋๊ธฐ (์ปค์คํ
๊ฒ์ฌ ์ ์ฌ์ฉ) -->
>
...
</form>method ์ ํ ๊ธฐ์ค:
| method | ๋ฐ์ดํฐ ์์น | ์ฌ์ฉ ์ผ์ด์ค |
|---|---|---|
get | URL ์ฟผ๋ฆฌ์คํธ๋ง ?name=value | ๊ฒ์, ํํฐ (๋ถ๋งํฌ/๊ณต์ ๊ฐ๋ฅํด์ผ ํ ๋) |
post | ์์ฒญ body | ๋ก๊ทธ์ธ, ํ์๊ฐ์ , ๊ฒ์๊ธ ์์ฑ (๋ฏผ๊ฐ ๋ฐ์ดํฐ) |
ํ์ผ ์ ๋ก๋ ํ์ ๊ท์น:
<!-- ํ์ผ์ ํฌํจํ ํผ์ ๋ฐ๋์ enctype="multipart/form-data" -->
<form method="post" enctype="multipart/form-data">
<input type="file" name="avatar" accept="image/*" />
</form>๐ฅ 2. input type ์ด์ ๋ฆฌ โ ์ธ์๋ก ๋ธ๋ผ์ฐ์ ๊ฐ ์ผํด์ค๋ค
<!-- โ
ํ์
์ ์ ๋๋ก ์ฐ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ๊ธฐ๋ณธ ๊ฒ์ฌ๋ฅผ ํด์ค -->
<form method="post" action="/api/signup">
<!-- ์ด๋ฉ์ผ ํ์ ์๋ ๊ฒ์ฌ + ๋ชจ๋ฐ์ผ์์ ์ด๋ฉ์ผ ํค๋ณด๋ ํ์ -->
<input type="email" name="email" required />
<!-- ๋น๋ฐ๋ฒํธ: ์
๋ ฅ๊ฐ *๋ก ๋ง์คํน -->
<input type="password" name="password" minlength="8" required />
<!-- ์ซ์ ๋ฒ์ ์ ํ + ๋ชจ๋ฐ์ผ ์ซ์ ํค๋ณด๋ -->
<input type="number" name="age" min="14" max="120" />
<!-- ๋ ์ง ์ ํ UI ์๋ ํ์ -->
<input type="date" name="birthdate" />
<!-- ์ฒดํฌ๋ฐ์ค -->
<input type="checkbox" name="terms" required />
<!-- ๋ผ๋์ค: ๊ฐ์ name๋ผ๋ฆฌ ๊ทธ๋ฃน -->
<input type="radio" name="gender" value="male" />
<input type="radio" name="gender" value="female" />
<!-- ํ์ผ ์ ํ, accept๋ก ํ์ฉ ํ์
์ ํ -->
<input type="file" name="photo" accept="image/png, image/jpeg" />
<!-- ๊ฒ์์ฉ: clear(X) ๋ฒํผ ๊ธฐ๋ณธ ์ ๊ณต -->
<input type="search" name="q" />
<!-- ์จ๊ฒจ์ง ๋ฐ์ดํฐ ์ ์ก -->
<input type="hidden" name="csrf_token" value="abc123" />
<button type="submit">์ ์ถ</button>
</form>์์ฃผ ์ฐ๋ input ์ ํจ์ฑ ์์ฑ:
| ์์ฑ | ๋์ | ์ค๋ช |
|---|---|---|
required | ๋๋ถ๋ถ | ๋น์ด์์ผ๋ฉด ์ ์ถ ๋ถ๊ฐ |
minlength / maxlength | text, email, password | ์ต์/์ต๋ ๊ธ์์ |
min / max | number, date | ์ต์/์ต๋ ๊ฐ |
pattern | text, email, tel | ์ ๊ท์ ํจํด ๋งค์นญ |
placeholder | text ๊ณ์ด | ํํธ ํ ์คํธ (๋์ฒด์ฌ ์๋! label์ด ํ์) |
๐ท๏ธ 3. label โ ํด๋ฆญ ์์ญ ํ์ฅ + ์ ๊ทผ์ฑ์ ํต์ฌ
๐ฃ ์์ฒ : "label ์ด ์ ํ์์์? placeholder ๊ฐ ์ค๋ช ํด์ฃผ๋๊น label ์์ด๋ ๋์ง ์๋์?"
๐ฆ ์ํธ ๋ฆฌ๋: "placeholder ๋ ๊ฐ์ ์ ๋ ฅํ๋ฉด ์ฌ๋ผ์ ธ์. ์คํฌ๋ฆฐ๋ฆฌ๋๋ label ๋ก ์ด๋ค ํ๋์ธ์ง ์ฝ๊ฑฐ๋ ์. label ์์ผ๋ฉด 'ํธ์ง ์์'๋ผ๊ณ ๋ง ์ฝํ์ ์ฌ์ฉ์๊ฐ ๋ฌด์์ ์ ๋ ฅํด์ผ ํ ์ง ์ ์๊ฐ ์์ด์. ๊ทธ๋ฆฌ๊ณ label ์ ํด๋ฆญํ๋ฉด ์ฐ๊ฒฐ๋ input ์ ํฌ์ปค์ค๊ฐ ๊ฐ์ UX๋ ์ข์์ ธ์."
<!-- ๋ฐฉ๋ฒ 1: for-id ์ฐ๊ฒฐ (๊ถ์ฅ) -->
<label for="email">์ด๋ฉ์ผ</label>
<input type="email" id="email" name="email" required />
<!-- ๋ฐฉ๋ฒ 2: ๊ฐ์ธ๊ธฐ (id ์์ด๋ ๋จ) -->
<label>
๋น๋ฐ๋ฒํธ
<input type="password" name="password" minlength="8" required />
</label>
<!-- โ ์ ๋ ์ ๋๋ ๊ฒ: label ์์ด placeholder๋ง -->
<input type="text" placeholder="์ด๋ฆ์ ์
๋ ฅํ์ธ์" />
<!-- ์คํฌ๋ฆฐ๋ฆฌ๋๊ฐ "ํธ์ง ์์"๋ผ๊ณ ๋ง ์ฝ์ -->fieldset + legend โ ๊ด๋ จ ์ ๋ ฅ ๊ทธ๋ฃนํ:
<fieldset>
<legend>๋ฐฐ์ก์ง ์ ๋ณด</legend>
<label for="address">์ฃผ์</label>
<input type="text" id="address" name="address" required />
<label for="zip">์ฐํธ๋ฒํธ</label>
<input type="text" id="zip" name="zip" pattern="\d{5}" />
</fieldset>โ 4. Constraint Validation โ ๋ธ๋ผ์ฐ์ ๋ด์ฅ ์ ํจ์ฑ ๊ฒ์ฌ
HTML5๋ถํฐ JavaScript ์์ด ๊ธฐ๋ณธ ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ๊ฐ๋ฅํด. ํ์ง๋ง ํ๊ณ๋ ๋ช ํํ ์์์ผ ํด.
<!-- HTML๋ก๋ง ๊ตฌํํ๋ ๊ธฐ๋ณธ ์ ํจ์ฑ ๊ฒ์ฌ -->
<form method="post" action="/api/signup">
<label for="email">์ด๋ฉ์ผ</label>
<input
type="email"
id="email"
name="email"
required
/>
<label for="pw">๋น๋ฐ๋ฒํธ (8์ ์ด์)</label>
<input
type="password"
id="pw"
name="password"
minlength="8"
required
/>
<!-- pattern: ํ๊ตญ ์ ํ๋ฒํธ ํ์ (010-XXXX-XXXX) -->
<label for="tel">์ ํ๋ฒํธ</label>
<input
type="tel"
id="tel"
name="tel"
pattern="010-\d{4}-\d{4}"
title="010-XXXX-XXXX ํ์์ผ๋ก ์
๋ ฅํด์ฃผ์ธ์"
/>
<button type="submit">๊ฐ์
ํ๊ธฐ</button>
</form>Constraint Validation์ด ์ฒดํฌํ๋ ํญ๋ชฉ:
| ์ํ | JS API | ์๋ฏธ |
|---|---|---|
required ๋ฏธ์ถฉ์กฑ | validity.valueMissing | ํ์ ํญ๋ชฉ ๋น์ด์์ |
type ํ์ ๋ถ์ผ์น | validity.typeMismatch | email ํ์ ์ ์ด๋ฉ์ผ ์๋ ๊ฐ |
pattern ๋ถ์ผ์น | validity.patternMismatch | ์ ๊ท์ ํจํด ๋ฏธ์ถฉ์กฑ |
minlength ๋ฏธ๋ฌ | validity.tooShort | ์ต์ ๊ธ์์ ๋ฏธ๋ฌ |
min ๋ฏธ๋ฌ | validity.rangeUnderflow | ์ต์๊ฐ ๋ฏธ๋ฌ |
โ ๏ธ ํด๋ผ์ด์ธํธ ๊ฒ์ฌ๋ง์ผ๋ก ์ ๋ ๋ถ์กฑํด
DevTools์ Elements ํจ๋์์required์์ฑ์ ์ญ์ ํ๋ฉด ์ฐํ๊ฐ ๊ฐ๋ฅํด์.
Postman์ด๋ curl๋ก ์ง์ HTTP ์์ฒญ์ ๋ง๋ค๋ฉด HTML ํผ ์์ฒด๋ฅผ ์ ๊ฑฐ์ณ์.
ํด๋ผ์ด์ธํธ ๊ฒ์ฌ = UX ๊ฐ์ ์ฉ, ์๋ฒ ๊ฒ์ฌ = ๋ณด์ ํ์ ๋ก ์ดํดํ์ธ์.
โ๏ธ 5. React์์์ ํผ โ ์ ์ด ์ปดํฌ๋ํธ vs ๋น์ ์ด ์ปดํฌ๋ํธ
// โ ๋น์ ์ด ์ปดํฌ๋ํธ (Uncontrolled): React๊ฐ ํผ ๊ฐ์ ๋ชจ๋ฆ
function SignupForm() {
const emailRef = useRef<HTMLInputElement>(null);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// ์ ์ถ ์์ ์๋ง ref๋ก ๊ฐ ๊ฐ์ ธ์ด
console.log(emailRef.current?.value);
};
return (
<form onSubmit={handleSubmit}>
<input type="email" ref={emailRef} />
</form>
);
}
// โ
์ ์ด ์ปดํฌ๋ํธ (Controlled): React state๊ฐ ํผ ๊ฐ์ ์ ์ด
function SignupForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// ์ค์๊ฐ์ผ๋ก state์์ ์ต์ ๊ฐ ์ฌ์ฉ ๊ฐ๋ฅ
console.log({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">์ด๋ฉ์ผ</label>
<input
type="email"
id="email" {/* JSX์์ htmlFor, id๋ก label ์ฐ๊ฒฐ */}
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<label htmlFor="pw">๋น๋ฐ๋ฒํธ</label>
<input
type="password"
id="pw"
value={password}
onChange={(e) => setPassword(e.target.value)}
minLength={8} {/* JSX์์ camelCase */}
required
/>
<button type="submit">๊ฐ์
ํ๊ธฐ</button>
</form>
);
}๐ Next.js Server Actions ์ฐ๊ฒฐ ๊ณ ๋ฆฌ
Next.js App Router์์๋<form action={serverAction}>ํํ๋ก ์๋ฒ ํจ์๋ฅผ ์ง์ action์ ๋๊ธธ ์ ์์ด์.
JavaScript ์์ด๋ ํผ ์ ์ถ์ด ์๋ํ๋ Progressive Enhancement ํจํด์ด ๊ฐ๋ฅํด์ง๋๋ค.
๐ ํต์ฌ ํจํด ์ด์ ๋ฆฌ
| ์ํฉ | ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ |
|---|---|
| ์ด๋ฉ์ผ ์ ๋ ฅ | <input type="email" required> |
| ๋น๋ฐ๋ฒํธ ์ ๋ ฅ | <input type="password" minlength="8" required> |
| ํ์ผ ์ ๋ก๋ | <form enctype="multipart/form-data"> + <input type="file"> |
| label ์ฐ๊ฒฐ | <label for="id"> + <input id="id"> |
| ๊ด๋ จ ์ ๋ ฅ ๊ทธ๋ฃน | <fieldset> + <legend> |
| ๊ฒ์ ํผ | method="get" (URL ๊ณต์ ๊ฐ๋ฅ) |
| ๋ฏผ๊ฐ ๋ฐ์ดํฐ ์ ์ถ | method="post" (body์ ์จ๊ฒจ์ง) |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ์ด๋ฏธ์ง ํ์ผ ์ ๋ก๋ ๊ธฐ๋ฅ์ ํฌํจํ ๊ฒ์๊ธ ์์ฑ ํผ์ ๋ง๋ค ๋ ํ์๋ก ์ค์ ํด์ผ ํ๋ form ์์ฑ ์กฐํฉ์?
- A)
method="get"+enctype="text/plain" - B)
method="post"+enctype="multipart/form-data" - ๊ฐ)
method="get"+enctype="multipart/form-data" - ๋ผ)
method="post"+enctype="application/json"
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: ํ์ผ์ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ผ ์ผ๋ฐ
application/x-www-form-urlencoded๋ฐฉ์์ผ๋ก๋ ์ ์กํ ์ ์์ด์.multipart/form-data๋ ํ์ผ๊ณผ ํ ์คํธ ํ๋๋ฅผ ํจ๊ป ์ฌ๋ฌ ํํธ๋ก ๋๋ ์ ์กํ๋ ์ธ์ฝ๋ฉ์ ๋๋ค. ํ์ผ ์ ์ก์๋get๋ฐฉ์์ ์ฐ๋ฉด URL ๊ธธ์ด ์ ํ ๋๋ฌธ์ ๋ถ๊ฐ๋ฅํ๋ฏ๋กpost๊ฐ ํ์์์. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "ํ์ผ =
method='post'+enctype='multipart/form-data'"
Q2. ๋ค์ ์ค ์คํฌ๋ฆฐ๋ฆฌ๋ ์ ๊ทผ์ฑ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํํ input-label ์กฐํฉ์?
<!-- A -->
<input type="text" placeholder="์ด๋ฆ ์
๋ ฅ" />
<!-- B -->
<label for="name">์ด๋ฆ</label>
<input type="text" id="name" name="name" />
<!-- ๊ฐ -->
<p>์ด๋ฆ</p>
<input type="text" name="name" aria-label="์ด๋ฆ" />
<!-- ๋ผ -->
<label>์ด๋ฆ: <input type="text" name="name" /></label>โ ์ ๋ต: B์ ๋ผ ๋ชจ๋ ์ ๋ต
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: ์คํฌ๋ฆฐ๋ฆฌ๋๋ ํฌ์ปค์ค๊ฐ
<input>์ ์ฌ ๋ ์ฐ๊ฒฐ๋<label>ํ ์คํธ๋ฅผ ์ฝ์ด์ค๋๋ค.for+id๋ฐฉ์(B)๊ณผ input์ label๋ก ๊ฐ์ธ๋ ๋ฐฉ์(๋ผ) ๋ชจ๋ ์ฌ๋ฐ๋ฅธ ์ ๊ทผ๋ฒ์ด์์. - ์ค๋ต ํผ๋๋ฐฑ: A๋ placeholder๋ง ์์ผ๋ฉด ๊ฐ ์
๋ ฅ ์ placeholder๊ฐ ์ฌ๋ผ์ ธ ์คํฌ๋ฆฐ๋ฆฌ๋๊ฐ ์ด๋ค ํ๋์ธ์ง ์ ์ ์์ด์. ๊ฐ์
aria-label๋ ์๋์ ํ์ง๋ง,<p>ํ๊ทธ์ input์ ํ๋ก๊ทธ๋๋งคํฑ์ผ๋ก ์ฐ๊ฒฐ๋์ง ์์์. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "
<label>์๋<input>= ์๊ฐ์ฅ์ ์ธ์๊ฒ ๋ณด์ด์ง ์๋ ํผ"
Q3. ์์ฒ ์ด์ ํ ์คํธ ํ์
์์ฒ ์ด๊ฐ ํ์๊ฐ์ ํผ์ HTML5 Constraint Validation์ ์ ์ฉํด์ ๋ฐฐํฌํ๋ค.
์์(PM)๊ฐ "์ด์ ์ด์ํ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์ค๋ ์ผ ์๊ฒ ์ฃ ?" ๋ผ๊ณ ๋ฌป๋๋ค.
์์ฒ ์ด๋ ์ด๋ป๊ฒ ๋ตํด์ผ ํ ๊น?
โ ์ ๋ต: "HTML ํด๋ผ์ด์ธํธ ๊ฒ์ฌ๋ก ์ผ๋ฐ ์ฌ์ฉ์์ ์ค์๋ ๋ฐฉ์งํ ์ ์์ง๋ง, ์๋ฒ์์๋ ๋ฐ๋์ ๋์ผํ ์์ค์ ๊ฒ์ฌ๋ฅผ ํด์ผ ํฉ๋๋ค. ํด๋ผ์ด์ธํธ ์ฝ๋๋ DevTools๋ก ์ฐํํ ์ ์์ด์ ์๋ฒ ๊ฒ์ฌ ์์ด๋ ์ ์์ ์์ฒญ์ ๋ง์ ์ ์์ต๋๋ค."
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: HTML
required,minlength,pattern์์ฑ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์ ์ถ ์ ์ ๊ฒ์ฌํ๋ ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๊ฒ์ฌ์ ๋๋ค. ํ์ง๋ง DevTools์์ ํด๋น ์์ฑ์ ์ญ์ ํ๊ฑฐ๋, Postman ๊ฐ์ ๋๊ตฌ๋ก ์ง์ HTTP ์์ฒญ์ ๋ณด๋ด๋ฉด HTML ํผ์ ์์ ์ฐํํด์. ์ค์ ๋ณด์์ ์๋ฒ์์ ์ ๋ ฅ๊ฐ์ ์ฌ๊ฒ์ฆํ๋ ๊ฒ์ด ํ์์ ๋๋ค. - ์ค๋ต ํผ๋๋ฐฑ: ์์ฒ ๋, "HTML ๊ฒ์ฌ ๋ถ์์ผ๋ ์์ ํ๋ค"๋ ์๊ฐ์ SQL Injection, XSS ๊ณต๊ฒฉ์ ์ฒซ ๋ฒ์งธ ์คํด์์. ํด๋ผ์ด์ธํธ = ์ ๋ขฐ ๋ชป ํจ, ์๋ฒ = ์ตํ์ ๋ณด๋ฃจ ๋ผ๋ ์์น์ ํญ์ ๊ธฐ์ตํ์ธ์.
- ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "ํด๋ผ์ด์ธํธ ๊ฒ์ฌ = UX, ์๋ฒ ๊ฒ์ฌ = ๋ณด์"
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋ ํ์๊ฐ์
ํผ ๋ค์ ์ง๋ฉด์ ์ง์ง ๋ฐ์ฑ ๋ง์ด ํ๋ค. <input> ํ๋์ type, name, id, label, required ๋ค ๋ถ์ด๊ณ ๋๋๊น ์ฝ๋๊ฐ ๊ธธ์ด์ก๋๋ฐ, ๊ทธ๊ฒ ๋ง๋ ๊ฑฐ๋๋ผ. "์งง๋ค = ์ข๋ค"๊ฐ ์๋๋ผ๋ ๊ฑธ ์ค๋ ๋ ๋ฐฐ์ ๋ค.
์ ์ผ ์ถฉ๊ฒฉ์ด์๋ ๊ฑด ์ํธ ๋ฆฌ๋ ๋์ด "ํด๋ผ์ด์ธํธ ์ ํจ์ฑ ๊ฒ์ฌ๋ง ํ๋ฉด ๋ณด์ ์ฌ๊ณ ๋ฉ๋๋ค"๋ผ๊ณ ํ์ ๋์๋ค. ๋๋ required ๋ถ์ด๋ฉด ์์ ํ ์ค ์์๋๋ฐ, DevTools ํ๋๋ฉด ๋ค ์ฐํ๋๋ค๋ ๊ฒ ๋ฌด์์ ๋ค.
๐ก "HTML ํผ์ ์ฌ์ฉ์๋ฅผ ์ํ UX ์์ ์ฅ์น, ์๋ฒ ๊ฒ์ฌ๋ ์์คํ ๋ณด์์ ๋ง์ง๋ ธ์ ์ด๋ค. ๋ ๋ค ์์ผ๋ฉด ์๋ฌด ์๋ฏธ๊ฐ ์๋ค."
ํด๊ทผํ๊ณ React Hook Form ๊ณต์ ๋ฌธ์ ์ข ์ฝ์ด๋ด์ผ๊ฒ ๋ค. ์ ์ด ์ปดํฌ๋ํธ ํจํด์ ์ด๋ ๊ฒ ๋ฐ๋ณต ์ฝ๋ ์์ด ํด๊ฒฐํด์ค๋ค๋๊น ํ ๋ฒ ์จ๋ณด๊ณ ์ถ๋ค. ์ค๋ ์ ๋ ๋๋์ด ํผ์ ๋ณธ์ง์ ์ดํดํ ๊ฒ ๊ฐ์์!
๐ ๋ ์์๋ณด๊ธฐ