logosvg한 입 크기로 잘라먹는 리액트
Search

5. 컴포넌트와 상태

지금까지는 값이 변하지 않는 정적인 리액트 컴포넌트를 만들었습니다. 지금부터는 사용자의 행위나 시간 변동에 따라 값이 변하는 동적인 리액트 컴포넌트를 만들차례입니다. 이를 위해서는 리액트의 핵심 기능 중 하나인 State를 알아야 합니다. 이번 절에서는 State를 이용해 동적인 컴포넌트를 만드는 방법을 살펴보겠습니다.

State 이해하기 

State는 상태라는 뜻입니다. 상태는 어떤 사물의 형편이나 모양을 일컫는 말로 일상 생활에서도 흔히 사용합니다. 
상태는 전구와 스위치에 빗대어 생각하면 쉽게 이해할 수 있습니다. 스위치를끄면 전구에 불이 들어오지 않는데, 이를 ‘소등 상태’라고 할 수 있습니다. 반대로 스위치를 켜면 전구에 불 이 들어오며 이를 ‘점등 상태’라고 할 수 있습니다.
전구의 상태와 상태 변화
전구의 상태와 상태 변화
전구의 상태 변화는 다음과 같이 정리할 수 있습니다. 
  • 전구의 상태는 소등과 점등으로 나눌 수 있다. 
  • 소등 상태일 때 스위치를 켜면 ‘점등’으로 상태 변화가 일어난다. 
  • 점등 상태일 때 스위치를 끄면 ‘소등’으로 상태 변화가 일어난다. 
 
용어를 상태가 아닌 State로 변경하면 다음과 같습니다. 
  • 전구 State는 off(소등), on(점등) 둘 중 하나의 값을 갖는다. 
  • 전구 State의 값이 off일 때 스위치를 켜면 값이 on으로 바뀐다. 
  • 전구 State의 값이 on일 때 스위치를 끄면 값이 off로 바뀐다. 
 
전구의 상태와 리액트 컴포넌트의 State는 매우 유사합니다. 전구의 상태가 상태 변화에 따라 점등 또는 소등으로 변하는 것처럼 리액트 컴포넌트 또한 State 값에 따라 다른 결과를 렌더링합니다.
 

State의 기본 사용법 

직접 State를 만드는 실습을 하면서 리액트의 State가 어떤 개념인지 자세히 알아보겠습니다. 

useState로 State 생성하기 

리액트에서는 함수 useState로 State를 생성합니다.
useState의 문법은 다음과 같습니다.
코드를 불러오는 중 입니다 ...
useState를 호출하면 2개의 요소가 담긴 배열을 반환합니다. 이때 배열의 첫 번째 요소 ‘light’는 현재 상태의 값을 저장하고 있는 변수입니다. 이 변수를 ‘State 변수’라고 부릅니다.
다음으로 두 번째 요소인 setLight는 State 변수의 값을 변경하는 즉 상태를 업데이트하는 함수입니다. 이 함수를 ‘set 함수’라고 부릅니다. useState를 호출할 때 인수로 값을 전달하면 이 값이 State의 초깃값이 됩니다. 위 코드에서는 ‘off’를 전달했으므로 State 변수 light의 초깃값은 off가 됩니다.
Body 컴포넌트에서 숫자를 카운트할 수 있는 State 변수 count를 생성하겠습니다. Body.js를 다음과 같이 수정합니다.
코드를 불러오는 중 입니다 ...
useState는 리액트가 제공하는 State를 만드는 함수입니다. State를 만들기 위해 useState를 react 라이브러리에서 불러옵니다.  함수 useState는 인수로 State의 초깃값을 전달합니다. 코드에서는 초깃값으로 0을 전달합니다. 그 결과 State 변수 count와 set 함수 setCount를 반환합니다.
저장하고 페이지에서 결과를 확인합니다.
State 변수 count를 만들때, 함수 useState에서 인수로 0을 전달했기 때문에 페이지에서는 0을 렌더링합니다.
State 값 렌더링하기
State 값 렌더링하기

