바로 전 글에서 썼던 제어 폼 요소를 사용하는 방법이 제일 좋다. 하지만 그 방법은 직접 변경을 감지하고 상태를 갱신해야 하기에 추가 작업이 필요하다. value, checked, selected 속성 값을 문자열 또는 React의 속성이나 상태를 이용해서 정의해야 React가 요소를 제어한다.
또한, 폼 요소는 value 속성이 상태나 정적인 값으로 설정되어 있지 않을 때는 React가 제어지 않아도 된다. 사실, 비제어 컴포넌트를 권장하진 않는다고 많이들 말하지만, 서버에 전달할 간단한 폼을 만들 때는 비제어 컴포넌트가 유용할 때가 있다.
일반적으로 비제어 컴포넌트를 사용하기 위해서는 폼에서 제출 이벤트를 정의해야 한다. 보통 버튼에 onClick 이벤트나 폼의 onSubmit 이벤트를 사용한다.
이벤트 핸들러를 추가하고나서 두 가지 방법 중에 선택할 수 있다.
두 가지 방법은 아래와 같다.
1. 제어 엘리먼트를 사용할 때처럼 변경을 감지해서 상태에 저장하지만, 상태를 value에 사용하지 않고 제출 시에만 사용하는 방식 => 즉 제어하지 않은 방식이다.
2. 변경을 감지하지 않는 방식
1번째 접근 방법은 간단한데, 같은 이벤트 리스너를 사용해서 상태를 갱신한다.
비제어 엘리먼트에서 변경 감지하기
'비제어 컴포넌트'는 value 속성을 React에서 설정하지 않는 것을 의미한다. 그리고 이 경우에는 컴포넌트의 내부 값 또는 상태가 컴포넌트의 표현 또는 뷰와 서로 다를 수 있다.
만약 비제어 컴포넌트를 사용하는 경우에는 사용자가 폼 요소에 무엇이든 입력할 수 있으므로 뷰와 상태 사이에 차이가 발생한다.
rener(){
reteurn < input type="text"/>
}
위의 코드의 텍스트 입력 영역은 React에서 value를 설정하지 않았기에 '비제어 컴포넌트'이다.
그리고 사용자 입력이 즉시 뷰에 렌더링된다.
비제어 컴포넌트에 변경을 감지하기 위해선 onChange를 사용한다.
React는 onChange를 통해 새로 입력된 값을 감지해서 상태에 '저장만' 한다.
이 방법으로 입력 영역을 위한 이벤트 핸들러를 구현할 수 있게 되는 것이다.
변경을 감지하기에 입력 내용이 상태를 갱신하긴 하지만, DOM의 텍스트 입력 요소의 값을 제어하지는 않는다.
이벤트를 감지하지 않는 비제어 엘리먼트
위에 써두었던 2번째 방법을 보자.
변경을 감지하는 방식일 때는 모든 데이터가 '상태'에 저장된다. 비제어 엘리먼트에서 변경을 감지하지 않기로 하면 데이터는 DOM에 그대로 남게된다.
즉, 변경을 감지하지 않는 비제어 엘리먼트를 사용하려면, 다른 엘리먼트에 접근해서 데이터를 가져올 수 있는 방법이 필요하다.
※ 제어 컴포넌트 or 데이터를 감지하는 비제어 컴포넌트를 다룰 때는 데이터가 항상 상태에 저장되어 있다.
값에 참조로 접근하기
비제어 컴포넌트를 다룰 때는 onChange 같은 이벤트를 이용해 입력을 감지하지 않으므로 'refs'를 통해 참조로 값에 접근한다. 참조 사용이 안티패턴이라 불편할 수도 있겠지만, 적합한 상황이라면 당연히 적용할 수 있다.
React 엘리먼트를 적절히 정의한다면 뷰(DOM)의 상태와 내부 상태가 동기화되기 때문에 참조를 사용할 필요가 거의 없다.
참조를 사용하면 React 컴포넌트의 DOM 요소 또는 노드를 가져올 수 있다. 변경을 감지하지 않고 폼 요소의 값을 가져올 때 유용하다.
그리고 참조를 사용하려면 아래와 같은 2가지 작업이 필요하다.
* render() 메서드에서 반환하는 엘리먼트의 ref 속성에 문자열을 전달하는 경우 '카멜 표기법'으로 작성해야 한다. ex) email:< input ref="myEmail"/>
* 지정한 이름으로 다른 메서드에서 DOM 인스턴스에 접근한다.
this.refs.NAME으로 React 컴포넌트의 인스턴스에 접근할 수 있는 것이다. 하지만 입력 값을 확인할 수 있을까? DOM 노드를 가져오는 것이 유용할 것이다. 컴포넌트의 DOM 노드에 접근하기 위해서는 ReactDOM.findDOMNode(this.refs.NAME)을 사용한다.
아래 예시 코드를 확인해보면 좋다.
let passwordNode = ReactDOM.findDOMNode(this.refs.password)
let password = passwordNode.value
참조를 통해 접근하는 경우의 예를 좀 더 확실하게 보여주면 아래와 같다.
class Content extends React.Component{
constructor(pros){
super(props)
this.submit = htis.submit.bind(this)
this.promp = 'thanks'
}
submit(event){
let emailAddress = this.refs.emailAddress
let comments = this.refs.comments
console.log(ReactDOM.findDOMNode(emailAddress).value)
console.log(ReactDOM.findDOMNode(comments).value)
}
}
위 코드를 보면 참조를 통해 emailAddress, comments에 접근해서 콘솔에 출력하는 코드라는 것을 알 수 있다.
ReactDOM.findDOMNode()가 DOM 노드를 반환하기 때문에 innerHTML 같은 일반적인 HTML 속성이나 getAttribute() 같은 메서드에도 접근할 수 있다.
참조를 사용하는 것은 비제어 엘리먼트를 사용하는 것처럼 흔하지 않은 경우이다. 참조는 과도하게 사용하는 것은 지양해야 한다. 대부분의 경우 제어 엘리먼트에서는 참조를 사용하는 대신 컴포넌트 상태를 사용한다.
비제어 컴포넌트는 상태 변경이나 변경을 감지하는 것이 선택 사항이기에 코딩할 양이 적다. 하지만 value를 상태에 연결하거나 하드코딩한 값을 넣을 수가 없다. 넣으려면 제어 엘리먼트를 사용해야 한다. 비제어 엘리먼트에는 value={this.state.email} 처럼 작성할 수 없다는 것이다.
이러한 경우가 필요한 경우는, 예를 들면, 사용자가 일부만 입력하고 저장한 다음 나중에 다시 작성할 때를 생각해보면 된다.
그러면 기본값을 어떻게 지정해야 할까?
기본값 설정
일반적인 HTML이라면, 폼 영역에 value를 작성하면 사용자가 페이지에서 값을 변경할 수 있다. 하지만 React는 value, checked, selected를 뷰와 엘리먼트 내부 상태에서 일관되게 유지한다.
따라서 아래와 같이 하드코딩을 한다면 '읽기 전용'이 된다.
< input type="text" name="success-way" value="success" />
React에서는 특별한 속성인 defaultValue를 이용해 입력 값을 설정하고, 사용자가 폼 요소를 수정할 수 있게 한다.
따라서 아래와 같이 코딩할 수 있다.
< input type="text" name="success-way" defaultValue={this.props.title} />
defaultValue를 대신해 value속성을 사용하면 읽기 전용이 된다. 제어 엘리먼트가 될 뿐 아니라, 사용자가 < input> 요소에 입력을 하더라도 값은 바뀌지 않는다. value를 하드코딩 했기에 React는 해당 값을 유지하고자 하는 것이다.
위의 코드와 같이 속성을 통해 입력 값을 가져올 수도 있고, 상태를 통해 가져올 수도 있다.
React의 defaultValue 기능은 주로 비제어 컴포넌트와 함께 사용된다. 하지만 참조를 사용하면 defaultValue를 제어 컴포넌트나 다른 경우에도 사용할 수 있다. 제어 컴포넌트에서는 생성자에서 기본값을 상태로 설정하기에 크게 중요하진 않다. 아래와 같이 말이다.
Ex) this.state={defaultName : 'success boy'}