Roen의 iOS 개발로그

React 기초4 : <Form>에서 받은 여러 <input>의 state 처리(양방향 바인딩)

by Steady On

📣 이 자료는 Udemy의 React 완벽 가이드 강의와 React 공식문서를 참고하여 작성되었습니다.

그리고 개인적으로 공부하고 이해한 내용을 바탕으로 정리하는 용도로 작성되었으므로 이해를 돕기위한 예시는 최소화하였습니다.

 

<Form>에서 받아온 여러 <input>값을 state로 처리하는 방법

더보기

먼저, input 태그에 대한 mdn docs의 참고 자료 : https://developer.mozilla.org/ko/docs/Web/HTML/Element/Input

 

<input>: 입력 요소 - HTML: Hypertext Markup Language | MDN

HTML <input> 요소는 웹 기반 양식에서 사용자의 데이터를 받을 수 있는 대화형 컨트롤을 생성합니다.

developer.mozilla.org

 

<input> 요소는 사용자의 데이터를 받을 수 있는 컨트롤을 생성하는 태그이다. <input>은 type 특성에 따라 유형이 크게 달라지는데, 일단 기본값은 text이다.

몇가지 타입에 대해 자세히 살펴보자면,

  1. button : <button> 요소가 없었던 시절에는 <input type="button">으로 해서 버튼으로도 사용했지만, <button>이 생긴 뒤로는 그렇게는 잘 안한다는 듯 하다.
  2. number : min과 max 특성으로 각각 최소값과 최대값을 정해줄 수 있다. 그리고 step 특성을 지정하면, input 박스의 끝에 숫자를 올리거나 내릴 수 있는 버튼이 생성되고, 지정해준 step 값 만큼 값을 올리거나 내릴 수 있다.
  3. date : 연/월/일을 포함하지만, 시간은 포함하지 않는 날짜형식이다. 이 특성을 타입으로 지정하면, 입력란을 클릭했을때 달력이 나오면서 날짜를 선택할 수 있게 된다. number와 마찬가지로 min, max를 정해줄 수 있다.
  1. <input> 요소 각각 하나하나마다 state를 만들고, 각각의 이벤트 리스너와 핸들러를 부여하는 방법
  2. <input> 요소를 감싸는 <Form> 요소 하나에 대해 각 <input> 요소에 부여된 name을 key로 하고, value를 value로 하는 객체를 하나의 state로 만들고, 하나의 이벤트 리스너와 핸들러로 정리하는 방법

둘 중 어느쪽도 정답은 없고 본인이 편한대로 만들어 사용하면 된다(이 글에서는 2번 방법을 기준으로 설명할 것이다). 그럼 여기서 주목할 점은 state를 업데이트하는 방식이다. 만약에 우리가 이전 값은 완전히 지워버리고 새로운 값으로 state를 쓸거라면 바로 setState(새로운 값)을 해버리면 된다. 하지만, 그게 아니라, 이전 값을 보존하면서 새로운 값을 추가해주는 거라면? 예를 들어 배열에 새로운 값이 추가되어야 한다거나 객체에 새로운 것이 추가되는 방식(이걸 이전의 state에 의존한다고 표현한다)이라면? 

 

prevState ; 이전 state의 스냅샷

그럴때는 자바 스크립트의 스프레드 연산자를 사용할 수 있다. setState({...state, 새로 추가될 값}) ... 은 State가 예를 들어 어떤 배열이라면 배열의 모든 요소를 밖으로 꺼내는 역할을 한다. 그렇게 해서 이전 state의 스냅샷을 먼저 가져오고 그것에 새로 추가되는 값을 붙여서 업데이트 하라는 코드가 된다. 하지만, 이 방법은 좋은 방법은 아니다. 왜냐하면, 리액트는 상태 업데이트에 대한 스케줄을 가지고 있어서 state가 업데이트 되었을 때 그걸 바로 실행하고 반영하는 것이 아니기 때문이다. 그래서 이론적으로 동시에 수많은 상태 업데이트를 계획한다면 오래되었거나 잘못된 상태 스냅샷에 의존하게 될 수도 있다. 그래서 사용해야 하는 방법이 setState 함수의 매개 함수를 작성해서 이전 state의 스냅샷을 리액트로부터 받아오는 것이다.

setState((prevState) => {
	return {...prevState, 새로 추가될 값}
    })
// return을 생략하고 화살표 옆에 바로 {...prevState, 새로 추가될 값}를 쓰면 SyntaxError가 난다.
// state 이름을 뭘로 지었건 간에 상관없이 매개변수명은 prevState를 쓴다! 그게 약속어!

여기서 setState의 매개함수가 받는 매개변수 prevState는 리액트가 자동으로 가져다주는 이전 state의 스냅샷이다. 이 방법을 사용하면, 리액트가 setState 안에 있는 함수에서 이 상태 스냅샷(prevState)가 가장 최신 상태의 스냅샷이라는 것, 그리고 항상 계획된 상태 업데이트를 염두에 두고 있다는 것을 보장해준다. 그래서 이 방법은 항상 최신 상태의 스냅샷에서 작업하도록 하는 좀 더 안전한 방법이다. 그래서 이전 상태에 따라 상태를 업데이트할 때마다 이 함수 구문을 사용해야 한다.

 

<Form> 요소의 Submit과 Event Listener & Handler

<form> 요소는 자식으로 가지는 요소들에게서 입력된 값을 제출받는다. 그래서 <form> 요소는 자식 요소들로 보통 <input> 요소들과 <button> 요소를 가진다. 그리고 <button>은 대게 type 특성으로 submit을 가지는데, 그럼 버튼을 눌렀을 때, <form>태그 안에 함께 묶인 <input>요소들의 value가 submit 된다.

