안녕하세요.
Hynn 입니다.
이번 포스팅에서는 HTML 에서 사용할 수 밖에 없지만, React 에서는 동작이 다르게 이루어지는 Form Element 에 대해 알아보도록 하겠습니다.
이번 포스팅은 다소 난해할 수 있기 때문에, 최대한 풀어서 작성해보도록 하겠습니다.
==========
==========
1. HTML 과 React 에서 Form 동작이 다른점 이해하기
먼저 이를 이해하기 위해서는 Component 의 개념으로 접근해야 합니다.
기존의 HTML 에서의 Form Element 는, "제어(Controller)", "비제어(unController)" Component 로 분류할 수 있습니다.
여기서 말하는 제어와 비제어의 차이는, 제어의 경우 Form Element 의 처리를 JavaScript 에서, 비제어의 경우, "브라우저"가 처리를 하는 개념을 뜻합니다.
하지만 React 에서는 "제어" Component 로서 동작합니다. 즉 React 는 브라우저가 처리하는 것이 아니라, JavaScript 를 이용해서 아래와 같은 동작구조로 이루어져 있습니다.
- React 에서 Form 의 값을 "State"에 저장합니다.
- 이를 이용해 요소의 변경사항이 있다면 "State"를 "업데이트" 하여 처리합니다.
React 에서는 위 두가지의 형태를 이용해서, Form 의 상태를 보다 쉽게 추적할 뿐 아니라, ID중복체크와 같은 유효성 검사를 쉽게하거나, 조건부 랜더링, 폼 데이터 제출(ex.로그인, 회원가입, 글작성) 의 기능을 구현하기 보다 쉽게 사용이 가능합니다.
이를 이요하면, React 에서는 아래와 같은 기능을 더 쉽게 구현하고, 이를 재사용할 수 있게 됩니다.
가령 예를 들면, Input component 를 작성자가 하나 만들되, 용도를 "유효성 검사" 를 만들겠다면, 이를 이용해 폼 전체에서 재사용이 유연하게 가능합니다. 즉 하나의 Input component 를 만들어두면, 이를 여러가지 방향에서 재사용이 가능하다는 의미이기도 합니다.
이를 React 에서는 "Custom Component" 라고 칭합니다.
물론 이후에 더 나아간다면 "고급 컴포넌트"라는 개념으로, Formik, Field 와 같은 다양한 Component 를 사용한다면 이를 더 효율적으로 처리할 수 있습니다.
2. 제어 컴포넌트 (Controlled 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 처럼 페이지가 새로고침되지 않고, 그대로 남게 됩니다.
위 코드가 바로 기초적인 제어 컴포넌트의 예시가 될 수 있습니다.
3. Input, textarea, select React 에서 사용하기
Form Element 에는 잘 아시다시피, Input, Textarea, Select Element 가 대표적으로 사용됩니다.
즉 Form Element 에 데이터를 전달하기 위해서 위 3개의 element 를 활용하여 작성이 이루어 집니다.
이 각각의 대한 세부적인 설명은 내용이 방대하다 보니, React Official Docs 에서 위 3개의 Element 에 대해서만 다루는 공식 문서가 존재합니다.
이 내용을 따로 자세하게 살펴보시기 위해서는 아래의 페이지로 이동하여 보시는 것을 권장합니다.
위 문서에서는 각 요소를 원하는 형태로 사용할때, Value 설정 및 사용가능한 type 유형에 대해서 상세하게 기술이 되어 있습니다.
내용이 방대하기때문에, 이러한 기술문서 소개로 생략하도록 하겠습니다.
4. 다중 입력 제어하기
이제 실제 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" 를 끌어올리는 방식에 대해 작성해보도록 하겠습니다.
감사합니다.
'개발공부일지 > React' 카테고리의 다른 글
React - 합성/상속 알아보기 (0) | 2023.03.07 |
---|---|
React - 상태 끌어올리기 (Lifting State up) (0) | 2023.03.06 |
React - List & Key 사용하기 (0) | 2023.03.06 |
React - 조건부 랜더링 (0) | 2023.02.28 |
React - 이벤트 처리하기 (0) | 2023.02.27 |
댓글