본문 바로가기
시작/TIL(Today I Learned)

Formik 이쁘게 사용하기

by 백씨네 2024. 6. 17.
728x90

목차

  • Formik이란?
  • Formik과 React-Hook-Form
  • 하지만 난 Formik을 쓴다.
  • 그렇다면 어떻게 해야 할까?

 

 

Formik이란?

formik은 React 애플리케이션에서 폼 상태 관리를 쉽게 도와주는 라이브러리이다.
복잡한 폼 입력, 검증, 폼의 제출 처리를 위한 로직을 간단하게 구현할 수 있도록 설계되어 있다.

최근에 개발자들은 React-Hook-Form과 비교하여 사용 중이다.

 

 

Formik과 React-Hook-Form

Formik과 React-Hook-Form(이하 'RHF')의 큰 차이점은 상태를 이용하여 관리하는가? 아닌가?로 나뉘는 것 같다.

Formik은 React의 State를 통해서 관리하고, RHF은 'uncontrolled components'를 사용하여 성능을 최적화한다. 그렇기 때문에 Formik은 입력 필드가 많아지면 상태에 의해 많은 리렌더링이 일어나 성능에 영향을 줄 수 있다.

RHF은 폼 데이터를 DOM 수준에서 관리하기 때문에 처리가 효율적이고 상호 작용이 가능하다. 하지만상태를 사용하지 않기 때문에 현재 상태에 대한 추적이 어렵다. 현재 상태를 동기화하거나 조건부 논리를 폼상태로 관리해야 할 땐 어려울 수 있다.

이를 보안하기 위해서 RHF는 watch 함수를 이용해서 특정 필드나 모든 폼의 필드를 감시하고 상태 변화를 감지하는 기능을 추가했다.

Controlled Component(제어 컴포넌트)와 Uncontrolled Component(비제어 컴포넌트)

제어 컴포넌트는 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트할 수 있다.
'신뢰 가능한 단일 출처'로 만들어 두 요소를 결합시킨 것이다.

반대로 비제어 컴포넌트는 입력 데이터의 상태를 직접 관리하지 않는다. Dom 자체가 데이터를 처리하고 저장하는 역할을 한다. 전통적인 HTML 폼 입력과 유사하며, React에서는 ref를 이용해서 필요할 때 입력 요소의 현재 값을 직접 접근할 수 있다.

이외에 RHF와 Formik을 간단히 비교했을 때

RHF(React-Hook-Form)

  • 종속성 없음
  • 번들 크기 작음
  • 리렌더 횟수 적음

Formik

  • 다른 라이브러리의 종속성
  • 번들 크기 큼 (RHF와 비교)
  • 키 입력 시 상태 변경에 의해 리렌더

 

 

 

하지만 난 Formik을 쓴다.

현재 프로젝트를 진행 중인 입장에서 RHF과 Formik을 간단하게 비교해 봤다. 정말 간단하게 비교한 내용이고 성능과 렌더링 횟수를 직접 분석한 글도 찾아봤다.

그러면서 RHF을 쓰면 간단하게 쓸 수 있다고 생각한 것도 많았다. 하지만 현재 프로젝트를 진행 중이기 때문에 이미 Formik을 쓰고 있었다.
프로젝트를 진행하면서 Form 관련해서 더 최적화를 하고 싶었기 때문에 RHF을 알아보게 되었고, 유효성 검사가 편리하고, 코드의 간결성함에 매력을 느꼈지만, 단순히 이러한 이유 때문에 기존의 스택을 대체하는 것은 실질적인 문제가 있었다...

이미 사용 중인 기술을 뒤엎어 새로운 라이브러리를 사용한다는 것은 상당한 비용과, 시간이 들고 프로젝트의 기존 코드베이스에 깊이 통합된 라이브러리를 변경하면, 많은 부분에서 수정이 필요할 뿐 아니라 새로운 에러 발생 가능성이 있다고 판단했다.

그렇다고 지금부터 작성하는 코드를 RHF으로 변환하는 것은 프로젝트 빌드 크기에 영향을 미칠 수 있다고 생각했다.

 

 

그렇다면 어떻게 해야 할까?

