๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Lect & Tip/node, Angular, React

๋ฆฌ์•กํŠธ ์ปค์Šคํ…€ ํผ ์ปดํฌ๋„ŒํŠธ์™€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ธฐ๋ฒ•

by st๊ณต๊ฐ„ 2025. 8. 19.
๋ฐ˜์‘ํ˜•

๋ฆฌ์•กํŠธ ์ปค์Šคํ…€ ํผ ์ปดํฌ๋„ŒํŠธ์™€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ธฐ๋ฒ•

๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํผ์€ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๋ฐ›์•„ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•ต์‹ฌ ์š”์†Œ์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ ์ž…๋ ฅ ์š”์†Œ๋ถ€ํ„ฐ ๋ณต์žกํ•œ ํผ๊นŒ์ง€ ๋‹ค์–‘ํ•œ ํ˜•ํƒœ์˜ ํผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์™€ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์ ์šฉํ•˜๋Š” ๊ฒƒ์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX) ํ–ฅ์ƒ๊ณผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•ˆ์ •์„ฑ์— ํฐ ์˜ํ–ฅ์„ ๋ฏธ์นฉ๋‹ˆ๋‹ค.

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ๋ฆฌ์•กํŠธ์—์„œ ์ปค์Šคํ…€ ํผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ํ•จ๊ป˜, ๋Œ€ํ‘œ์ ์ธ ํผ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ Formik๊ณผ React Hook Form์„ ํ™œ์šฉํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

ํผ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์„ฑ์˜ ์ค‘์š”์„ฑ

ํผ ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š” ์—ญํ• ์„ ๋„˜์–ด์„œ, ์‚ฌ์šฉ์ž์™€์˜ ์ธํ„ฐ๋ž™์…˜์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ ์ •ํ™•์„ฑ์„ ๋ณด์žฅํ•˜๊ณ , ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๋ช…ํ™•ํ•œ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ž˜ ๊ตฌ์„ฑ๋œ ํผ ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ : ์ž…๋ ฅ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ฆ‰๊ฐ์ ์ธ ํ”ผ๋“œ๋ฐฑ๊ณผ ์‹œ๊ฐ์  ์•ˆ๋‚ด๋ฅผ ์ œ๊ณตํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•์Šต๋‹ˆ๋‹ค.
  • ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ: ํผ์˜ ๊ฐ ์ž…๋ ฅ ์š”์†Œ๋ฅผ ๋…๋ฆฝ์ ์ธ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌํ•˜๋ฉด, ๋‹ค์–‘ํ•œ ํผ์—์„œ ๋™์ผํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด: ํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ๋ณ„๋„์˜ ๋ชจ๋“ˆ๋กœ ์บก์Аํ™”ํ•˜๋ฉด, ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.

๋ฆฌ์•กํŠธ์—์„œ ์ปค์Šคํ…€ ํผ ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„

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

์ด๋•Œ Formik์ด๋‚˜ React Hook Form๊ณผ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜๋ฉด, ์ด๋Ÿฌํ•œ ํผ ๊ด€๋ จ ๋กœ์ง์„ ๋ณด๋‹ค ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋“ค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ํผ ์ƒํƒœ ๊ด€๋ฆฌ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ, ๊ทธ๋ฆฌ๊ณ  ํผ ์ œ์ถœ ์‹œ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์—ฌ ๊ฐœ๋ฐœ์ž๊ฐ€ ํผ ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•์Šต๋‹ˆ๋‹ค.

Formik์„ ํ™œ์šฉํ•œ ํผ ๊ตฌ์„ฑ ๋ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

Formik์€ ๋ฆฌ์•กํŠธ์—์„œ ํผ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋Œ€ํ‘œ์ ์ธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ๊ฐ„๋‹จํ•œ API๋ฅผ ํ†ตํ•ด ๋ณต์žกํ•œ ํผ ๋กœ์ง์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Formik์€ ๋‚ด๋ถ€์ ์œผ๋กœ ํผ์˜ ์ƒํƒœ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ๊ทธ๋ฆฌ๊ณ  ์ œ์ถœ ์ฒ˜๋ฆฌ๋ฅผ ์ž๋™ํ™”ํ•ด์ฃผ๋ฉฐ, Yup๊ณผ ๊ฐ™์€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๊ฒฐํ•ฉํ•˜์—ฌ ๋ณด๋‹ค ๊ฐ•๋ ฅํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