set 함수로 State 값 변경하기

이번에는 set 함수를 호출해 State 값을 변경하겠습니다. 컴포넌트에서 버튼을 하나 만들고, 버튼을 클릭할 때마다 State(count) 값을 1씩 늘리겠습니다. Body 컴포넌트를 다음과 같이 수정합니다.
코드를 불러오는 중 입니다 ...
버튼의 이벤트 핸들러 onIncrease에서는 set 함수인 setCount를 호출합니다. 인수로 count에 1 더한 값을 전달합니다.
페이지에서 <+> 버튼을 클릭하면 onIncrease 이벤트 핸들러가 실행됩니다. 함수 onIncrease는 setCount를 호출하고, 인수로 현재의 count 값에 1 더한 값을 전달합니다. 그 결과 State(count) 값은 1 증가합니다.
페이지에서 <+> 버튼을 클릭해 컴포넌트를 어떻게 렌더링하는지 확인합니다.
set 함수로 State 값 변경
set 함수로 State 값 변경
<+> 버튼을 클릭할 때 마다 숫자가 1씩 늘어나는 것을 확인할 수 있습니다.
이렇듯 set 함수를 호출해 State 값을 변경하면, 변경값을 페이지에 반영하기 위해 컴포넌트를 다시 렌더링합니다. 리액트에서는 이것을 ‘컴포넌트의 업데이트’라고 표현합니다.
컴포넌트가 페이지에 렌더링하는 값은 컴포넌트 함수의 반환값 입니다. 따라서 컴포넌트를 다시 렌더링한다고 함은 컴포넌트 함수를 다시 호출한다는 의미와 같습니다. 컴포넌트 함수를 다시 호출한다는 게 어떤 의미인지 직접 확인해 보겠습니다.
다음과 같이 Body를 호출할 때마다 콘솔에 문자열 Update!를 출력하도록 Body 컴포넌트를 수정합니다.
코드를 불러오는 중 입니다 ...
Body 컴포넌트를 호출할 때마다 콘솔에 문자열 Update!를 출력합니다.
저장한 다음, 페이지에서 <+> 버튼을 정확히 8번 클릭한 다음 콘솔을 확인합니다.
컴포넌트의 재호출
컴포넌트의 재호출
처음 나온 Update!는 컴포넌트를 처음 렌더링할 때 출력된 것입니다. 나머지 8번의 Update!는 <+> 버튼을 클릭할 때마다 Body 컴포넌트를 다시 호출하기 때문에 출력 되었습니다.
컴포넌트는 자신이 관리하는 State 값이 변하면 다시 호출됩니다. 그리고 변경된 State 값을 페이지에 렌더링합니다. State 값이 변해 컴포넌트를 다시 렌더링하는 것을 ‘리렌더’ 또는 ‘리렌더링’이라고 합니다. 리액트 컴포넌트는 자신이 관리하는 State 값이 변하면 자동으로 리렌더됩니다.

State로 사용자 입력 관리하기

웹 사이트에서는 다양한 입력 폼을 제공하는데, 사용자는 이 입력 폼을 이용해 텍스트, 숫자, 날짜 등의 정보를 입력합니다. HTML에서 입력 폼을 만드는 태그로는 다양한 형식의 정보를 입력할 수 있는 <Input> 태그, 여러 옵션에서 하나를 선택하도록 드롭다운(DropDown) 목록을 보여주는 <Select> 태그, 여러 줄의 텍스트를 입력할 수 있는 <Textarea> 태그 등이 있습니다.
입력 폼은 로그인, 회원 가입, 게시판, 댓글 등이 필요한 페이지에서 자주 활용되는 웹 개발의 필수 요소입니다. 리액트에서 State를 이용하면 다양한 입력 폼에서 제공되는 사용자 정보를 효과적으로 처리할 수 있습니다.

<input> 태그로 텍스트 입력하기

