[React #21] State

01. State

  • 현재 가지고 있는 모양이나 형태를 정의하는 값으로 변화할 수 있는 동적인 값이기도 함
  • 리액트의 컴포넌트들은 모두 다 자신의 형태나 모양을 정의하는 State를 가질 수 있음
  • 현재 상태를 보관하는 변수, 상태에 따라서 각각 다른 UI를 화면에 렌더링할 수 있음
  • 하나의 컴포넌트에 여러 개의 State를 만드는 것도 가능함

💡리렌더링?

더보기

컴포넌트가 다시 렌더링 되는 상황, '리렌더'라고도 함

 

 

02. State 생성 방법

01) import

import { useState } from 'react';

 

02) useState 콘솔 출력하기

import './App.css'; import { useState } from 'react'; export default function App() { ​​const state = useState(); ​​console.log(state); ​​return <></>; }

콘솔창

 

 

📌 useState()

- 새로운 state를 생성하는 함수로 두 개의 요소를 담은 배열을 반환

- 첫 번째 요소 : state의 현재 값, 두 번째 요소 : state를 변경시키는 상태 변화 함수

- 상태 변화 함수는 비동기로 동작

- 배열을 반환함으로 보통 비 구조화 할당 문법을 이용함

import './App.css'; import { useState } from 'react'; export default function App() { ​​const [state, setState] = useState(); ​​return ( ​​​​<> ​​​​​​<h1>{state}</h1> ​​​​</> ​​); }
 

 

 

03. State 활용

01) 버튼을 클릭하면 값 증가하기

import './App.css'; import { useState } from 'react'; export default function App() { ​​const [state, setState] = useState(0); ​​return ( ​​​​<> ​​​​​​<h1>{state}</h1> ​​​​​​<button ​​​​​​​​onClick={() => { ​​​​​​​​​​setState(state + 1); ​​​​​​​​}} ​​​​​​> ​​​​​​​​+ ​​​​​​</button> ​​​​</> ​​); }

실행화면

 

- 컴포넌트 내에 새로운 state를 생성하고 state의 값을 변경하면, 리액트가 내부적으로 컴포넌트의 state가 변경되었다는 것을 감지해서 이 컴포넌트를 리렌더링

- 컴포넌트 역할을 하는 함수를 다시 호출하고 새롭게 반환함

02) 버튼으로 토글 효과 주기

import './App.css'; import { useState } from 'react'; export default function App() { ​​const [light, setLight] = useState('OFF'); ​​return ( ​​​​<> ​​​​​​<div> ​​​​​​​​<h1>{light}</h1> ​​​​​​​​<button ​​​​​​​​​​onClick={() => { ​​​​​​​​​​​​setLight(light === 'OFF' ? 'ON' : 'OFF'); ​​​​​​​​​​}} ​​​​​​​​> ​​​​​​​​​​{light === 'OFF' ? '켜기' : '끄기'} ​​​​​​​​</button> ​​​​​​</div> ​​​​</> ​​); }

실행화면

 

💡자바스크립트 변수로 변경하지 않고 State를 쓰는 이유?

더보기

변수의 값이 바뀐다고 컴포넌트가 리렌더링 되지 않음

리액트는 state의 값이 변환했을 때에만 리렌더링이 일어남

→ 그래서 리액트는 가변적인 값을 관리할 때 State를 이용해서 처리함

 

03) props 이용해서 state를 자식 컴포넌트에게 전달

import './App.css'; import { useState } from 'react'; // 전구 역할을 하는 컴포넌트 const Bulb = ({ light }) => { ​​return ( ​​​​<div> ​​​​​​{light === 'ON' ? ( ​​​​​​​​<h1 style={{ backgroundColor: 'orange' }}>ON</h1> ​​​​​​) : ( ​​​​​​​​<h1 style={{ backgroundColor: 'gray' }}>OFF</h1> ​​​​​​)} ​​​​</div> ​​); }; export default function App() { ​​const [count, setCount] = useState(0); ​​const [light, setLight] = useState('OFF'); ​​return ( ​​​​<> ​​​​​​<div> ​​​​​​​​<Bulb light={light} /> ​​​​​​​​<button ​​​​​​​​​​onClick={() => { ​​​​​​​​​​​​setLight(light === 'OFF' ? 'ON' : 'OFF'); ​​​​​​​​​​}} ​​​​​​​​> ​​​​​​​​​​{light === 'OFF' ? '켜기' : '끄기'} ​​​​​​​​</button> ​​​​​​</div> ​​​​​​<hr></hr> ​​​​​​<div> ​​​​​​​​<h1>{count}</h1> ​​​​​​​​<button ​​​​​​​​​​onClick={() => { ​​​​​​​​​​​​setCount(count + 1); ​​​​​​​​​​}} ​​​​​​​​> ​​​​​​​​​​+ ​​​​​​​​</button> ​​​​​​</div> ​​​​</> ​​); }