Formik๊ณผ Yup์„ ํ™œ์šฉํ•œ ์˜ˆ์ œ

์•„๋ž˜ ์˜ˆ์ œ๋Š” ๊ฐ„๋‹จํ•œ ํšŒ์›๊ฐ€์ž… ํผ์„ Formik๊ณผ Yup์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•œ ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค. ์ด ํผ์€ ์‚ฌ์šฉ์ž ์ด๋ฆ„, ์ด๋ฉ”์ผ, ๋น„๋ฐ€๋ฒˆํ˜ธ ๋“ฑ์˜ ์ž…๋ ฅ ๊ฐ’์„ ๋ฐ›๊ณ , Yup์„ ํ†ตํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

// SignupForm.jsx
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import styled from 'styled-components';

// ์Šคํƒ€์ผ๋ง๋œ ์ปดํฌ๋„ŒํŠธ
const FormContainer = styled.div`
  max-width: 400px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
`;

const InputField = styled(Field)`
  width: 100%;
  padding: 8px 12px;
  margin-bottom: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
`;

const ErrorText = styled.div`
  color: red;
  font-size: 0.9rem;
  margin-bottom: 10px;
`;

const SubmitButton = styled.button`
  padding: 10px 15px;
  background-color: #1976d2;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    background-color: #1565c0;
  }
`;

// ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์Šคํ‚ค๋งˆ ์ •์˜ (Yup)
const SignupSchema = Yup.object().shape({
  username: Yup.string()
    .min(3, '์ด๋ฆ„์€ ์ตœ์†Œ 3๊ธ€์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.')
    .required('์ด๋ฆ„์€ ํ•„์ˆ˜ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'),
  email: Yup.string()
    .email('์œ ํšจํ•œ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.')
    .required('์ด๋ฉ”์ผ์€ ํ•„์ˆ˜ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'),
  password: Yup.string()
    .min(6, '๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ตœ์†Œ 6๊ธ€์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.')
    .required('๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'),
});

const SignupForm = () => {
  return (
    <FormContainer>
      <h2>ํšŒ์›๊ฐ€์ž…</h2>
      <Formik
        initialValues={{ username: '', email: '', password: '' }}
        validationSchema={SignupSchema}
        onSubmit={(values, { setSubmitting, resetForm }) => {
          // ํผ ์ œ์ถœ ์ฒ˜๋ฆฌ ๋กœ์ง: ์‹ค์ œ API ํ˜ธ์ถœ ๋“ฑ
          console.log('ํผ ๋ฐ์ดํ„ฐ:', values);
          setTimeout(() => {
            alert('ํšŒ์›๊ฐ€์ž…์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!');
            resetForm();
            setSubmitting(false);
          }, 1000);
        }}
      >
        {({ isSubmitting }) => (
          <Form>
            <InputField type="text" name="username" placeholder="์ด๋ฆ„" />
            <ErrorMessage name="username" component={ErrorText} />

            <InputField type="email" name="email" placeholder="์ด๋ฉ”์ผ" />
            <ErrorMessage name="email" component={ErrorText} />

            <InputField type="password" name="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ" />
            <ErrorMessage name="password" component={ErrorText} />

            <SubmitButton type="submit" disabled={isSubmitting}>
              {isSubmitting ? '์ฒ˜๋ฆฌ์ค‘...' : '๊ฐ€์ž…ํ•˜๊ธฐ'}
            </SubmitButton>
          </Form>
        )}
      </Formik>
    </FormContainer>
  );
};

export default SignupForm;