처음 다룰 입력 폼은 텍스트, 전화번호, 날짜, 체크박스 등 여러 형식의 정보를 입력할 수 있는 <input> 태그가 만드는 폼입니다. <input> 태그로 텍스트를 입력하는 폼을 하나 만들고, 사용자가 텍스트를 입력할 때마다 콘솔에 출력하는 이벤트 핸들러를 구현하겠습니다.
Body 컴포넌트를 다음과 같이 수정합니다.
코드를 불러오는 중 입니다 ...
입력 폼에서 이벤트 핸들러로 사용할 함수 handleOnChange를 만듭니다. 이 함수는 이벤트 객체를 매개변수로 저장해 사용자가 폼에 입력한 값(e.target.value)을 콘솔에 출력합니다.  <input> 태그로 텍스트를 입력할 폼을 만들고, 이 폼의 onChange 이벤트 핸들러로 handleOnChange를 설정합니다. onChange 이벤트는 사용자가 입력 폼에서 텍스트를 입력하면 바로 발생합니다.
✋🏼
<input> 태그로 만들 수 있는 입력 폼은 매우 다양합니다. <input> 태그의 type 속성에서 text를 지정하면 텍스트 폼, date를 지정하면 날짜 형식의 폼, tel을 지정하면 전화번호 형식의 폼을 만듭니다. 이외에도 라디오 버튼이나 체크박스 등도 <input> 태그를 이용해 만들 수 있습니다. type 속성에서 아무것도 지정하지 않으면 기본 입력 폼인 텍스트 폼을 만듭니다.
코드를 저장하고 페이지의 입력 폼에서 임의의 텍스트를 입력합니다. 그리고 개발자 도구의 콘솔에서 어떤 변화가 일어나는지 확인합니다.
입력 폼에 입력한 텍스트를 콘솔에 출력하기
입력 폼에 입력한 텍스트를 콘솔에 출력하기
텍스트를 입력하는 즉시 콘솔에서도 입력한 텍스트를 출력합니다.
이 상태로도 텍스트 입력 폼을 이용해 사용자에게 입력을 받을 수 있습니다. 그 러나 지금은 사용자가 입력한 텍스트가 리액트 컴포넌트가 관리하는 State에 저장되어 있지는 않습니다. 따라서 만약 버튼을 클릭했을 때 사용자가 입력한 텍스트를 콘솔에 출력하는 등의 동작을 수행하게 하려면 돔 API를 이용하는 등 번거로운 작업이 별도로 요구됩니다.
따라서 State를 하나 만들고 사용자가 폼에서 입력할 때마다 텍스트를 State 값으로 저장하겠습니다. Body 컴포넌트를 다음과 같이 수정합니다.
코드를 불러오는 중 입니다 ...
빈 문자열을 초깃값으로 하는 State 변수 text를 생성합니다.  폼에 입력한 텍스트를 변경할 때마다 set 함수를 호출해 text 값을 현재 입력한 텍스트로 변경합니다.  <input> 태그의 value 속성에 변수 text를 설정합니다.  변수 text의 값을 페이지에 렌더링합니다
정리하면 입력 폼에서 사용자가 텍스트를 입력하면 onChange 이벤트가 발생해 이벤트 핸들러 handleOnChnage를 호출합니다. handleOnChange는 내부에서 set 함수를 호출하는데, 인수로 현재 사용자가 입력한 텍스트를 전달합니다. 그 결과 사용자가 폼에서 입력한 값은 text에 저장되면서 State 값을 업데이트합니다.
State 값이 변경되면 컴포넌트는 자동으로 리렌더됩니다. 따라서 페이지에서는 현재의 State 값을 다시 렌더링합니다. 저장하고 페이지의 입력폼에서 임의의 텍스트를 입력해 어떻게 변하는지 확인합니다.
폼에 입력한 텍스트를 페이지에 렌더링하기
폼에 입력한 텍스트를 페이지에 렌더링하기
폼에서 임의의 텍스트를 입력하면, 다음 줄에 입력한 텍스트를 그대로 렌더 링 합니다.

<input> 태그로 날짜 입력하기

