본문 바로가기
개발공부일지/React

React - Form 사용하기

by Hynn1429 2023. 3. 6.
반응형

안녕하세요.

Hynn 입니다.

 

이번 포스팅에서는 HTML 에서 사용할 수 밖에 없지만, React 에서는 동작이 다르게 이루어지는 Form Element 에 대해 알아보도록 하겠습니다.

이번 포스팅은 다소 난해할 수 있기 때문에, 최대한 풀어서 작성해보도록 하겠습니다.

 

 

==========

==========

 

 

먼저 이를 이해하기 위해서는 Component 의 개념으로 접근해야 합니다.

기존의 HTML 에서의 Form Element 는, "제어(Controller)", "비제어(unController)" Component 로 분류할 수 있습니다.

여기서 말하는 제어와 비제어의 차이는, 제어의 경우 Form Element 의 처리를 JavaScript 에서, 비제어의 경우, "브라우저"가 처리를 하는 개념을 뜻합니다.

 

하지만 React 에서는 "제어" Component 로서 동작합니다. 즉 React 는 브라우저가 처리하는 것이 아니라, JavaScript 를 이용해서 아래와 같은 동작구조로 이루어져 있습니다.

 

  1. React 에서 Form 의 값을 "State"에 저장합니다.
  2. 이를 이용해 요소의 변경사항이 있다면 "State"를 "업데이트" 하여 처리합니다.

React 에서는 위 두가지의 형태를 이용해서, Form 의 상태를 보다 쉽게 추적할 뿐 아니라, ID중복체크와 같은 유효성 검사를 쉽게하거나, 조건부 랜더링, 폼 데이터 제출(ex.로그인, 회원가입, 글작성) 의 기능을 구현하기 보다 쉽게 사용이 가능합니다. 

 

이를 이요하면, React 에서는 아래와 같은 기능을 더 쉽게 구현하고, 이를 재사용할 수 있게 됩니다.

가령 예를 들면, Input component 를 작성자가 하나 만들되, 용도를 "유효성 검사" 를 만들겠다면, 이를 이용해 폼 전체에서 재사용이 유연하게 가능합니다. 즉 하나의 Input component 를 만들어두면, 이를 여러가지 방향에서 재사용이 가능하다는 의미이기도 합니다. 

이를 React 에서는 "Custom Component" 라고 칭합니다. 

 

물론 이후에 더 나아간다면 "고급 컴포넌트"라는 개념으로, Formik, Field 와 같은 다양한 Component 를 사용한다면 이를 더 효율적으로 처리할 수 있습니다.

 

 

이제 실제 위에서 언급한 제어 컴포넌트에 대해서 기초적인 작성을 한번 해보도록 하겠습니다.

     const Form = () => {
            return(
                <form>
                    <label>Name : <input type="text" name="name"/></label>
                    <input type="submit" value="Submit"/>
                    </form>
            )
        }

        const root = ReactDOM.createRoot(document.getElementById("root"))
        root.render(<Form />)

 

이를 위해 기초적인 Form 을 작성해보도록 하겠습니다.

위 Form 은 React 의 측면이 아니라, 단순히 기존의 HTML 에 기초한 일반적인 Form 작성 방식입니다.

하지만 위에서 언급했듯이, HTML 은 이렇게 작성하는 경우, Input box 에 입력한 값은 "name" 으로 처리되고, Submit 을 누르면, 이 값이 새로운 페이지로 이동됩니다.

 

하지만, 이러한 방식은 React 의 장점을 전혀 사용하지 않은 방식으로, 이제 React 에서 사용하는 일반적인 사용방식을 작성해보도록 하겠습니다.

 

        class Form extends React.Component {
            constructor(props){
                super(props)
                this.state = { value: ""}
            }

            render(){
                return (
                    <form>
                        <label> Name : <input type="text" name="name" />
                        </label>
                        <input type="submit" value="submit" />
                    </form>
                )
            }
        }
        const root = ReactDOM.createRoot(document.getElementById("root"))
        root.render(<Form />)

 오히려 더 길어진거 같지만, 이는 재사용을 위해 생명주기 method 및 다양한 함수를 추가함으로써, 기능을 구현할 수 있습니다.

즉 위에서는 JSX 문법을 이용해서 Form 을 처리하고 있기 때문에, 제어 컴포넌트라고 합니다.

이를 이제 실제 사용에 까지 반영할 수 있는 예시를 작성해보도록 하겠습니다.

        class Form extends React.Component {
            constructor(props){
                super(props)
                this.state = { value: ""}
            }

            handleChange = (e) => {
                console.log(e.target.value, 'handleChange')
                this.setState({ value: e.target.value})
            }

            handleSubmit = (e) => {
                console.log(e.target, 'handleSubmit')
                alert("이름이 처리되었습니다" + this.state.value)
                e.preventDefault()
            }

            render(){
                return (
                    <form onSubmit={this.handleSubmit}>
                        <label> Name : <input type="text" value={this.state.value} onChange={this.handleChange}/>
                        </label>
                        <input type="submit" value="submit" />
                    </form>
                )
            }
        }

