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) : 기초부터 실전까지'
댓글