<input> 태그에서 type 속성을 "date"로 설정하면 날짜 형식의 데이터를 입력할 수 있습니다. 이번에는 State를 이용해 날짜 형식의 데이터를 입력 정보로 받아보겠습니다.
Body 컴포넌트를 다음과 같이 수정합니다.
코드를 불러오는 중 입니다 ...
<input> 태그에서 type을 date로 설정하면 onChange 이벤트가 발생했을 때 이벤트 객체의 e.target.value에는 문자열로 이루어진 yyyy-mm-dd 형식의 날짜가 저장됩니다.
따라서 날짜 형식을 지정하는 별도의 처리 없이도 텍스트 폼에서 입력할 때 처럼 State 값을 날짜 형식으로 저장할 수 있습니다. 저장한 다음, 변경된 페이지를 확인합니다. 날짜 형식으로 입력할 수 있는 입력 폼이 만들어집니다. 날짜 입력 폼 오른쪽에 나타나는 캘린더 아이콘을 클릭해 날짜를 바꾸면서 State 값이 콘솔에서 어떻게 나타나는지 직접 확인합니다.
입력 폼에서 날짜 입력받기
입력 폼에서 날짜 입력받기
입력 폼에서 날짜를 변경하면 콘솔에서도 변경된 날짜가 바로 출력됩니다.

드롭다운 상자로 여러 옵션 중에 하나 선택하기

<select> 태그는 <option>과 함께 사용합니다. 이 태그를 사용하면 드롭다운 (DropDown) 메뉴로 여러 목록을 나열해 보여 주는 입력 폼이 만들어집니다.
이 폼 목록에서 하나를 선택하면 해당 항목을 입력할 수 있습니다. 드롭다운 입력 폼은 쇼핑몰 사이트에서 여러 옵션을 선택할 때 자주 활용됩니다. 드롭다운 입력 폼에서 입력한 값을 State로 어떻게 처리하는지 알아보겠습니다.
Body 컴포넌트를 다음과 같이 수정합니다.
코드를 불러오는 중 입니다 ...
드롭다운 입력 폼에서 사용자가 옵션을 변경하면 onChange 이벤트가 발생합니다. 이때 이벤트 핸들러에 제공되는 이벤트 객체 e.target.value에는 현재 사용자가 선택한 옵션의 key 속성이 저장됩니다. 만약 사용자가 3번 옵션을 선택하면 해당 옵션의 key 속성인 3번이 e.target.value에 저장됩니다. 따라서 이 값으로 현재 State에 저장된 값을 변경합니다.
저장한 다음, 페이지에서 드롭다운 입력 폼을 클릭해 3번으로 옵션을 변경합니 다. 그리고 콘솔에서 어떤 변화가 있는지 확인합니다.
드롭다운 입력 폼에서 3번을 선택한 결과 콘솔에서는 변경된 값이 3번임을 표시합니다.
드롭다운 입력 폼에서 옵션 선택
드롭다운 입력 폼에서 옵션 선택

글상자로 여러 줄의 텍스트 입력하기

<textarea> 태그는 사용자가 여러 줄의 텍스트를 입력할 때 사용하는 폼을 만듭니다. 이 폼은 웹 페이지에서 사용자가 자기소개와 같이 여러 줄의 내용을 입력할 때주로 활용됩니다. 이 폼을 편의상 글상자라고 하겠습니다. 이번에는 리액트에서 글 상자에 입력한 내용을 State로 어떻게 처리하는지 살펴보겠습니다.
Body 컴포넌트를 다음과 같이 수정합니다.
코드를 불러오는 중 입니다 ...
<textarea>는 <input> 태그의 입력 폼과 동일한 형태로 텍스트를 처리합니다. 사용자가 텍스트를 입력하면 onChange 이벤트가 발생합니다. 이때 이벤트 핸들러는 이벤트 객체의 e.target.value에 저장된 값을 인수로 전달해 State 값을 변경합니다.
저장하고 글상자에서 안녕리액트라고 입력한 다음 콘솔에서 확인합니다.
글상자에서 텍스트 입력
글상자에서 텍스트 입력
글자를 입력할 때마다 콘솔에서는 입력한 텍스트가 바로 표시됩니다.

