개요
하루하루 나름 꾸준하게 계속해서 React에 대한 개념을 하나씩 잡아가고 있다. 어제는 Component와 Props에 대한 내용을 정리했고, 오늘은 State에 대해 정리해보려고한다.
생각보다 근데 State라는 개념이 어려우면서도 이해가 되는(?) 복잡 오묘하다...
1. State란?
앞서 정리했던 Props는 부모 컴포넌트에서 자식 컴포넌트로 내려주는 읽기만 가능한 (= 자식 컴포넌트에서 변경 불가능한) 데이터라고 했다.
반면, State는 동적인 데이터를 관리하는 객체로 컴포넌트 내부에서 관리되고, 변경될 수 있다.
State가 변경될 때마다 React에서 자동으로 UI를 렌더링 해주기 때문에, 사용자 이벤트에 의해 데이터가 변경되어 새로운 데이터로 렌더링이 필요할 때, State를 사용한다.
1-1. State 생성
함수형 컴포넌트와 클래스형 컴포넌트 사이에서 state를 사용하는 방법은 다른데, 하나씩 알아보자.
클래스형 컴포넌트
-> state를 초기화하려면 constructor (생성자) 에서 this.state를 설정해줘야 한다.
-> state를 변경하려면 this.setState()를 활용해서 변경해야 한다. 얘를 사용하지 않으면 리렌더링이 발생하지 않는다.
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increase</button>
</div>
);
}
}
export default Counter;
: constructor(props)
-> 생성자 함수에서 state에 대한 초기 값을 지정해준다.
: this.state = { count : 0 } ;
-> 초기 상태를 count로 설정한다.
: increment
-> setState를 활용해 count 값을 증가시킨다.
: render
-> state의 Count를 표시하고 버튼을 클릭할 때마다 increment를 호출해 state값을 재할당한다.
함수형 컴포넌트
-> 함수형 컴포넌트는 기본적으로 상태(state)를 가지지 않는 순수 함수(pure function)이다.
-> 그러므로, state를 기본 변수로 선언하면 상태가 유지되지 않는다.
const Counter = () => {
let count = 0;
const increment = () => {
count += 1;
console.log(count);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increase</button>
</div>
);
};
-> 위의 코드에서 count는 일반 변수이기 때문에 리렌더링 될 때마다 결국 0으로 초기화되어 Count가 증가하는 UI를 사용자가 볼 수 없다.
-> 함수형 컴포넌트에서 상태를 가지기 위해서는 Hook 이라는 것을 사용해야 한다.
-> 그 중에서도 useState를 사용하면 상태를 가질 수 있다.
const [state, setState] = useState(initialValue);
: state
-> 현재 상태 값을 저장하는 변수
: setState
-> 상태 값을 변경하는 함수
: initialValue
-> state의 초기값
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0); // 초기값을 0으로 설정
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
<button onClick={() => setCount(count - 1)}>Decrease</button>
</div>
);
};
export default Counter;
-> setCount 값을 지정해줌으로 버튼을 눌렀을 때 각각의 Count 값을 증가시키거나 감소시킨다.
* 순수 함수 만족 조건
-> 같은 입력이 주어지면 항상 같은 결과를 반환해야 함.
-> Side Effect가 없어야 한다.(= 외부 변수나 상태를 가지면 안된다.)
* Hook
-> 함수형 컴포넌트에서 상태(state)와 생명주기(Lifecycle)을 가질 수 있도록 하는 기능 (= 추후 정리)
2. State 사용시 주의점
1) state를 직접 변경해서는 안된다.
const [count, setCount] = useState(0);
const increment = () => {
count += 1; // ❌ 직접 변경하면 안 됨
};
-> count를 직접 변경하더라도 React에서는 이를 감지하지 못하고, 리렌더링 되지 않는다.
const increment = () => {
setCount(prevCount => prevCount + 1);
};
-> setState, setCount를 활용해서 지정해줘야 한다.
2) state는 비동기적으로 동작한다.
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
console.log(count); // 예상값: 1, 실제 출력값: 0 (이전 값이 출력됨)
};
-> React는 UI를 최적화하기 위해 setState를 비동기적으로 처리한다.
-> 즉, setState가 호출되면 즉시 상태를 변경하는 것이 아닌 한 프레임 안에서 여러 개의 상태 변경을 수집한 뒤 한 번의 리렌더링을 수행한다.
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
-> count가 3이 나올 것이라 예상되지만, 실제로는 1만 증가한다.
-> React는 여러 개의 setState 호출을 하나로 묶어(batch) 한 번만 리렌더링하는 배치 업데이트(Batching Update) 방식을 사용한다.
-> 3을 증가시키기 위해서는 함수형 업데이트를 사용하면 된다.
const incrementTwice = () => {
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
};
3) state 값이 이전 상태와 동일하면 리렌더링을 발생시키지 않는다.
const [count, setCount] = useState(0);
const updateCount = () => {
setCount(0); // ❌ 동일한 값으로 설정하면 리렌더링되지 않음
};
4) 객체 변경시 불변성을 유지해야 한다.
const [user, setUser] = useState({ name: "John", age: 30 });
const updateAge = () => {
user.age = 31; // ❌ 직접 변경하면 React가 감지하지 못함
setUser(user);
};
-> 객체를 직접 변경하면 참조값이 동일하기 때문에 변경을 감지하지 못한다.
-> 즉, 상태가 변경되어도 리렌더링이 발생하지 않는다.
const updateAge = () => {
setUser(prevUser => ({ ...prevUser, age: 31 }));
};
-> 위의 코드와 같이 새로운 객체를 생성해야 React가 상태 변화를 감지한다.
* Immer를 통한 객체 갱신 로직
-> 객체의 복사본 생성을 도와주는 유용한 라이브러리이다.
updatePerson(draft => {
draft.artwork.city = 'Seoul';
});
-> 위에서 일반적으로 변경하는 것과 다르게 이전 state를 덮어쓰지 않는다!
-> 이것이 가능한 이유는 Immer에서 draft라는 특별한 객체 타입(=Proxy)을 통해 기록하고, 내부적으로 draft의 어느 부분이 변경되었는지 알아내, 변경사항을 포함해 완전히 새로운 객체를 생성한다.
참고
'Web > React' 카테고리의 다른 글
[React] React Hook (1) | 2025.02.14 |
---|---|
[React] React LifeCycle (0) | 2025.02.11 |
[React] Component와 Props (1) | 2025.02.11 |
[React] Element란? (1) | 2025.02.08 |
[React] JSX란? (0) | 2025.02.08 |