실행화면

 

- 리액트 컴포넌트들은 자신이 갖는 state가 변경되지 않아도부모로부터 받는 props의 값이 변경되면 리렌더링 됨

- 그래서 count의 값을 변경해도 Bulb도 리렌더링 됨

- 불필요하게 리렌더링 경우가 많아지면 성능이 떨어지기 때문에 컴포넌트를 분리하는게 좋음

 

💡 컴포넌트가 리렌더링 할 때

더보기

1. 자신이 관리하는 state의 값이 변경되었을 때

2. 자신이 제공받는 props의 값이 변경되었을 때

3. 부모 컴포넌트가 리렌더링 될 때

 

04) 컴포넌트 분리

import './App.css'; import { useState } from 'react'; // 전구 역할을 하는 컴포넌트 const Bulb = () => { ​​const [light, setLight] = useState('OFF'); ​​return ( ​​​​<div> ​​​​​​{light === 'ON' ? ( ​​​​​​​​<h1 style={{ backgroundColor: 'orange' }}>ON</h1> ​​​​​​) : ( ​​​​​​​​<h1 style={{ backgroundColor: 'gray' }}>OFF</h1> ​​​​​​)} ​​​​​​<button ​​​​​​​​onClick={() => { ​​​​​​​​​​setLight(light === 'OFF' ? 'ON' : 'OFF'); ​​​​​​​​}} ​​​​​​> ​​​​​​​​{light === 'OFF' ? '켜기' : '끄기'} ​​​​​​</button> ​​​​</div> ​​); }; // 카운터 컴포넌트 const Counter = () => { ​​const [count, setCount] = useState(0); ​​return ( ​​​​<div> ​​​​​​<h1>{count}</h1> ​​​​​​<button ​​​​​​​​onClick={() => { ​​​​​​​​​​setCount(count + 1); ​​​​​​​​}} ​​​​​​> ​​​​​​​​+ ​​​​​​</button> ​​​​</div> ​​); }; export default function App() { ​​return ( ​​​​<> ​​​​​​<Bulb /> ​​​​​​<hr></hr> ​​​​​​<Counter /> ​​​​</> ​​); }

- 전구 컴포넌트와 카운터 컴포넌트를 분리해서 불필요한 리렌더링을 막음

 

 

04. 사용자의 입력값 받기

01) 이름

import React from 'react'; import { useState } from 'react'; function Register() { ​​const [name, setName] = useState(''); ​​const onChangeName = (e) => { ​​​​setName(e.target.value); ​​}; ​​return ( ​​​​<> ​​​​​​<div> ​​​​​​​​<input value={name} onChange={onChangeName} placeholder='이름' /> ​​​​​​​​{name} ​​​​​​</div> ​​​​</> ​​); } export default Register;

- 이벤트 객체의 target에 value는 현재 인풋에 저장되어 있는 값을 보관하고 있음

- e.target.value : 사용자가 인풋에 작성한 텍스트에 접근

- value 값 꼭 설정하기⭐


✏️ setName(e.target.value);

① input에 값을 입력했을 때 onChangeName 함수 실행

② setName 함수를 호출하면서 현재 이 input에 사용자가 입력한 텍스트를 'name'에 전달

 

02) 생일

import React from 'react'; import { useState } from 'react'; function Register() { ​​const [birth, setBirth] = useState(''); ​​const onChangeBirth = (e) => { ​​​​setBirth(e.target.value); ​​}; ​​return ( ​​​​<> ​​​​​​<div> ​​​​​​​​<input value={birth} onChange={onChangeBirth} type='date' /> ​​​​​​​​{birth} ​​​​​​</div> ​​​​</> ​​); } export default Register;

03) 국적

import React from 'react'; import { useState } from 'react'; function Register() { ​​const [country, setCountry] = useState(''); ​​const onChangeCountry = (e) => { ​​​​setCountry(e.target.value); ​​}; ​​return ( ​​​​<> ​​​​​​<div> ​​​​​​​​<select value={country} onChange={onChangeCountry}> ​​​​​​​​​​<option></option> ​​​​​​​​​​<option value={'ko'}>한국</option> ​​​​​​​​​​<option value={'uk'}>영국</option> ​​​​​​​​​​<option value={'us'}>미국</option> ​​​​​​​​</select> ​​​​​​​​{country} ​​​​​​</div> ​​​​</> ​​); } export default Register;