여러 개의 사용자 입력 관리하기

지금까지 리액트의 State를 이용해 컴포넌트에서 사용자의 입력을 처리하는 방법을 알아보았습니다. 그런데 회원 가입을 유도하는 페이지에는 사용자의 입력 폼이 하나가 아니라 작게는 3개, 많게는 10개까지 되는 곳도 있습니다.
이번에는 여러 개의 사용자 입력을 State로 관리하는 방법을 살펴보겠습니다. 이름, 성별, 출생 연도, 자기소개 등을 한 번에 입력할 수 있도록 Body 컴포넌트를 다음과 같이 수정합니다.
코드를 불러오는 중 입니다 ...
<input> 태그의 입력 폼에서 이름을 받고, State(name)으로 관리합니다.  <select> 태그의 드롭다운 폼에서 성별을 받고, State(gender)로 관리합니다.  type이 date인 <input> 태그의 입력 폼에서 생년월일을 받고, State(birth)로 관리합니다.  <textarea> 태그의 글상자에서 자기소개 내용을 받고, State(bio)로 관리합니다.
총 4개의 State 변수와 이벤트 핸들러를 생성합니다. 저장한 다음, 4개의 입력 폼이 잘 나타나는지, 값은 잘 입력 되는지 확인합니다.
4개의 입력 폼에서 State 관리
4개의 입력 폼에서 State 관리
사용자로부터 여러 입력 정보를 받아 State로 처리하는 경우, 관리할 State의 개수가 많아지면 코드의 길이 또한 길어집니다. 객체 자료형을 이용하면 입력 내용이 여러 가지라도 하나의 State 에서 관리할 수 있어 더 간결하게 코드를 작성할 수 있습니다.
다음과 같이 Body 컴포넌트를 수정하겠습니다.
코드를 불러오는 중 입니다 ...
객체 자료형으로 State를 하나 생성하고 초깃값을 설정합니다. State 변수인 객체 state의 초깃 값은 모두 공백 문자열("")이며, name, gender, birth, bio 프로퍼티가 있습니다. 모든 입력 폼에서 name 속성을 지정합니다. 예를 들어 이름을 입력하는 <input> 태그의 name 속성은 "name"으로 지정합니다. 나머지 태그들도 name 속성을 추가합니다.  모든 입력 폼의 value를 객체 state의 프로퍼티 중 하나로 설정합니다. 예를 들어 이름을 입력하 는 <input> 태그의 value 속성에는 state.name을 지정하는데, 객체 state의 name 프로퍼티와 동일한 값으로 설정합니다. 나머지 태그에도 value 속성을 이 같이 변경합니다. 사용자의 입력을 처리할 이벤트 핸들러 handleOnChange를 생성합니다. 나머지 태그에도 동일하게 생성합니다.
번에서 호출하는 이벤트 핸들러는 이벤트 객체 e를 매개변수로 저장하고 다음과 같이 setState를 호출합니다.
코드를 불러오는 중 입니다 ...
함수 setState에서는 새로운 객체를 생성해 전달합니다. 이때 스프레드 연산자를 이용해 기존 객체 state의 값을 나열합니다. 그리고 객체의 괄호 표기법을 사용하여 입력 폼의 name 속성(e.target.name)을 key로, 입력 폼에 입력한 값(e.target.value)을 value로 저장합니다.
e.target.name은 현재 이벤트가 발생한 요소의 name 속성입니다. 예를 들어 성별을 입력하는 <select> 태그에서 onChange 이벤트가 발생했다면, e.target.name은 gender가 됩니다. 결국 객체 state의 4가지 프로퍼티 중 현재 이벤트가 발생한 요소인 gender 프로퍼티의 value 값을 변경하게 됩니다. 객체 자료형을 이용하면 하나의 State로 여러 개의 입력을 동시에 관리할 수 있습니다. 저장한 다음, 페이지에서 이름은 ‘1’, 성별은 ‘남성’, 날짜는 오늘 날짜, 자기소개에는 ‘1’을 각각 입력한 다음 콘솔에서 확인합니다.
콘솔에서 다음 그림과 같은 결과가 나왔다면 정상적으로 동작하는 겁니다.
입력 폼에서 State가 정상적으로 관리되는지 콘솔에서 확인
입력 폼에서 State가 정상적으로 관리되는지 콘솔에서 확인

