리액트 State로 사용자 입력 관리하기, Ref
사용자들의 다양한 입력을 받아보고, State로 관리해 볼 것이다.
[input 태그를 State로 관리하는 방법]
const [name, setName] = useState(“”);
사용자가 input에 입력한 값을 만들 State를 만들어준다. 사용자들이 input 태그에 입력하는 메세지들을 name State를 이용해서 관리한다.
<input value ={name}/>
input 태그의 value 속성으로 name 값을 넣어준다. 그러면 value가 빈문자열을 가지는 값으로 고정돼서 뭘 입력해도 input 창에 아무것도 들어가지 않는다.
*input의 value 속성은 input 요소의 초기값을 명시하는 것이다
<input value={name} onChange={(e)=>{
setName(e.target.value)
}}
/>
입력된 값을 실시간으로 name State에 저장해 주기 위해서, input의 값이 변경되었음을 의미하는 이벤트인 onChange 이벤트 핸들러를 설정한다
그리고 매개변수로 이벤트 객체인 e를 받아와서 setName함수를 호출한 뒤 인수로 e.target.value를 전달한다.
이때 e.target.value에서
e.target은 이벤트가 발생한 HTML태그, 즉 <input>을 의미하고,
.value는 input에 입력한 값을 의미함
그래서 input에 12를 입력하면 e.target.value가 12가 되는 것이고, 최종적으로 상태변화함수 setName에 인수로 전달되어 name에 12가 저장된다.
import './Body.css'
import { useState } from 'react'
export default function Body(){
const [name, setName] = useState("");
return(
<div className='body'>
<input value={name} onChange={(e)=>{
setName(e.target.value)
}}/>
</div>
)
}
전체 코드를 해석하면.
1. input 창에 1을 입력하면 onChange 이벤트가 발생하면서 콜백함수가 실행된다.
2. 콜백함수에서는 상태변화함수 setName을 호출하면서 인수로 사용자가 입력한 값을 나타내는 e.target.value를 전달한다
3. 그에 따라 name State값이 1로 업데이트된다.
4. State값이 변함에 따라 Body 컴포넌트가 리렌더링 된다.
console.log(name)을 입력해 보면 input에 입력하는 값이 변할 때마다 바로바로 바뀌는 것을 확인할 수 있다
import './Body.css'
import { useState } from 'react'
export default function Body(){
const [name, setName] = useState("");
const onChangeName = (e) => {
setName(e.target.value)
}
return(
<div className='body'>
<input value={name} onChange={onChangeName}/>
</div>
)
}
이런 식으로 이벤트 핸들러 함수를 따로 선언해서 사용하면 조금 더 깔끔하게 코드를 작성할 수 있다.
사용자의 입력을 State로 관리하면, 단순히 사용자의 입력을 보관하는 용도뿐만 아니라, 실시간으로 변화하는 사용자의 입력에 따라 각기 다른 결과를 렌더링 하도록 만들 수도 있다.
[Select 태그를 State로 관리하는 방법]
gender의 초기값이 “”이고, select 태그의 value값이 gender이므로 초기값은 '밝히지 않음'이다.
‘여성’을 선택하면 onChangeGender 함수가 실행이 되어서 setGender 함수에 e.target.value의 값인 female을 전달한다. 그러면 gender의 값이 female로 업데이트된다. 이후 body 컴포넌트가 리렌더링 되고, gender의 값이 female이므로 select태그도 female에 맞는 요소를 찾아서 보여주게 된다.
[textarea 태그를 State로 관리하는 방법]
textarea도 input이랑 똑같이 설정해 주면 됩니다잉
[State를 조금 더 클린하게 사용하는 방법]
전체 코드를 보면 대부분의 입력태그에 비슷한 형태를 통해 사용자 입력을 State로 보관하는 것을 알 수 있다. 하나의 입력이 있을 때마다 하나의 State를 만들고 전용 이벤트 핸들러까지 비슷하게 만드는 것이다.
아쉬운 점은 사용자로부터 입력받는 폼이 하나하나 늘어날 때마다 새로운 State를 계속 만들어줘야 하고, State가 늘어나면 이벤트 핸들러도 계속 만들어야 하고, value와 onChange에는 각각 전용 이벤트 핸들러를 다 붙어줘야 한다. 추후 복잡한 폼을 가진 회원가입 페이지를 리액트로 만들어야 한다면 코드가 너무 지저분해질 것이다.
한 줄 요약) 복잡해서 요약할 거임
비슷하게 사용되는 3개의 State를 한 번에 통합시킬 것이다.
name, gender, bio 각각의 State였던 것을 하나의 'state'로 통합하고, 그 안에 name, gender, bio가 있는 식으로 구조를 변경했다.
이에 맞게 이벤트 핸들러도 수정할 것이다.
state 값은 name, gender, bio가 들어있는 객체이기 때문에 setState값에도 객체를 넣어준다. 다만 인수로 …state를 먼저 넣는다.
스프레드연산자인 ...state를 먼저 넣는 이유는! onChangeName 함수는 name만 바꾸면 되기 때문이다. gender와 bio는 건들 이유가 없다. 즉 기존의 gender와 bio를 유지시키기 위해서 넣는 것이다
이후 name의 값만 e.target.value로 업데이트 시켜주면 된다. 나머지 핸들러들도 수정해 주면 된다.
마지막으로
value= {name}을 객체에 맞게
state.name으로 변경하면 된다.
나머지 onChange 함수들도 똑같이 바꿨다면 위의 코드과 같을 것이다. 사진을 보면 거의 똑같이 생긴 setState를 호출하고 있다. 즉 비슷하기 때문에 코드를 더 줄일 수 있다.
각각의 input, select, textarea에 값을 name을 설정한다.
각각 분리되어 있던 onChnage 함수를 전부 지우고, 통합해서 사용할 수 있는 onChange 함수를 만든다. 이렇게 되면, 이름을 바꿔도, 성별을 입력해도, bio를 입력해도 onChange가 실행될 것이다.
이때 중요한 것은 setState를 호출하면서 객체 프로퍼티로 어떤 것을 전달하느냐 이다.
앞서 설정한 name값을 사용할 것이다. e.targer.name을 통해, 해당 요소의 name의 값을 추적해서 key 값으로 사용하고, e.target.value를 통해 해당 태그에 입력된 값을 value 값으로 사용한다.
즉 name이 gender인 태그에서 onChange가 호출 됐다면, key 값은 gender이고 value 값은 사용자가 선택한 값이 될 것이다. 이 값이 setState에 인수로 전달된다. 이후 최종적으로 state의 값을 변경시킬 것이다.
[전체 코드]
import './Body.css'
import { useState } from 'react'
export default function Body(){
const [state, setState] = useState({
name : "",
gender : "",
bio : "",
})
const onChange = (e) => {
setState({
...state,
[e.target.name] : e.target.value
})
}
return(
<div className='body'>
<div>
<input name={"name"} value={state.name} onChange={onChange}/>
</div>
<div>
<select name={"gender"} value={state.gender} onChange={onChange}>
<option value="">밝히지 않음</option>
<option value="female">여성</option>
<option value="male">남성</option>
</select>
</div>
<div>
<textarea name={"bio"} value={state.bio} onChange={onChange}/>
</div>
</div>
)
}
내가 정리를 개떡같이 해놔서 스스로도 이해가 잘 안 되니, 전체 코드를 보면서 천천히 이해해 보자.
[Ref란? 레퍼런스 객체다!]
리액트에서 화면에 렌더링 된 DOM요소(HTML 태그)에 접근하기 위해서는 레퍼런스 객체(Ref)를 이용해야 한다.
새로운 레퍼런스 객체를 생성하고, input 태그에 접근해서 회원가입 버튼을 클릭했을 때 이름이 입력되어있지 않다면 focus를 주는 기능을 구현해 볼 것이다!
const onSubmit = () => {
}
<div>
<button onClick={onSubmit}>회원가입</button>
</div>
이전 코드에서 회원가입 버튼을 추가한다.
const nameRef = useRef()
useRef를 import 후 레퍼런스 객체를 새롭게 만들어준다.
useRef()는 레퍼런스 객체를 생성해서 반환한다. 레퍼런스 객체는 어떤 값을 담거나 참조할 수 있는데, 중요한 건 레퍼런스 객체가 담고 있는 값은 컴포넌트가 리렌더링 되어도 그대로 유지된다.
레퍼런스 객체는 특정 DOM 요소를 참조하는 값으로 만들 때 자주 사용하게 된다. 이번에도 input 태그를 레퍼런스 객체로 참조하기 위해서 useRef를 사용한다. 우선 nameRef에 저장된 useRef가 input 태그를 참조하도록 만들어야 한다.
<input ref={nameRef} name={"name"} value={state.name} onChange={onChange}/>
input 태그에 ref={nameRef}를 넣어주면 nameRef 객체가 input 태그를 참조한다. 그리고 다시 onSubmit 함수로 돌아가서,
const onSubmit = () => {
if(state.name === ""){
nameRef.current.focus();
return;
}
alert(`${state.name}님 회원가입을 축하합니다.`);
}
if문을 통해 공백이 있으면 input 태그에 포커스를 주는 코드를 추가한다.
nameRef.current : nameRef가 현재 참조하는 값을 지칭. 위의 코드에서는 input값을 지칭
.focus는 포커스를 말함
*레퍼런스 객체의 current 프로퍼티에는 이 레퍼런스 객체가 현재 참조(접근)하는 값이 저장된다
이제 회원가입 버튼을 눌렀을 때, 이름이 공백이면 input에 포커싱이 생기게 된다.