이제 이를 조금 나누어 설명해보도록 하겠습니다.

먼저 이는 클래스 컴포넌트로 작성되었습니다. 이후에는 이제 함수형 컴포넌트로 작성할 예정이기때문에, 이 역시도 함수형 컴포넌트로 작성을 변경할 예정입니다.

 

위 함수를 예시로 본다면, 먼저 Construtor 에서 상태의 값을 ""으로 null 로 처리하였습니다.

그리고 두개의 method 는 각각의 이벤트를 처리하는 method 를 작성하였습니다.

위에서는 화살표함수(Arrow Function) 을 사용하여 bind 처리를 생략한 작성이기때문에, 화살표함수가 아니라면, constructor 에 bind 처리를 해야 올바르게 동작이 가능합니다. 

 

두개의 이벤트 처리 Method 는 아래의 역활을 담당합니다.

  • handleChange - input 요소의 값이 변경되는 경우 동작하는 method 입니다. 이를 위해 input element 에 onChange 를 설정하고, " { } " 를 사용하여 함수를 부여합니다.
  • handleSubmit - Form Element 가 Input type 이 Submit 을 누를시, 처리되는 Method 입니다. e.preventDefault() 를 명시적으로 부여하고, Form 에 onSubmit 이벤트가 발생시, 어떠한 동작이 처리되는지를 정의한 method 입니다.

이를 사용하면, 이제 Form Element 를 제출하면, State 는 입력한 값으로 업데이트가 되어 처리가 되고, 기존의 HTML 처럼 페이지가 새로고침되지 않고, 그대로 남게 됩니다.

위 코드가 바로 기초적인 제어 컴포넌트의 예시가 될 수 있습니다.

 

 

Form Element 에는 잘 아시다시피, Input, Textarea, Select Element 가 대표적으로 사용됩니다.

즉 Form Element 에 데이터를 전달하기 위해서 위 3개의 element 를 활용하여 작성이 이루어 집니다.

이 각각의 대한 세부적인 설명은 내용이 방대하다 보니, React Official Docs 에서 위 3개의 Element 에 대해서만 다루는 공식 문서가 존재합니다.

 

이 내용을 따로 자세하게 살펴보시기 위해서는 아래의 페이지로 이동하여 보시는 것을 권장합니다.

위 문서에서는 각 요소를 원하는 형태로 사용할때, Value 설정 및 사용가능한 type 유형에 대해서 상세하게 기술이 되어 있습니다.

내용이 방대하기때문에, 이러한 기술문서 소개로 생략하도록 하겠습니다.

 

 

이제 실제 Form 에서 여러개의 Input value 를 처리하는 예제를 작성해보도록 하겠습니다.

대표적으로 로그인이나, 회원가입역시 여러개의 Input value 를 처리해야 합니다. 개별적으로 form 을 하나하나 만들수는 없기 때문이죠.

예시로 작성을 해보도록 하겠습니다. 