Props와 State 

동적으로 변하는 값인 리액트의 State 역시 일종의 값이므로 Props로 전달할 수 있습니다. 이번에는 Body에 자식 컴포넌트를 만들고, Body의 State를 Props로 전달합니다. 다음과 같이 Body.js를 수정합니다.
코드를 불러오는 중 입니다 ...
Viewer 컴포넌트를 선언합니다. 이 컴포넌트에는 Props로 Body 컴포넌트에 있는 State 변수 number가 전달됩니다. Viewer 컴포넌트는 조건부 렌더링을 이용해 변수 number의 값을 평가하고, 값에 따라 짝수 또는 홀수 값을 페이지에 렌더링합니다.  Body에서 Viewer를 자식 컴포넌트로 사용하며, Props로 변수 number를 전달합니다.
코드를 저장하고 페이지에서 결과를 확인합니다. <+> 버튼과 <-> 버튼을 클릭해 값이 어떻게 변하는지 확인합니다.
State 값이 변할 때 자식 컴포넌트의 변화
State 값이 변할 때 자식 컴포넌트의 변화
여기서 알 수 있는 중요한 사실이 있습니다. 바로 자식 컴포넌트는 Props로 전달된 State 값이 변하면 자신도 리렌더된다는 사실입니다. 즉, 부모에 속해 있는 State(number) 값이 변하면 Viewer 컴포넌트에서 구현한 ‘짝수’, ‘홀수’ 값도 따라서 변합니다.

State와 자식 컴포넌트 

부모의 State 값이 변하면 해당 State를 Props로 받은 자식 컴포넌트 역시 리렌더된 다는걸 알았습니다.
그렇다면 부모 컴포넌트가 자식에게 State를 Props로 전달하지 않는 경우는 어떻게 될까요? 그래도 부모 컴포넌트의 State가 변하면 자식 컴포넌트도 리렌더될까요? 다음과 같이 Body.js를 수정합니다.
코드를 불러오는 중 입니다 ...
Viewer 컴포넌트가 Props를 받지 않습니다. console.log를 이용해 리렌더될 때마다 viewer component update!라는 메시지를 콘솔에 출력합니다.  Body도 더 이상 Viewer 컴포넌트에 Props로 State를 전달하지 않습니다.
수정을 완료했다면 <+> 버튼을 정확히 5번 누른 다음 콘솔을 확인합니다.
부모 컴포넌트의 State가 변하면 자식 컴포넌트도 리렌더
부모 컴포넌트의 State가 변하면 자식 컴포넌트도 리렌더
콘솔에서 6번의 viewer component update!가 출력되었습니다. 첫 번째 출력은 Viewer 컴포넌트를 페이지에 처음 렌더링할 때 출력된 것입니다. 나머지 5번은 부모인 Body 컴포넌트의 State가 변할 때마다 출력되었습니다.
리액트에서는 부모 컴포넌트가 리렌더하면 자식도 함께 리렌더됩니다. 사실 지금의 Viewer는 Body 컴포넌트의 State가 변한다고 해서 리렌더할 이유가 없습니다. Viewer 컴포넌트의 내용에는 변한 게 없기 때문입니다.
의미 없는 리렌더가 자주 발생하면 웹 브라우저의 성능은 떨어집니다. 따라서 컴포넌트의 부모-자식 관계에서 State를 사용할 때는 늘 주의가 필요합니다. 리액트에서는 이런 성능 낭비를 막는 최적화 기법이 있는데, 이는 추후에 살펴보겠습니다.
 
PREV4. 이벤트 처리하기
NEXT6. Ref