<form onSubmit={onSubmitHandler}>
	<div>
        <label>뭐할까?</label> // label 요소는 바로 뒤에 오는 input 요소에 라벨을 붙여주는 것!
        <input type='text' name='title' onChange={onChangeHandler} />
        <label>언제하지?</label>
        <input type='date' name='date' onChange={onChangeHandler} />
    </div>
    <button type='submit'>할일 추가!</button>
</form>

그래서 이 submit이라는 이벤트에 대한 리스너와 핸들러가 <button> 태그에 추가되어야 할 것 같지만, button은 이미 submit 속성으로 form 요소에 input값을 submit 해주고 있다. 그래서 submit 속성을 가진 <button>에 onclick 속성으로 이벤트 리스너를 달아주면, 이걸 싸고 있는 <form>요소가 그 이벤트를 생략해버리기 때문에 <button>은 단순히 입력을 제출하도록 하고, <form> 요소에 이벤트 리스너와 핸들러를 달아서 이벤트가 처리될 수 있도록 하는 것이다.

그리고 모든 다른 <input> 태그는 name 속성과 onChange 이벤트 리스너를 부여받는다. name은 여러 input값을 구분하기 위한 key용도로 사용된다. onChange 이벤트 리스너는 <input> 요소에 어떤 변화든지 변화가 일어나면 캐치를 해서 이 input 요소를 객체로 이벤트 핸들러에 전달하고, 그렇게 전달된 이벤트는 입력된 값인 value를 포함하고 있다. 그래서 name을 key로 하고, input 요소의 value를 value로 가지는 객체를 만들 수가 있는 것이다.

 

Event Handler 작성요령

1. onChangeHandler

위에서 onChangeHandler는 input 요소에 발생하는 어떤 변화를 감지해서 event 객체로 받아온다고 말했다. 그리고 그렇게 하는 궁극적인 목적은 <input> 요소에 담겨 있는 입력값을 가지고 오기 위함이다. 그래서 onChangeHandler는 이렇게 작성될 수 있다.

const [state, setState] = useState(
	{ name: '',
      date: ''
    })
    
const onChangeHandler = (event) => {
        const {name, value} = event.target
        setState((prevState) => {return {...prevState, [name]:value}})
   }

먼저 이벤트에서 가져온 data들을 담을 state를 생성한다.

그리고 onChange 이벤트를 통해 받아온 이벤트 객체에서 target이 되는 input 요소의 name과 value를 가져온다. 그리고 그 data를 이전 State에 추가해준다(사실 여기서는 이전 state에 값이 추가된다기 보다 객체의 속성 값을 변경하는 방식이 된다). 그리고 여기서 등장하는 것([name])이 바로 JS의 Dynamic key라는 문법이다. 지금 event.target은 <input> 2개에서 들어오고 있다. 각각의 input은 고유의 name를 가지고 있어서 그것으로 구분되어진다. 즉 name 값은 고정된 어떤 값이 아니라는 뜻이다. 이때 name에 대괄호를 씌워주면, name이 동적할당이 되면서 받아온 <input>의 2개의 객체 각각에서 name이라는 속성을 가져온다.

 

2. onSubmitHandler

<기본 이벤트 방지코드>

이렇게 <form> 요소에서 받아온 이벤트 객체를 Event Handler에서 처리할때는 무조건 첫줄에 추가되어야 하는 코드가 있다!

event.preventDefault()

submit 속성의 button을 누르면 기본적으로 브라우저는 페이지를 다시 로드한다. 이처럼 어떤 이벤트가 발생했을 때 웹 브라우저가 기본적으로 처리해주는 것을 기본 이벤트라고 부르는데, 이러한 기본 이벤트를 막는 것이 저 코드이다.

 

<submit 후 input 요소 초기화 하기>

객체에 초기값을 줄 수 있는 것처럼, input 요소의 value 속성에서 기본값을 줄 수 있다. 위에서 작성한 onChangeHandler에서 우리는 state값의 초기값을 설정했다. 그 초기값을 input.value에 할당하고, submit 이벤트가 일어났을 때, state를 적절하게 처리 후 초기화 해주면 input을 다시 초기화 할 수 있다. 코드로 보자!

const example = () => {
	const initialState = { name: '', date: ''}

    const [state, setState] = useState(initialState)

    const onChangeHandler = (event) => {
            const {name, value} = event.target
            setState((prevState) => {return {...prevState, [name]:value}})
       }
       
    const submitHandler = (event) => {
    	event.preventDefault()
        //들어온 이벤트를 적절히 처리하는 어떤 코드들...//
        setState(initialState)
    }

	return (
    <form onSubmit={onSubmitHandler}>
        <div>
            <label>뭐할까?</label>
            <input type='text' name='title' value={state.title} onChange={onChangeHandler} />
            <label>언제하지?</label>
            <input type='date' name='date' value={state.date} onChange={onChangeHandler} />
        </div>
        <button type='submit'>할일 추가!</button>
    </form>
	)
}

input 요소의 value에 state 객체의 속성이 각각에 맞는 것으로 초기값을 잡았다. 초기값은 빈문자열이므로 아무것도 눈에 보이지 않는다. onChangeHandler와 submitHandler로 각각의 이벤트가 적절한 처리를 거치고 나면, submitHandler의 마지막에서 state를 초기화 해주면, input 요소의 value도 그에 맞게 초기화가 된다!

이렇게 한쪽에서 일방적으로 값을 보내기만 하는게 아니라 양쪽에서 값을 주고 받는걸 '양방향 바인딩'이라고 한다.

 

 

 

 

 

블로그의 정보

Roen의 iOS 개발로그

Steady On

활동하기