- select 태그는 기본적으로  option 중 맨 위에 있는 값을 초기값으로 설정

- 비어있는 값을 하고싶다면 빈 option 태그 추가하기

- 보통은 선택지에는 친절하고 길게 텍스트를 설정하고 value에는 간결한 값을 사용함

04) 자기소개

import React from 'react'; import { useState } from 'react'; function Register() { ​​const [bio, setBio] = useState(''); ​​const onChangeBio = (e) => { ​​​​setBio(e.target.value); ​​}; ​​return ( ​​​​<> ​​​​​​<div> ​​​​​​​​<textarea ​​​​​​​​​​value={bio} ​​​​​​​​​​onChange={onChangeBio} ​​​​​​​​​​placeholder='자기소개를 입력해 주세요.' ​​​​​​​​/> ​​​​​​​​{bio} ​​​​​​</div> ​​​​</> ​​); } export default Register;

- textarea : input 태그와 동일하지만, 여러 줄의 입력을 받을 수 있음

 

💡 초기값을 설정해주고 싶을 때

useState("이름"); 입력해주고 input의 'value'를 state의 값을 입력해줌

 

실행화면

 

 

05. 비효율적인 코드 개선하기

01) 여러 개의 state를 객체로 만들기

const [input, setInput] = useState({ ​​​​name: '', ​​​​birth: '', ​​​​country: '', ​​​​bio: '', });

 

- useState에 초기 값으로 객체를 넣고 name, birth, country, bio 각각 프로퍼티로 설정

- 비슷한 여러 개의 state를 하나의 객체 값으로 묶어서 하나의 state로 통합해서 관리하면 편함

02) 이벤트 핸들러 합치기

​​const onChange = (e) => { ​​​​setInput({ ​​​​​​...input, ​​​​​​[e.target.name]: e.target.value, ​​​​}); ​​}; ​​ ​​​<input name='name' value={input.name} onChange={onChange} placeholder='이름' />

- 스프레드 연산자를 이용해서 기존의 input state에 들어있던 프로퍼티 값들은 그대로 유지하고, 변경하고 싶은 프로퍼티의 값만 바꿔주기

- 여러 개의 비슷하게 생긴 이벤트 핸들러들은 통합 이벤트 핸들러로 묶어줄 수 있음


✏️모든 input에 onChange 이벤트 함수 실행

① setInput 상태 변화 함수 호출

② 인수로는 객체를 만들어서 전달

③ ...input : 스프레드 연산자로 인풋의 값을 나열해줌

④ [e.target.name] : '프로퍼티의 key'로 이벤트가 발생하는 name으로 변수 설정

 

03) 최종 코드

import React from 'react'; import { useState } from 'react'; function Register() { ​​const [input, setInput] = useState({ ​​​​name: '', ​​​​birth: '', ​​​​country: '', ​​​​bio: '', ​​}); ​​const onChange = (e) => { ​​​​setInput({ ​​​​​​...input, ​​​​​​[e.target.name]: e.target.value, ​​​​}); ​​}; ​​return ( ​​​​<> ​​​​​​<div> ​​​​​​​​<input ​​​​​​​​​​name='name' ​​​​​​​​​​value={input.name} ​​​​​​​​​​onChange={onChange} ​​​​​​​​​​placeholder='이름' ​​​​​​​​/> ​​​​​​​​{input.name} ​​​​​​</div> ​​​​​​<div> ​​​​​​​​<input ​​​​​​​​​​name='birth' ​​​​​​​​​​value={input.birth} ​​​​​​​​​​onChange={onChange} ​​​​​​​​​​type='date' ​​​​​​​​/> ​​​​​​​​{input.birth} ​​​​​​</div> ​​​​​​<div> ​​​​​​​​<select name='country' value={input.country} onChange={onChange}> ​​​​​​​​​​<option></option> ​​​​​​​​​​<option value={'ko'}>한국</option> ​​​​​​​​​​<option value={'uk'}>영국</option> ​​​​​​​​​​<option value={'us'}>미국</option> ​​​​​​​​</select> ​​​​​​​​{input.country} ​​​​​​</div> ​​​​​​<div> ​​​​​​​​<textarea ​​​​​​​​​​name='bio' ​​​​​​​​​​value={input.bio} ​​​​​​​​​​onChange={onChange} ​​​​​​​​​​placeholder='자기소개를 입력해 주세요.' ​​​​​​​​/> ​​​​​​​​{input.bio} ​​​​​​</div> ​​​​</> ​​); } export default Register;

 

 

참고자료

이정환 Winterlood, '한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지'

 

댓글