내가 바꾸려고 생각했던 가장 큰 이유 중 하나는 Formik을 효과적으로 사용하고 있지 못한다고 판단했다. 처음 프로젝트에 참여할 때 Formik을 처음 쓰게 되었고, 기존 코드에 작성 방식을 따르며 사용하고 있었지만, 기존 코드를 제대로 알고 사용한 것이 아니었고, 문제없이 작동이 되었기에 더 깊게 생각하지 않았던 것이 이 사태를 불러온 것 같다.

 

그래서 공식문서부터 차근차근 읽으며 사용법을 다시 익히기로 했다

1. 기존에 쓰던 useFormik() 대신 사용하기

// before
const formik = useFormik({
    initialValues: {},    
    onSubmit: (values) => {},    
    validationSchema: Yup.object({}),
})

return (
    <form>
        <input/>
        <button type="submit"/>
    </form>
)


//after

return (
    <Formik initialValues={initialValues} onSubmit={handleSubmit} validationSchema={validationSchema}>
        {(props)=>(
            <Form>
                <Field></Field>    
                <button type="submit"></button>
            </Form>
        )}
    </Formik>
)

useFormik() 대신 Formik 컴포넌트를 이용하기로 했다.
Formik 컴포넌트를 직접 사용함으로써 리렌더링의 최적화를 할 수 있게 되었다.
기존에는 onChange로 인해서 키를 누를 때마다 리렌더링이 일어났지만 Formik, Form, Field 컴포넌트를 이용하면서 리렌더링이 최소화되었다.
(Form을 쓰는 상단에서 리렌더링이 되는 것이 아닌 Field 내부에서 리렌더링이 되는 것으로 추측됨.)

2. Formik 컴포넌트 안에 props 사용하기

Formik 컴포넌트 안에서 쓰는 props의 역할은 폼의 상태 관리와 동작 제어를 할 때 사용된다.

<Formik>
    {(props)=>(
        <Form>
            // formik의 values에 접근 가능
            {props.valuse} 
             //ddsfs//df
             <button disabled={props.isSubmitting}/>
        </Form>
    )}
</Formik>

대표적으로 2개를 사용해 봤는데, values를 이용해서 formik의 값에 접근할 수 있고 isSubmitting을 이용해서 onSubmit 함수가 동작하는 상태를 boolean 타입으로 알 수 있다.
이를 이용해서 버튼 비활성화나 버튼의 UI를 변경할 수 있고, 중복 제출을 막을 수 있다.

3. Field 컴포넌트

  • Field 컴포넌트는 기본적으로 input과 같다.
  • name
    • formik의 values에 대응하는 키값을 넣어서 사용한다.
  • type
    • input의 타입을 바꿀 수 있다 ( input, checkbox...)
  • as
    • "select" , "textarea", "input"으로 사용할 수 있다.
<Field name="email" type="" as="" />

4. 유효성 검사 및 ErrorMessage 컴포넌트

Yup을 이용하여 유효성 검사를 하는 방식을 선택하였고, Yup에 유효성 검사로직이 잘 구현되어 있기 때문에 해당 라이브러리를 추가 설치하여 사용했다.

npm install Yup

Formik의 props 중 validationSchema를 이용하여 적용하였다.

/*

valuse = {
    name : ""
    emaile :""
    option : [
        {id:"",value:""}
        {id:"",value:""}
    ]
    checked : []
}

*/


const validationSchema = Yup.object({
    // string 타입검사
    name: Yup.string().required('필수 입력사항입니다.'),
    //string, email 형식검사
    email: Yup.string().email('이메일 형식이 아닙니다.').required('필수 입력사항입니다.'),
    // 배열의 각 요소에 대한 검사
    option: Yup.array().of(
        Yup.object().shape({
            value: Yup.string().required('필수 입력사항입니다.'),
        }),
    ),
    // 배열 타입의 value에 대한 검사
    checked: Yup.array().min(1, '최소 1개 이상 선택해주세요.'),
})

<Formik validationSchema={validationSchema}>
    // 중략..
    <div>
        email
        <Field name="email" type="" as="text" />
        <ErrorMessage name="email" component={'div'} />
    </div>
</Formik>

는 유효성 검사 후 error에 대해서 지정한 message를 출력해 준다.

중요한 것은 component이다.
간혹 ErrorMessage에서 component에 CSS가 적용이 안된다고 하는데