이후의 포스팅에서도 예제로 활용될 예정이기때문에, 일반적인 로그인/회원가입 페이지처럼, ID의 중복여부, 비밀번호의 입력규칙, 이메일의 입력규칙을 제어하도록 조건문까지 같이 부여를 해보겠습니다.

 

        class Login extends React.Component {
            constructor(props) {
                super(props)
                this.state = {
                    appId: '',email: '',password: '',appIdError: '',emailError: '',passwordError: '',
                }
            }

            handleInputChange = (e) => {
                const { name, value } = e.target;
                let errors = {}
                switch (name) {
                    case 'appId':
                        if (value === 'Hynn') {
                            errors.appIdError = 'App ID는 "Hynn"이 될 수 없습니다.'
                        } else {
                            errors.appIdError = ''
                        }
                        break;
                    case 'email':
                        if (!value.match(/^\S+@\S+\.\S+$/)) {
                            errors.emailError = '올바른 이메일 주소 형식이 아닙니다. (예: example@example.com)';
                        } else {
                            errors.emailError = ''
                        }
                        break
                    case 'password':
                    if (!value) {
                        errors.passwordError = ''
                    } else if (!value.match(/^(?=.*\d)(?=.*[a-zA-Z]).{8,}$/)) {
                        errors.passwordError = '비밀번호는 8자 이상이어야 하며 문자와 숫자를 모두 포함해야 합니다.'
                    } else {
                        errors.passwordError = ''
                    }
                        break
                    default:
                        break
                }
                this.setState({
                    [name]: value,
                    ...errors,
                });
            }

            handleSubmit = (e) => {
                e.preventDefault()
                const { appId, email, password } = this.state;
                let errors = {}
                if (appId === 'Hynn') {
                    errors.appIdError = 'App ID는 "Hynn"이 될 수 없습니다.'
                }
                if (!email.match(/^\S+@\S+\.\S+$/)) {
                    errors.emailError = '올바른 이메일 주소 형식이 아닙니다. (예: example@example.com)'
                }
                if (!password.match(/^(?=.*\d)(?=.*[a-zA-Z]).{8,}$/)) {
                    errors.passwordError = '비밀번호는 8자 이상이어야 하며 문자와 숫자를 모두 포함해야 합니다.'
                }
                if (Object.keys(errors).length > 0) {
                    this.setState(errors)
                } else {
                    console.log({
                        appId: appId,
                        email: email,
                        password: password,
                    })
                    this.setState({
                        appId: '',email: '',password: '',appIdError: '',emailError: '',passwordError: '',
                    })
                }
            }

            render() {
                const { appId, email, password, appIdError, emailError, passwordError } = this.state
                return (
                    <form onSubmit={this.handleSubmit}>
                        <label>App ID : <input type="text" name="appId" value={appId} onChange={this.handleInputChange} /></label>
                        {appIdError && <p>{appIdError}</p>}
                        <label>App Email : <input type="email" name="email" value={email} onChange={this.handleInputChange} /></label>
                        {emailError && <p>{emailError}</p>}
                        <label>App Password : <input type="password" name="password" value={password} onChange={this.handleInputChange} /></label>
                        {passwordError && <p>{passwordError}</p>}
                        <input type="submit" value="로그인" />
                    </form>
                )}}
        const root = ReactDOM.createRoot(document.getElementById("root"))
        root.render(<Login />)

코드가 다소 길어보일 수 있지만, 다른곳에서도 재활용이 가능한 하나의 Component 가 되었습니다.

위 예제는 비록 3개의 Input 에 다중 입력이 된 항목을 제어하는 것이지만, 이 제어에 대한 코드의 대한 세부 설명은 아래와 같습니다.

 

먼저 class component 를 생성합니다. 

그리고 constructor method 를 사용하여, 초기 구성요소의 "state" 를 정의합니다.

각 요소는 아래와 같이 설정합니다.

 

  • App ID, Email, Password : Input 요소에서 입력된 값을 담을 state 입니다.
  • appId/email/password Error : If 문을 사용하여 Error 처리를 위한 State 입니다.

그리고 각각의 onChange, onSubmit 을 위한 함수를 작성합니다. 저는 bind 처리를 생략하기 위해, 화살표 함수로 작성했지만, 그렇지 않은 경우에는 constructor에 bind 처리까지 해야 올바르게 동작함을 기억해야 합니다.

 

  • handleInputChange : 입력된 Value 를 실시간으로 변경되는 값을 처리하는 함수입니다. 각각의 입력값에 대한 변경사항을 추적하고 유효성 검사 결과에 따라 실시간으로 오류를 나타낼 수 있습니다. errors 라는 빈 객체를 지역변수로 선언하고, switch 문 내에 AppId, email, password 3개의 요소에 유효성검사를 부여하여 오류가 발생할 때, errors 라는 객채 내에 위에 선언한 state 값을 업데이트 하도록 설정합니다. 만약 유효성 검사에 통과될 경우, state 값을 null 로 업데이트 함으로 메시지가 출력되지 않습니다.
  • handleSubmit : Submit 에서도 동일하게 위와 같이 오류처리를 진행합니다. 단, 모든 유효성검사가 완료되고, 빈 값이 아닌 올바른 값이 입력되었다면, console.log 에 데이터가 표시되도록 작성되어 있습니다.

 

그리고 render 는 아래의 규칙을 사용해 작성하도록 합니다.

 

  • form Element 의 이벤트 주체는 Form element 이므로, form element 에 onSubmit 이벤트 method 를 부여합니다.
  • 각각의 input 요소에는 value 를 중괄호로 값을 사용하고, onChange를 부여합니다.
  • 각각의 error message 는 발생할 경우 "<p>" 를 사용하여 출력하도록 작성합니다. 

실제 위 코드를 이용해서 데이터를 받게 되면, 아래와 같이 Object 데이터로, Key:Value 형태로 전달이 됩니다.

만약 위 코드에서 실제 사용을 위한 추가를 한다면 아래와 같이 추가가 가능할 것입니다.

 

  • componentDidMount method 를 이용해 axios,fetch 와 같은 비동기 통신을 이용해서 서버와 연결하여, 실시간으로 DB와 데이터를 비교해서 아이디,이메일 중복검사를 효과적으로 사용할 수 있습니다. 

 

다음 포스팅에서는 "State" 를 끌어올리는 방식에 대해 작성해보도록 하겠습니다.

감사합니다.

반응형

댓글