5. Input 상태 관리하기

20118 단어 react.jsreact.js

0. Input 태그의 상태 관리

<Input /> 태그는 기본적으로 텍스트를 입력하는 공간을 제공합니다. 이번 장에서는 지난 장에서 언급한 useState() 함수를 사용하여, <Input /> 태그가 하나일 때와 두 개 이상일 때 각각 상태를 어떻게 관리하는지에 대해 알아보겠습니다.
단순할 것 같지만 이번 장을 통해 아래 여러 가지에 대해 알 수 있습니다.

  • 이벤트 객체
  • onChange 이벤트
  • onClick 이벤트
  • <input /> 태그의 value 속성 지정에 따른 상태 변화

1. 단일 Input 상태 관리

입력창에 입력되는 텍스트가 그대로 브라우저에 출력되고, 버튼을 눌러 입력한 텍스트를 초기화하는 예시입니다.

InputSample.js

import React, {useState} from 'react';

function InputSample() {
  const [text, setText] = useState('');
  
  const onChange = (e) => {
    setText(e.target.value)
  }

  const onReset = () => {
    setText('');
  }

  return (
    <div>
      <input onChange={onChange} value={text} placeholder="텍스트를 입력하세요!"/>
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b> {text}
      </div>
    </div>
  );
}

export default InputSample;

App.js

import React from 'react';
import InputSample from './InputSample';

function App() {
  return (
    <InputSample />

  );
}

export default App;


위 파일을 2개를 작성하고 yarn start하면 이런 컴포넌트가 브라우저에 등장합니다. 입력칸에 텍스트를 입력하면 입력하는 그대로 값: 이라고 된 부분에 나타나고, 초기화 버튼을 누르면 입력칸과 값: 의 텍스트가 초기화됩니다.
App.js는 컴포넌트를 삽입하는 부분이니 크게 볼 것이 없습니다. InputSample.js를 보면서 중요한 점들을 짚어 보겠습니다.

먼저, 상태를 정의하는 부분부터 보겠습니다.

  const [text, setText] = useState('');

text라는 상태를 정했습니다. 초기값은 공백('')으로 초기화되었네요. 이것이 <Input /> 태그에 적힐 텍스트의 상태입니다.

그 다음은 InputSample 컴포넌트가 렌더링할 내용을 살펴봅시다.

  return (
    <div>
      <Input onChange={onChange} value={text} placeholder="텍스트를 입력하세요!"/>
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b> {text}
      </div>
    </div>
  );

<Input /> 태그에서는 onChange, value, placeholder 속성을 지정했습니다.
onChangeForm event의 한 종류입니다. Form element의 일종인 <Input />의 입력 내용에 변화가 생길 때마다 onChange 이벤트가 발생합니다. 즉, 위의 onChange={onChange}는 onChange 이벤트가 일어날 때마다 {} 안의 함수가 발동한다는 뜻입니다. value 속성은 Form element에 표시할 내용을 의미합니다. 여기서는 text로 설정되어 있습니다. text가 입력되는대로 입력칸에 텍스트가 보일 것입니다. placeholder는 아무것도 입력되지 않은 입력칸에서 보일 기본 텍스트를 의미합니다.
다음으로 <Button /> 태그를 보면, onClick 속성이 지정되어 있습니다. 이는 Mouse Event로서, 마우스로 하는 행동들과 관련 있는 이벤트입니다. onClick은 마우스 클릭 이벤트를 의미합니다. 마우스 클릭이 발생할 때 {} 안의 함수가 동작합니다.
마지막으로 <div> 태그 안의 내용은 text 상태의 값을 그대로 보여주겠다는 의미입니다.

렌더되는 요소들을 봤으니, 그 안에 있던 이벤트가 발생할 때 동작하던 두 가지 함수를 살펴보겠습니다.

  const onChange = (e) => {
    setText(e.target.value)
    //console.log(e.target)
    //console.log(e.target.value)
  }

  const onReset = () => {
    setText('');
  }