์ฝ”๋“œ ์„ค๋ช…

  1. Formik ์ปดํฌ๋„ŒํŠธ: initialValues๋กœ ํผ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์ •์˜ํ•˜๊ณ , validationSchema๋ฅผ ํ†ตํ•ด Yup ๊ธฐ๋ฐ˜์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์Šคํ‚ค๋งˆ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. onSubmit ํ•จ์ˆ˜๋Š” ํผ ์ œ์ถœ ์‹œ ํ˜ธ์ถœ๋˜๋ฉฐ, API ํ˜ธ์ถœ์ด๋‚˜ ๋‹ค๋ฅธ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. Field์™€ ErrorMessage: Formik์˜ Field ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , ErrorMessage๋ฅผ ํ†ตํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ์จ ์‚ฌ์šฉ์ž์—๊ฒŒ ์‹ค์‹œ๊ฐ„ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  3. ์Šคํƒ€์ผ๋ง: Styled Components๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํผ ์ „์ฒด ๋ ˆ์ด์•„์›ƒ๊ณผ ์ž…๋ ฅ ํ•„๋“œ, ๋ฒ„ํŠผ ๋“ฑ์— ๋Œ€ํ•ด ๋ชจ๋“ˆํ™”๋œ ์Šคํƒ€์ผ์„ ์ ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Formik์„ ์‚ฌ์šฉํ•˜๋ฉด ํผ์˜ ์ƒํƒœ ๊ด€๋ฆฌ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ๊ทธ๋ฆฌ๊ณ  ์ œ์ถœ ์ฒ˜๋ฆฌ๋ฅผ ๋ชจ๋‘ ํ•œ ๊ณณ์—์„œ ํ†ตํ•ฉ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด, ๋ณต์žกํ•œ ํผ์„ ๋ณด๋‹ค ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

React Hook Form์„ ํ™œ์šฉํ•œ ํผ ๊ด€๋ฆฌ ๋ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

React Hook Form์€ ๋ฆฌ์•กํŠธ ํ›… ๊ธฐ๋ฐ˜์˜ ํผ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ์ตœ์†Œํ•œ์˜ ๋ฆฌ๋ Œ๋”๋ง๊ณผ ๋น ๋ฅธ ์„ฑ๋Šฅ์„ ์ž๋ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋“ฑ๋ก(register) ๊ฐœ๋…์„ ํ†ตํ•ด ๊ฐ ์ž…๋ ฅ ์š”์†Œ์˜ ์ƒํƒœ์™€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, Formik๋ณด๋‹ค ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„ ์ตœ๊ทผ ๋งŽ์€ ํ”„๋กœ์ ํŠธ์—์„œ ์ธ๊ธฐ๋ฅผ ๋Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

React Hook Form๊ณผ Yup์„ ํ™œ์šฉํ•œ ์˜ˆ์ œ

์•„๋ž˜ ์˜ˆ์ œ๋Š” React Hook Form์„ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ผํ•œ ํšŒ์›๊ฐ€์ž… ํผ์„ ๊ตฌํ˜„ํ•œ ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค. Yup์„ ํ™œ์šฉํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ , ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

// RHFSignupForm.jsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from 'yup';
import styled from 'styled-components';

// ์Šคํƒ€์ผ๋ง ์ปดํฌ๋„ŒํŠธ๋Š” Formik ์˜ˆ์ œ์™€ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉ
const FormContainer = styled.div`
  max-width: 400px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
`;

const InputField = styled.input`
  width: 100%;
  padding: 8px 12px;
  margin-bottom: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
`;

const ErrorText = styled.div`
  color: red;
  font-size: 0.9rem;
  margin-bottom: 10px;
`;

const SubmitButton = styled.button`
  padding: 10px 15px;
  background-color: #1976d2;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    background-color: #1565c0;
  }
`;

// ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์Šคํ‚ค๋งˆ (Yup)
const SignupSchema = Yup.object().shape({
  username: Yup.string()
    .min(3, '์ด๋ฆ„์€ ์ตœ์†Œ 3๊ธ€์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.')
    .required('์ด๋ฆ„์€ ํ•„์ˆ˜ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'),
  email: Yup.string()
    .email('์œ ํšจํ•œ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.')
    .required('์ด๋ฉ”์ผ์€ ํ•„์ˆ˜ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'),
  password: Yup.string()
    .min(6, '๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ตœ์†Œ 6๊ธ€์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.')
    .required('๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'),
});

const RHFSignupForm = () => {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    reset,
  } = useForm({
    resolver: yupResolver(SignupSchema),
  });

  const onSubmit = (data) => {
    console.log('ํผ ๋ฐ์ดํ„ฐ:', data);
    setTimeout(() => {
      alert('ํšŒ์›๊ฐ€์ž…์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!');
      reset();
    }, 1000);
  };

  return (
    <FormContainer>
      <h2>ํšŒ์›๊ฐ€์ž… (React Hook Form)</h2>
      <form onSubmit={handleSubmit(onSubmit)}>
        <InputField type="text" placeholder="์ด๋ฆ„" {...register('username')} />
        {errors.username && <ErrorText>{errors.username.message}</ErrorText>}

        <InputField type="email" placeholder="์ด๋ฉ”์ผ" {...register('email')} />
        {errors.email && <ErrorText>{errors.email.message}</ErrorText>}

        <InputField type="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ" {...register('password')} />
        {errors.password && <ErrorText>{errors.password.message}</ErrorText>}

        <SubmitButton type="submit" disabled={isSubmitting}>
          {isSubmitting ? '์ฒ˜๋ฆฌ์ค‘...' : '๊ฐ€์ž…ํ•˜๊ธฐ'}
        </SubmitButton>
      </form>
    </FormContainer>
  );
};

