Reactㅣ폼 다루는 추천 방법

휘Bin·2023년 7월 2일
0
post-thumbnail

폼 요소는 사용자로부터 텍스트 같은 데이터나 클릭 같은 조작을 전달받는 데 사용하기 때문에 웹 개발에서 중요한 부분이다!

일반적인 HTML에서 입력 요소를 다룰 때는 페이지의 DOM이 해당 요소의 값을 DOM 노드에서 관리한다. 즉, DOM을 저장소로 사용하는 것이다.

React 공식 문서에서는 "React 컴포넌트는 초기화 시점은 물론 어느 시점에든지 뷰의 상태를 표현해야한다" 라고 말한다. 그도 그럴것이 React는 선언형 스타일을 사용하여 UI를 다루므로 모든 것을 단순하게 유지한다. 즉 이 말은 UI가 결과적으로 어떻게 보여야 할지에 대해 말한다.

전통적인 HTML의 폼 요소는 사용자 입력에 의해 요소의 상태가 변경된다. 하지만 React는 UI를 표현하기 위해 선언형 스타일을 사용하기 때문에 상태를 적절히 반영하려면 입력이 동적이어야 한다.

따라서 컴포넌트 상태를 자바스크립트에서 관리하지 않고, 뷰와 동기화하지 않으면 문제가 발생한다. 이를 해결하기 위한 가장 좋은 방법은 React의 render() 메서드를 폼 요소의 데이터를 포함한 실제 DOM에 최대한 밀접하게 유지시키는 것이다.

React는 컴포넌트의 render()에서 새로운 값을 포함해야 한다. 따라서 엘리먼트의 값을 새 값으로 설정해야 한다. 하지만 HTML에서 < input> 영역을 구현하면 React는 항상 상태를 실제 DOM에 동기화되도록 유지한다. 즉, React는 사용자가 값을 바꿀 수 없게 하는 것이다.

이해를 돕기 위해 몇 가지 예시를 들어 보면,

render(){
	return <input type="text" name="thanks" value="hwibin" />
}

위 코드를 보면 상태와 상관없이 항상 같은 뷰이므로 input 영역의 입력 값은 항상 hwibin으로 유지된다. 하지만 입력 영역이란 사용자 입력이나 클릭에 따라 변경되어야 한다. 즉, 값이 동적으로 변경되어야 하는 것이다. 여기서 좀 더 나아지게 하려면 상태에 따라 입력 값을 갱신하도록 구현해보는 것이다.

render(){
	reeturn <input type="text" name="thanks" value={this.state.thanks} />
}

자, 여기서 중요한 것은, React가 사용자가 폼 요소에 뭔가를 작성한다는 것을 알 수 없다는 것이다.
따라서 React가 변경을 감지할 수 있도록 onChange에 이벤트 핸들러를 추가해줘야 한다.
아래와 같이 말이다.

handleChange(event){
	this.setState({thanks: event.target.value})
}
render(){
	return <input type="text" name="thanks" value={this.state.thanks} onChange={this.handleChange.bind(this)}/>
}

즉 내부 상태와 뷰를 동기화시키는 것이 가장 좋은 방법인데 순서는 아래와 같다.

1. render()에서 상태 값을 이용해 엘리먼트를 정의한다.

2. onChange를 이용해 폼 요소에 발생하는 변경 사항을 감지한다.

3. 이벤트 핸들러에서 내부 상태를 갱신한다.

4. 새로운 값이 상태에 저장되면 새로운 render()가 실행되어 뷰를 갱신한다.

이러한 방식을 '단방향 바인딩'이라고 한다. 상태가 뷰를 갱신하는 것이 다이기 때문이다.
또한, 상태나 모델을 자동을 갱신하지도 않는다.
여러 뷰에서 암묵적으로 상태 또는 데이터 모델을 갱신하거나 역으로 상태가 뷰를 갱신하는 큰 규모의 앱을 다룰 때 복잡도를 제거할 수 있다는 것이 단방향 바인딩의 가장 큰 강점 중 하나이다.