onChange 함수는 <Input/>에서 onChange 이벤트가 발생할 때 동작하는 함수입니다. e라는 파라미터를 받으며 text 상태를 e.target.value로 업데이트합니다. 여기서 받는 파라미터는 e합성 이벤트 객체 라고 합니다(이벤트 객체는 여러 가지 속성을 가지는데 이것이 궁금하신 분들은 공식 문서를 확인해 보세요.). 어떤 이벤트가 발생했을 때 그 내용이 이벤트 객체에 담기며, 이를 파라미터로서 활용할 수 있습니다. e.target에는 '이벤트가 발생한 타겟 DOM'에 대한 정보가 있습니다. 즉, 여기서는 <Input /> DOM 요소에 키보드로 무언가를 입력할 때마다 변화가 발생하므로, e.target<Input />이 됩니다. e.target.value는 '변화가 발생하는 DOM 요소가 가지고 있는 값'을 의미합니다. 여기서는 <Input />에 입력되고 있는 input 요소이겠죠. 궁금하신 분들은 console.log 주석을 풀고 개발자 도구를 확인해 보세요/>

onReset 함수는 파라미터를 받지 않으며 text 상태를 초기화해버리는 역할을 합니다.

컴포넌트 설명은 다 끝났습니다. 이제 이 코드를 실제로 실행하여 테스트해 보시기 바랍니다. 아래와 스크린샷과 같이 입력 내용을 따라 실시간으로 아래에 텍스트가 써지고, '초기화' 버튼을 통해 입력칸과 그 아래 텍스트가 모두 지워지면 잘 되는 것입니다!


2. 여러 Input 상태 관리하기

이번엔 입력창이 여러 개일 때의 텍스트 상태를 관리해 봅시다.

InputSample2.js

import React, { useState } from 'react'

function InputSample2() {
  const [inputs, setInputs] = useState({
    name: '',
    nickname: '',
  })

  const {name, nickname} = inputs;

  const onChange = (e) => {
    const { name, value } = e.target;
 
    setInputs({
      ...inputs,
      [name]: value,
    })
  }

  const onReset = () => {
    setInputs({
      name: "",
      nickname: "",
    })

  }

  return (
    <div>
      <input 
        name="name" 
        placeholder="이름" 
        onChange={onChange} 
        value={name}
      />
      <input 
        name="nickname" 
        placeholder="닉네임" 
        onChange={onChange} 
        value={nickname}
      />
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b> {name} ({nickname})
      </div>
    </div>
  )
}

export default InputSample2;

App.js

import InputSample2 from './InputSample2';


function App() {
  return (
    <InputSample2 />
  );
}

export default App;


<InputSample2 />에서 상태를 정의하는 부분만 봐도 알 수 있듯이, 하나의 인풋창만을 관리할 때와 많이 달라졌습니다. 차근차근히 살펴보겠습니다.

  const [inputs, setInputs] = useState({
    name: '',
    nickname: '',
  })

  const {name, nickname} = inputs;

이번에는 inputs라는 상태를 정의합니다. inputsname, nickname이라는 프로퍼티(property)를 갖는 객체입니다. 객체 형태로 상태를 관리하는 것은 여러 가지의 상태를 한 번에 관리할 때 용이하며 코드를 더 간결하게 만들어 줍니다. 생각해 보세요. inputs 객체가 없으면 namenickname 각각에 대해 Setter 함수를 따로 만들어야 합니다.
그리고 const {name, nickname} = inputs;에서는 비구조화 할당 을 통해 input 객체 안에 있는 namenickname 데이터를 각각 꺼내 변수에 할당하고 있습니다. 이로써, 각 프로퍼티를 활용해야 할 때, input.name 또는 inputs.nickname 과 같이 길게 쓰지 않아도 됩니다.

  return (
    <div>
      <input 
        name="name" 
        placeholder="이름" 
        onChange={onChange} 
        value={name}
      />
      <input 
        name="nickname" 
        placeholder="닉네임" 
        onChange={onChange} 
        value={nickname}
      />
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b> {name} ({nickname})
      </div>
    </div>
  )