component를 안 쓰게 되면 text로 들어가기 때문이다.

 

 

(ErrorMessage의 CSS가 적용이 안된다면 component를 확인해 보길...)

또한 compnent를 이용하면 컴포넌트를 이용하여 만들 수 있다

 

 

위에 1은 input에 대한 CSS가 전혀 없지만 기존에 만든 Input 컴포넌트를 이용해서 component속성에 넣어주면 위와 같이 적용된다

Field, ErrorMessage 컴포넌트에서 모두 사용가능

<div>
    email
    <Field name="email" component={Input} />
    <ErrorMessage name="email" component={'div'} className="text-xl text-fm-text-800" />
</div>

 

 

5. 중복되는 Field 코드 제거

Form을 만들 때 보통 여러 개의 input과 같이 만들게 될 것이다.
그러면 각 value 마다 Field, ErrorMessage를 만드는 것은 매우 코드가 지저분하게 보일 수 있다.

<>
    <div>
        name:
        <Field name="name" />
        <ErrorMessage name="name" component={Button} />
    </div>
    <div>
        id:
        <Field name="id" />
        <ErrorMessage name="id" component={Button} />
    </div>
    <div>
        pw:
        <Field name="pw" />
        <ErrorMessage name="pw" component={Button} />
    </div>
</>

이를 조금 더 간단하고, 재사용성을 늘리기 위해서 아래와 같이 묶어서 사용할 수 있다.

const FormikInput = ({ label, ...props }) => {    
    const [field, meta] = useField(props.name) // 공식문서에는 props로 되어있는데 TS오류로 props.name을 쓰면 문제 없이 작동했다. (이유를 모르겠네....)

    return (
        <div>
            <label htmlFor={props.name}>{label}</label>
            <Input {...field} {...props} />
            {meta.touched && meta.error ? <Button>{meta.error}</Button> : null}
        </div>

    )
}

return (
    // 중략
    <FormikInput label="name" name="name" />
    <FormikInput label="id" name="id" />
    <FormikInput label="pw" name="pw" />

)

6. FieldArray: 값이 배열인 필드 (ex. checkbox)

체크박스에 대한 formik을 사용할 때 보통 value의 타입이 배열일 것이다.

//예를 들면
initalValues = {
    option: [
        { id: 1, title: 'option1', value: '1' },
        { id: 2, title: 'option2', value: '1' },
    ],
}

이때는 를 이용한다.

<FieldArray name="option">
    {(arrayHelper) => (
        <>
        {values.option &&
            values.option.length > 0 &&
            values.option.map((option, index) => (
                    <div key={option.id}>        
                        <div>{option.title}</div>
                        <Field name={`option.${index}.value`} />
                        <Button
                            type={'button'}        
                            onClick={() => arrayHelper.remove(index)}
                            className="inline"
                        >
                            -
                        </Button>
                        <ErrorMessage
                            name={`option.${index}.value`}
                            component={'div'}
                            className="text-xl text-fm-text-800"
                        />
                    </div>
                ))}
            // 배열에 추가하는 버튼
            <Button
                type="button"
                onClick={() =>
                    arrayHelper.push({
                        id: values.option.length + 1,
                        title: `option${values.option.length + 1}`,
                        value: '',
                    })
                }
            >
                +
            </Button>
        </>
    )}
</FieldArray>

간단하게 구현한 FieldArray 컴포넌트이다.
arrayHelper를 통해서 option의 배열옵션을 사용할 수 있다.

그래서 버튼의 onClick 이벤트를 보면 arrayHelper.remove, arrayHelper.push 등 배열 메서드를 사용할 수 있다.

 

새로운 프로젝트를 진행했다면 React-Hook-Form을 썼을 것 같지만 지금 내 상황에서는 Formik을 더 잘 쓰는 것이 낫다고 생각했고, 더 잘 쓰기 위해서 공식문서를 살펴봤다.

 

RHF을 한 번도 써본 적이 없기 때문에 직접적인 비교를 할 수는 없지만, Formik이 나쁜, 못 쓸만한 라이브러리는 아니라고 생각한다.

상태를 이용한 Form 데이터 관리가 많다면 Formik도 좋은 선택지인 것 같다.

 

 

반응형

댓글