위와 같은 이유로 폼을 다룰 때 가장 권장하는 방법이 '제어 컴포넌트를 사용하는 방식'이다.
이 방법은 컴포넌트 내부 상태와 뷰를 항상 동기화시킬 수 있다.
'단방향 바인딩'은 모델에서 뷰로 데이터를 옮기는 것을 담당한다.
즉, 변경을 감지한 후 상태에 반영하여 뷰를 갱신시키는 것이다.

폼과 이벤트 정의

< form> 요소를 보면, 일반적으로 입력 요소를 DOM의 아무곳에나 무작위로 두지는 않는다. 서로 기능이 다른 여러 입력 요소 집합을 다루는 경우에는 잘못될 수 있기 때문이다. 따라서 input 요소를 공통 목적을 가진 항목끼리 묶어 form 요소로 감싼다.

그렇다고 < form> 요소로 감싸는 것이 필수인건 아니다. 하지만 여러 요소의 그룹으로 이뤄진 단일 페이지처럼 복잡한 UI를 다룰 때는 각 그룹을 < fomr>으로 구분하는게 현명할 수 있다.

React의 < form>은 HTML의 < form>처럼 렌더링되기 때문에 HTML 폼에 적용할 수 있는 방법은 React의 form 요소에도 적용할 수 있다.

form 요소에 이벤트를 사용할 수도 있는데, 표준 React DOM 이벤트와 폼 요소를 위한 세 가지 이벤트를 지원한다.
세 가지 이벤트는 아래와 같다.

  • onChange : 폼의 입력 요소에 변경이 생기면 발생

  • onInput : < textaret> < input> 요소의 값이 변경될 때 발생한다.(React에서 이 이벤트 사용을 추천하지는 않는다.)

  • onSubmit : 폼 제출 시 발생.

React에서는 가급적 onChange 이벤트를 사용하고, onInput 이벤트의 네이티브 구현에 접근해야 하는 경우에만 onInput을 사용하는 것을 추천한다. React가 감싸서 만든 onChange의 동작이 일관성이 있어 믿을 수 있기 때문이다.

폼 요소 정의

HTML이 거의 모든 입력 영역은 < input>, < textarea>, < select>, < option>, 이 4가지를 사용해서 구현할 수 있다.
React에서는 속성을 변경할 수 없는데, 폼 요소는 사용자가 폼 요소와 상호작용하면서 속성을 변경하기 때문에 굉장히 특별한 경우이다.

React는 변경 가능한 속성인 value, checked, selected를 두어서 폼 요소를 특별하게 다룬다. 이러한 변경 가능한 속성을 '대화형 속성'이라 한다.
폼 요소에 연결한 onChange 같은 이벤트에서 이 속성을 읽을 수 있다. 대화형 속성을 조금 설명해보면,

  • value : < input>, < textarea>, < select>에 적용된다.

  • checked : < input>에 type="checkbox" or type="radio"인 경우 적용

  • selected : < select>와 함께 < option>을 사용할 때 적용

위와 같은 대화형 속성을 이용해 값을 읽거나 변경할 수 있다.

< input> 요소

input 요소는 type 속성에 입력 값에 따라 여러 방식의 입력 영역을 렌더링 할 수 있다.

  • text : 일반적인 텍스트 입력 영역

  • password : 입력 내용이 가려진 텍스트 영역

  • radio : 라디오 버튼

  • checkbox : 체크박스 요소

  • button : 버튼 폼 요소

체크박스와 라디오 버튼을 제외한 모든 < input> 요소의 주 용도는 요소의 변경 가능한 대화형 속성을 사용하는 것이다.

value를 변경 가능한 속성으로 사용하지 않는 두 가지 예외인 체크박스와 라디오 버튼, 이 두 유형은 HTML 요소 당 값을 하나만 가지므로 값이 변경되지는 않지만, checked 또는 selected 속성이 변경된다.

즉, value는 변경할 필요가 없어 하드코딩이 되고, 사용자 조작에 의해 변경되는 것은 checked or selected 속성이다.

< textarea> 요소

장문 입력을 감지하고 보여주기 위해 사용된다.
일반적인 HTML에서 textarea는 inner HTML(자식)을 사용하여 값을 보여준다.

