setState()는 컴포넌트의 state 객체에 대한 업데이트를 실행한다. state가 변경되면, component는 리렌더링 된다.
props와 state는 일반 Javascript의 객체이다.
두 객체 모두 렌더링 결과물에 영향을 주는 정보를 갖고 있는데, 한 가지 중요한 방식에서 차이가 있다.
props는 컴포넌트에 전달되는 반면, state는 컴포넌트 안에서 관리된다는 점이다.
props 컴포넌트로 전달
const element = <Welcome name="Sara" />;
class Welcome extends React.Component {
redner() {
return <h1>Hellow {this.props.name}</h1>;
}
}
function Welcome(props) {
return <h1>Hello {props.name}</h1>;
}
props는 부모 컴포넌트로부터 전달 받는다. 하지만 항상 그러는 것은 아니다.
class Welcome extends React.Component {
redner() {
return <h1>Hellow {this.props.name}</h1>;
}
}
Welcome.defaultProps = {
name: 'world!';
};
function Welcome({ name = "world!" }) {
return <h1>Hello {name}</h1>;
}
위 코드와 같이 defaultProps를 설정해주게 된다면, 부모로부터 props를 전달 받지 않더라도 props를 설정해줄 수 있다.
props는 변하지 않아야 한다.
props는 부모에서 받은 그대로 사용해주는게 좋다. 동일한 입력이 주어지면 항상 동일한 출력을 렌더링 해준다. 그렇게 되면 컴포넌트가 하는 일에 대해 이해하기 쉬워진다.
state를 사용해야하는 경우는 예를 들어, 구성 요소가 렌더링 간에 정보를 추적해야하는 경우로 볼 수 있다.
코드를 보며 살펴보면 이해하기 조금 더 쉬울 수 있다.
먼저 class component를 통해 알아보자
class Button extends React.Component {
constructor() {
super();
this.state = {
count: 0;
};
}
updateCount() {
this.setState((prevState, props) => {
return { count: prevState.count + 1 };
});
}
render() {
return (
<button onClick={() => this.updateCount()}>
Clicked {this.state.count} times
</button>
);
}
}
위 코드를 보면 constructor에서 state.count를 초기값 0으로 초기화 하고
updateCount를 통해 button이 클릭되면 count 값을 1씩 증가시키는 코드입니다.
여기에서 중요한 부분은 상태 값을 업데이트 하기 위해 이전의 상태값을 참조하려면 직접 state에 접근this.setState(this.state.count + 1)
하여 상태의 값을 조작하는 것이 아닌 this.setState의 콜백 함수의 첫 번째 인자(prevState)를 이용하는 것입니다.
updateCount() {
this.setState((prevState) => { count: prevState + 1});
}
// 이렇게 사용하면 안된다!!
updateCount() {
this.setState({count: this.state.count + 1});
}
위 코드는 어떻게 보면 합리적이고 오류가 발생하지 않을 수 있을 것 같지만, count가 동기화 되지 않는 상태에서 this.state.count 값에 접근할 수 있기 때문에 찾기 어려운 오류를 발생시킬 수 있다.
추가적으로 주의해야하는 부분은 count의 상태값을 변경하고 싶다고 하더라도 아래와 같이 this.state.count 의 값을 직접적으로 변경하면 안됩니다. 이러한 방식으로 count의 상태값을 변경하게 된다면 React의 component는 변경을 감지하지 못하고 리렌더링이 발생하지 않습니다.
this.state.count = this.state.count + 1;
위 코드를 함수형 component로 변경한다면 아래와 같이 변경할 수 있습니다.
function Button() {
const [count, setCount] = useState(0);
const updateCount = () => {
setCount((prevCount) => prevCount + 1);
};
return (
<button onClick={updateCount}>
Clicked {count} times
</button>
);
}
this.setState의 호출은 비동기적으로 이뤄진다. 따라서 this.setState 호출 직후 새로운 값이 this.state에 반영될 거라고 믿어서는 안된다. 이전 state 값을 기준으로 값을 계산해야하는 경우에는 this.setState에 {count: this.state.count + 1} 이 아닌 this.state(prevState ⇒ {count: prevState + 1})을 전달해줘야 한다.
React는 모든 컴포넌트가 자신의 이벤트 핸들러에서 setState()를 호출할 때 까지 리렌더링하지 않고 내부적으로 기다리고 있는다. 이를 통해 불필요한 리렌더링을 방지하며 성능을 향상시킨다.
해당하는 컴포넌트에서 state를 사용하는 경우 statefull component 라고 부른다.
function Counter() {
const [count, setCount] = useState(0);
const onClickUpdateCount = () => {
setCount((prev) => prev + 1);
};
return (
<button onClick={onClickUpdateCount}>Clicked {count} times</button>
)
}
해당하는 컴포넌트에서 state를 사용하지 않는 경우 stateless component라고 부른다.
function Welcome({ name }) {
return <h1>Hello, {name}</h1>
}
최대한 stateless component를 사용해주는게 가독성에 유리하고, 가독성에 유리하면 디버깅이나 유지보수에 유리한 측면이 있기 때문에 statefull component를 남발하는 것 보다는 stateless component를 사용해 component의 재사용성도 높이고, 가독성도 챙길 수 있는 component를 생성하는게 중요하다.
React에서는 state에 있는 값을 바꾸기 위해선 setState()를 반드시 거쳐야 하기 때문이다. React에서는 setState()가 호출되면 컴포넌트가 리렌더링 되게 설계 되어 있기 때문입니다.
setState()는 비동기적으로 동작합니다.
setState가 비동기적으로 동작함으로 내부의 일관성을 보장하게 되는데,
state가 동기적으로 업데이트 되더라도 props는 그렇지 않다. 따라서 부모 컴포넌트가 리렌더링 될 때까지 자식 컴포넌트에서는 props를 알 수 없기 때문에 리렌더링이 일어나지 않고 이는 내부의 일관성을 해치게 된다.
또한, 비동기적으로 동작함으로써 setState() 내부에서 렌더링을 조정할 수 있다. 다른 페이지로 넘어갈 때 보통 스피너를 넣는 경우가 많은데 setState() 가 동기적으로 작동한다면 다른 페이지로 넘어가는 시간이 짧더라도 스피너는 항상 보여지게 된다. 이것은 유저 경험을 감소시킨다.
위와 같은 이유 때문에 setState()는 비동기적으로 작동하게 설계되어있다.
const [state, setState] = useState(0);
// ?? X
state = 1;
// O
setState(1);
state의 불변성을 지켜야하는 이유는 React에서 제공하는 setter함수인 setState를 사용하지 않게 되면, previous state가 오염되기 때문이다. 이전상태가 오염되게 되면 React는 이전 상태와 새로운 상태를 얕은 비교와 병합 하는 과정에 방해가 생기게 됩니다.
https://lucybain.com/blog/2016/react-state-vs-pros/, https://ko.reactjs.org/docs/faq-state.html#gatsby-focus-wrapper, https://velopert.com/3629