- State란 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다. (데이터가 저장되는 곳)
- 왜 바뀌어야 할까? -> UI 로의 반영을 위해서!
React에서 값이 바뀔 데이터를 담는 두가지 방법을 비교하며 왜 State를 써야하는지 알아보자.
const root = document.getElementById("root");
let counter = 0; // counter라는 변수를 만들었다
function countUp() {
counter = counter + 1;
}
const Container = () => (
<div>
<h3>Total click : {counter}</h3>
<button onClick={countUp}>Click me!</button>
</div>
);
ReactDOM.createRoot(root).render(<Container />); // 렌더링 한 번만 실행
이렇게 하면 버튼 클릭시 counter는 증가하지면 아직 UI에 반영되지는 않는다.
여기에 있는 코드 어디에서도 UI를 re-rendering 해주고 있지 않기 때문이다.
우리는 컴포넌트를 한 번만 rendering 하고 있기 때문이다.
즉 UI에 반영하려면 countUp() 을 호출할 때마다 rendering을 또 해주어야 한다.
const root = document.getElementById("root");
let counter = 0;
function countUp() {
counter = counter + 1;
ReactDOM.createRoot(root).render(<Container />); // 추가!
}
const Container = () => (
<div>
<h3>Total click : {counter}</h3>
<button onClick={countUp}>Click me!</button>
</div>
);
ReactDOM.createRoot(root).render(<Container />);
이 방법은 우리가 값을 바꿀 때마다 다시 rendering 하는 것을 잊어서는 안된다는 치명적인 단점이 있다.
counter 변수를 생성하는 대신 useState()
함수를 이용해보자!
import React, { useState } from "react";
function App() {
const data = useState();
console.log(data); // 콘솔에 찍어보자!
return <div></div>;
}
export default App;
useState()
를 data라는 변수에 담아 콘솔에 찍어보면 위와 같다.
우리가 useState()
로 우리가 받게 되는 건 undefined
와 함수
가 들어있는 배열이다. (길이가 2인)
const data = useState('hyewon');
으로 초기값을 주면 다음과 같이 출력된다.
이 데이터에 접근하려면 어떻게 해야할까?
import React, { useState } from "react";
function App() {
const data = useState("hyewon");
const counter = data[0];
const setCounter = data[1];
return <div></div>;
}
export default App;
이렇게 할 수도 있겠지만... 더 멋찐 방법이 있다. 구조 분해 할당!
구조분해할당 ?
- 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식
const food = ["tomato", "potato"]; const [myFavFood, mySecondFavFood] = food; console.log(myFavFood); // tomato console.log(mySecondFavFood); // potato
import React, { useState } from "react";
function App() {
const [counter, setCounter] = useState(0); // 구조분해할당
return <div></div>;
}
export default App;
이제 useState() 가 넘겨주는 배열의 두번째 요소인 modifier 함수를 이용하여 값을 바꿔보자.
modifier() 함수는 어떤 값을 부여하든 그 값으로 업데이트하고, re-rendering을 일으킨다.
(보통 첫번째 요소는 counter와 같이 원하는 이름을 붙이고, 두번째 요소는 setCounter와 같이 앞에 "set"을 붙인다.)
import React, { useState } from "react";
function App() {
function onClick() {
setCounter(counter + 1);
}
const [counter, setCounter] = useState(0);
return (
<div>
<h3>Total click : {counter}</h3>
<button onClick={onClick}>Click me!</button>
</div>
);
}
export default App;
버튼 클릭할 때마다 UI에 바로바로 반영이 된다.
(데이터가 바뀔때마다 컴포넌트를 re-rendering하고 UI를 refresh하는 것! -> React의 파워!💪)
버튼 클릭할 때마다 setCounter()라는 modifier 함수로 state를 바꾸게 되고, 이때 새로운 값을 가지고 컴포넌트 전체가 재생성된다. 즉, state가 바뀌면 React 가 새로운 값을 가지고 컴포넌트를 refresh 해준다.
reactJS는 똑똑해서 실제로 바뀌는 값만 판단해서 불필요한 rerendering을 제외한 채로 동작한다!
(<h3>Total click : {counter}</h3>
전체가 리렌더링 되지 않고 {counter}
부분만 리렌더링 된다)
이전의 counter를 이용해서 현재의 counter를 바꾸려면 setCounter() 를 이용해야 한다.
위에서는 setCounter(counter + 1);
과 같이 사용했다.
그러나 counter는 다른 곳에서 변경되어 우리가 생각했던 값이 아니게 될 수도 있어서 별로 좋은 방법은 아니다.
state를 변경하는 방법을 알아보자!
- 직접 할당
- setCounter(counter + 1);
- 함수를 할당 (함수형 업데이트)
- setCounter((current) => current + 1);
둘다 현재의 state를 가지고 새로운 값을 계산해 내지만 2번이 좀 더 안전하다.
2번이 current가 확실히 현재 값이라는 것을 보장하고 있다. 예상치 못한 변경이 어디선가 일어났다 하더라도 혼동을 방지할 수 있다.
// 기존에 우리가 사용하던 방식
setNumber(number + 1);
// 함수형 업데이트 ⭐️
setNumber((currentNumber) => currentNumber + 1);
// src/App.js
import { useState } from "react";
const App = () => {
const [number, setNumber] = useState(0);
return (
<div>
{/* 버튼을 누르면 1씩 플러스된다. */}
<div>{number}</div>
<button
onClick={() => {
setNumber(number + 1); // 첫번째 줄
setNumber(number + 1); // 두번쨰 줄
setNumber(number + 1); // 세번째 줄
}}
>
버튼
</button>
</div>
);
}
export default App;
// src/App.js
import { useState } from "react";
const App = () => {
const [number, setNumber] = useState(0);
return (
<div>
{/* 버튼을 누르면 3씩 플러스 된다. */}
<div>{number}</div>
<button
onClick={() => {
setNumber((previousState) => previousState + 1);
setNumber((previousState) => previousState + 1);
setNumber((previousState) => previousState + 1);
}}
>
버튼
</button>
</div>
);
}
export default App;
일반 업데이트 방식은 버튼을 클릭했을 때 첫번째 줄 ~ 세번째 줄의 있는 setNumber가 각각 실행되는 것이 아니라, 배치(batch)로 처리한다. 즉 우리가 onClick을 했을 때 setNumber 라는 명령을 세번 내리지만, 리액트는 그 명령을 하나로 모아 최종적으로 한번만 실행을 시킨다. 그래서 setNumber을 3번 명령하든, 100번 명령하든 1번만 실행된다.
반면에 함수형 업데이트 방식은 3번을 동시에 명령을 내리면, 그 명령을 모아 순차적으로 각각 1번씩 실행시킨다. 0에 1더하고, 그 다음 1에 1을 더하고, 2에 1을 더해서 3이라는 결과가 우리 눈에 보이는 것이다.😀
📢 리액트는 성능을 위해 setState()를 단일 업데이트(batch update)로 한꺼번에 처리할 수 있습니다.
[출처] React 공식문서
공식문서의 설명처럼, 불필요한 리-렌더링을 방지(렌더링 최적화)하기 위해 즉, 리액트의 성능을 위해 한꺼번에 state를 업데이트 하는 것!