반면에 React는 'value 속성'을 사용한다.
React는 < textarea>에 자식이 있는 경우에 자식으로 입력된 텍스트를 기본값으로 사용한다.

대신, textarea에 value 속성을 사용하는 것을 권장하고 있다.

render(){
	return <textarea name="content" value={this.state.content}/>
}

input 요소와 마찬가지로 변경을 감지하려면 onChange를 이용한다.

< select>, < option> 요소

select와 option 영역은 사용자가 미리 입력된 값 목록에서 한 가지 또는 여러 가지 값을 선택할 수 있는 UX를 제공한다.

React에서는 select에 아래와 같이 value 속성을 사용한다.

render(){
	return <form>
     <select value={this.state.selectedValue} onChange={this.handleSelectChange}>
          <option value="ruby">Ruby</option>
          <option value="node">Node</option>
          <option value="python">Python</option>
        </select>
}

'다중 선택 요소'를 사용해야 하는 경우네느 React에서는 JSX를 작성할 때 별도의 값을 주지 않고 multiple속성만 작성하면 React가 true로 처리한다. 또는 명시적으로 multiple={true}라고 값을 주기도 한다.

※"true", {true} 둘 다 결과는 같다. 하지만 "false"라고 하면 안 된다. 이전 글에서도 설명을 하긴 했었는데, 자바스크립트에서는 문자열 "false"는 참 값이라 true 처리된다.

만약 여러 항목을 기본으로 선택하려면 < select>의 value 속성에 배열로 값을 전달한다. 아래와 같이 말이다.

<select multiple={true} defaultValue={['a', 'b']} readOnly>
          <option value="a">A</option>
          <option value="b">B</option>
          <option value="c">C</option>
        </select>

변경 감지

폼 요소의 변경을 감지할 때는 onChange 이벤트 리스너를 사용한다.
onChange 이벤트는 일반적인 DOM의 onInput 이벤트를 대체한다. 일반적인 HTML DOM의 onInput과 같은 동작이 필요한 경우에는 React의 onInput 이벤트를 사용할 수 있다. 하지만 React의 onChange 이벤트는 일반적인 DOM의 onChange 이벤트와 비슷하나 동일하지 않ㄷ나. 일반적인 DOM의 onChange 이벤트는 요소가 포커스를 잃었을 때만 발생하지만, React의 onChange 이벤트는 모든 새로운 입력에 대해 발생한다.
onChange 이벤트를 발생시키는 요인은 아래와 같이 요소에 따라 차이가 있긴하다.

  • < input>, < textarea>, < select> : value가 변경될 때 onChange 이벤트 발생

  • < input> 체크박스와 라디오 버튼 : checked가 변경될 때 onChange 이벤트 발생

위 분류에 따라 value를 익는 방법도 달라진다. 이벤트 핸들러의 인자로는 '합성 이벤트'를 받는다. 요소에 따라 event.target은 value, checked, selected 같은 값을 가진다.

변경을 감지하려면, 컴포넌트에 이벤트 핸들러를 정의하고, onChange 속성으로 이벤트 핸들러를 전달해주면 된다.
설명이 좀 어려운 것 같아 예를 들어 이메일 입력 영역의 변경을 감지하는 경우가 있다고 생각해보면 아래와 같다.

handleChange(event){
	console.log(event.target.value)
}

render(){
	return <input
    	type="text"
        onChange={this.handleChange}
        defaultValue="chlgnlqls3@nate.com"
    />
}

재밌는 점은, onChange를 정의하지 않고 value만 입력하면 React가 경고를 보내고 읽기 전용으로 만들어버린다. 읽기 전용 영역이 필요하다면 명시적으로 readOnly 속성을 추가 하는 것이 좋다. readOnly={true}라고 해도 되고, 값 없이 readOnly 속성만 작성해도 된다.

요소의 변경을 감지하면 아래와 같이 컴포넌트 상태에 저장할 수도 있다.

handleChange(event){
	this.setState({emailValue : event.target.value})
}
profile
One-step, one-step, steadily growing developer

0개의 댓글