그 다음엔 화면에 렌더링되는 요소들을 봅시다. 2개의 <Input />과 1개의 버튼, 그리고 입력값을 그대로 보여주는 <div>가 있습니다.
<Input />에는 name, placeholder, onChange, value의 4가지 속성이 있습니다. 뒤의 세 가지는 위에서도 봤지만, name는 왜 있는 것일까요? name 속성은 해당 요소에 이름을 부여하는 것인데, 이는 Form event인 onChange가 발생할 때 그 이벤트가 발생한 요소를 참조하기 위함입니다. 즉, onChange={onChange}에서 {} 안의 onChange 함수가 작동할 때, 두 가지 <Input /> 태그 중 어떤 것에 변화가 일어나고 있는지는 name을 통해 알 수 있는 것이죠.
<div>namenickname 두 가지 상태를 참조한다는 것 이외에 나머지 속성은 특이할 것이 없습니다.

이제 이벤트 발생 시 작동하는 함수를 정의하는 부분을 봅시다.

  const onChange = (e) => {
    const { name, value } = e.target;
 
    setInputs({
      ...inputs,
      [name]: value,
    })
  }

  const onReset = () => {
    setInputs({
      name: "",
      nickname: "",
    })

  }

onChange 함수는 이벤트 객체 e를 받되, 비구조화 할당을 통해 변수 name에는 e.target.name을, 변수 value에는 e.target.value를 할당하고 있습니다. e.target.name은 onChange 이벤트가 발생하고 있는 요소의 이름을 가리킵니다. 아까 <Input />에서 name= 속성에 'name', 'nickname'을 지정했지요. 따라서 둘 중 어떤 입력창에 텍스트를 적느냐에 따라 e.target.name은 'name', 'nickname' 둘 중 하나가됩니다.
그리고 setInputs Setter 함수를 보면 ...이 나옵니다. 이를 spread 연산자라고 부르며, 어떤 기존의 데이터를 복사하여 새로운 데이터를 만드는 기능을 합니다(이에 대해서는 velopert님께서 설명을 잘 해주셨습니다. 여기를 클릭해서 보러 가기). setInputs({...inputs, [name]: value})의 내용을 해석하자면, Input 상태의 프로퍼티를 그대로 복사하여 만들어진 새 객체의 name 프로퍼티의 value를 value(=e.target.value)로 업데이트 하겠다는 의미입니다. 이는 풀어쓰자면 아래 코드와 같은 의미입니다.

const nextInputs = {
	...inputs,
}
nextInputs[name] = value;

setInputs(nextInputs)

inputs 상태를 복사하여 nextInputs라는 새 상태를 만들고, 그 중 name 프로퍼티의 값을 value로 업데이트한 다음, inputs 상태를 nextInput로 업데이트하는 것입니다. 객체 형태의 상태를 업데이트 하는 흐름이 이해 되시나요?

  • spread 연산자로 기존 객체(A)를 복사한 새 객체 생성(A')
  • 새 객체(A')를 업데이트
  • 기존 객체(A)를 새 객체(A')로 업데이트(setInputs(nextInputs))

왜 객체를 바로 업데이트 하지 않고, 새 객체를 만들어야 할까요? 이는 리액트 상태의 중요한 개념인 불변성 을 지켜야 하기 때문입니다. inpus[name] = value와 같이 객체 형태의 state를 곧바로 수정할 경우, setter 함수를 사용하지 않으므로 state 업데이트가 감지되지 않아 리렌더링이 일어나지 않습니다. 또한 코드 최적화를 위해서라도 불변성을 꼭 유지해 주어야 합니다(이에 대해서도 역시 velopert님께서 알기 쉽게 정리해 주셨습니다... 여기를 클릭하여 설명 보러 가기)

이제 두 개의 입력창의 텍스트를 입력하는대로 브라우저에 잘 출력되는지 봅시다. 초기화 버튼도 잘 동작하나 확인해 보세요.

좋은 웹페이지 즐겨찾기