export default RHFSignupForm;

์ฝ”๋“œ ์„ค๋ช…

  1. useForm ํ›…: React Hook Form์˜ useForm ํ›…์„ ํ†ตํ•ด ํผ์˜ ์ƒํƒœ์™€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. yupResolver๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Yup ์Šคํ‚ค๋งˆ ๊ธฐ๋ฐ˜์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์—ฐ๋™ํ•ฉ๋‹ˆ๋‹ค.
  2. register ํ•จ์ˆ˜: ๊ฐ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ register ํ•จ์ˆ˜๋กœ ๋“ฑ๋กํ•˜์—ฌ, ํผ ์ƒํƒœ์— ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค.
  3. ์—๋Ÿฌ ์ฒ˜๋ฆฌ: formState์˜ errors ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ๊ฐ ํ•„๋“œ์˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธํ•˜๊ณ , ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์œผ๋กœ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
  4. ํผ ์ œ์ถœ: handleSubmit ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํผ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , onSubmit ํ•จ์ˆ˜ ๋‚ด์—์„œ ์‹ค์ œ ํผ ์ œ์ถœ ๋กœ์ง์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

React Hook Form์€ ์ตœ์†Œํ•œ์˜ ์ฝ”๋“œ๋กœ ๋น ๋ฅด๊ฒŒ ํผ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ์ค„์—ฌ ์„ฑ๋Šฅ ์ตœ์ ํ™”์—๋„ ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

๋ฆฌ์•กํŠธ์—์„œ ์ปค์Šคํ…€ ํผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์˜ ์ •ํ™•์„ฑ์„ ๋ณด์žฅํ•˜๊ณ , ์›ํ™œํ•œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. Formik๊ณผ React Hook Form๊ณผ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜๋ฉด, ๋ณต์žกํ•œ ํผ ์ƒํƒœ ๊ด€๋ฆฌ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋“ฑ์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, Yup๊ณผ ๊ฐ™์€ ์Šคํ‚ค๋งˆ ๊ธฐ๋ฐ˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋„๊ตฌ๋ฅผ ๊ฒฐํ•ฉํ•˜๋ฉด ๋ณด๋‹ค ๊ฒฌ๊ณ ํ•œ ํผ์„ ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋งˆ๋‹ค ํŠน์ง•๊ณผ ์žฅ๋‹จ์ ์ด ์žˆ์œผ๋ฏ€๋กœ, ํ”„๋กœ์ ํŠธ์˜ ์š”๊ตฌ์‚ฌํ•ญ๊ณผ ๊ฐœ๋ฐœ ํŒ€์˜ ์„ ํ˜ธ๋„์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋„๊ตฌ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. Formik์€ ๋น„๊ต์  ์ง๊ด€์ ์ธ API์™€ ํ’๋ถ€ํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ์ง€์›์„ ํ†ตํ•ด ๋ณต์žกํ•œ ํผ์„ ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ณ , React Hook Form์€ ํผํฌ๋จผ์Šค์™€ ์ฝ”๋“œ ๊ฐ„๊ฒฐ์„ฑ ๋ฉด์—์„œ ๋งŽ์€ ์žฅ์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์•ž์œผ๋กœ ๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ์‹œ, ์‚ฌ์šฉ์ž ์นœํ™”์ ์ด๊ณ  ๊ฒฌ๊ณ ํ•œ ํผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•ด ์ด๋“ค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ ๊ทน ํ™œ์šฉํ•˜์‹œ๊ธธ ๊ถŒ์žฅ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ž…๋ ฅ ์˜ค๋ฅ˜๋ฅผ ์‚ฌ์ „์— ๋ฐฉ์ง€ํ•˜๊ณ , ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œ์ผœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „๋ฐ˜์ ์ธ ์‹ ๋ขฐ๋„์™€ ํ’ˆ์งˆ์„ ๋†’์ผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€