상태관리, useState에 관하여
state란 상태라는 뜻이며
React, Vue 와 같은 SPA를 쉽게 만들도록 도와주는 웹 프레임워크, 라이브러리에서는
화면을 리렌더링하는 시점, 상황을 쉽게 개발자가 컨트롤 하기 위해 변수 형태로 사용을 합니다.
예를들어, 버튼을 눌렀을 때 화면에 나타나 있는 숫자 값이 증가하는 페이지가 있습니다.
이 때, 버튼을 눌러도 화면에 나타나 있는 숫자 값은 변하지 않습니다. 이미 html파일을 불러온 상태이므로 변수값이 바뀌어 콘솔창에는 증가하는 값이 찍히더라도 화면은 바뀌지 않아야합니다.
동적인 웹 이라는 개념이 생긴 web2가 나타난지 20년 가까이 됐습니다. 그렇기에 사용자들은 내 행동에 따라 응답이 없는 페이지를 기대하고 접속하지 않습니다.
사용자 동작에 맞게 화면을 다시 그리는 행위(리렌더링)를 알맞은 순간에 실행해야합니다.
이 때, 사용하는 것이 reactive 변수, ref 변수, state 변수, property 어노테이션 변수 등등 각 프레임워크 플랫폼에 맞는 단어로 표현됩니다.
각 플랫폼에서는 개발자가 원하는 상황에 리렌더링을 할 수 있게 위 와 같은 개념을 제공합니다.
제가 처음 접한 웹 라이브러리는 'Lit' 이었고 그 다음이 'Vue' 그리고 'React' 였습니다.
각 플랫폼마다 사용하는 이름, 사용하는 방법은 다르지만 모두 원하는 상황에 리렌더링할 수 있게 기능을 제공합니다.
이번 포스팅에는 React의 함수형 컴포넌트에서 사용하는 hook인 useState에 대해서만 얘기하겠습니다.
const [object, setObject] = useState({title: 'title', desc: 'desc'})
const buttonClicked = () => {
test.desc = 'new desc'
console.log(test)
setTest(test)
}
return (
<div>
<button onClick={buttonClicked}>test</button>
<div>
<p>{test.title}</p>
<p>{test.desc}</p>
</div>
</div>
)
위 와 같이 오브젝트 변수 하나를 상태변수로 선언하고
이후, test의 desc값을 변경 한 뒤 setObject(test) 라는 setter를 통해 상태변수에 값을 넣더라도 올바르게 넣더라도 리렌더링이 발생하지 않습니다.
그 이유는, React는 얕은 비교를 통해 참조값이 변경되었는지를 확인하기에 desc의 값이 변경됐음에도 참조하는 주소값이 같은 객체이기때문에 리렌더링이 발생하지 않습니다.
이는 서비스내에서 개발자의 의도와는 다른 결과를 가져다주어 버그를 초래하고 큰 오류를 발생 시킬 수 있습니다.
리렌더링을 위해 buttonClicked 함수를 아래와 같이 변경합니다.
const buttonClicked = () => {
setTest({
...test,
desc: 'new desc'
})
}
참조하는 test객체를 변경함으로서 변경을 알리고 리렌더링을 발생시킵니다.
함수형 컴포넌트가 아닌 클래스 컴포넌트의 경우에는 위와같은 문제가 없지만
불변성을 전제로하는 함수형 컴포넌트와 달리 클래스 컴포넌트는 this.state가 언제든 바뀔 수 있고 어디서든 바꿀 수 있으므로 더 큰 문제를 초래 할 수 있고 모든 예외처리를 진행했다 하더라도 가독성이 떨어진 코드를 작성할 확률이 높아 좋은 형태라고 할 수 없다고 생각됩니다.
클로저를 통해 클래스 컴포넌트 또한 함수형 컴포넌트 처럼 그 당시의 state 값을 출력할 순 있지만, 그렇게 사용하기엔 함수형으로 코드를 작성하는게 더 알맞다고 생각이 되고 클로저 또한 불변성을 가져야하므로 진정으로 올바른 방법이라 생각되지 않습니다.
함수형 컴포너틑와 클래스 컴포넌트는 state 외에도 다양한 차이점이 존재하지만 내용이 많이 다른 포스팅에서 더욱 상세하게 얘기해보겠습니다.
또한 작업을 진행하면서 setState이후 console.log를 통해 값을 찍어보려했지만 초기에 지정한 값이 나오는 등 곧바로 데이터가 변경되지 않아 작업 중 삽질하는 상황이 있었습니다.
처음에는 비동기적으로 데이터가 받아와서 바로 찍히지 않는가 싶었는데 찾아보니
지금 컴포넌트의 state변수 값은 현재의 값만 참조하고 있고 리렌더링이 될 때 함수형 컴포넌트가 새로이 생성되므로 렌더링이 될 때 찍히는 값만 알 수 있었습니다.
setState 이후 변경된 값, 그리고 그 이후 특정 동작을 원한다면 useEffect를 통해 해당 변수가 변경되었을 때 동작을 정의하면 됩니다.
useEffect(() => {
console.log(test
}, [test])
위와 같이 setTest를 통해 test값이 변경되었을 때 동작을 정의하면 됩니다.
React는 라이브러리이므로 프레임워크에 비해 신경써야 하는 부분이 더 많습니다. 그만큼 프레임워크에 비해 가볍고 자유롭기에 개발자 역량이 된다면 더욱 많은 것을 할 수 있을 수도 있습니다.
뭐든지 장점만 있을 순 없고 각자 추구하는 것 장단점이 있기에 목적에 맞게 사용하는 것이 바람직합니다.
useState를 사용하면서 위와 같은 점에서 리렌더링 관련 문제를 접하게 됐고 찾은 정보로 정리를 해봤습니다. 부족한 부분도 있을 수 있으니 문제를 발견하셨다면 언제든지 댓글을 통해 좋은 지식을 공유해주시면 감사하겠습니다.
참고
https://overreacted.io/ko/a-complete-guide-to-useeffect/
useEffect 완벽 가이드
이펙트는 데이터 흐름의 한 부분입니다.
overreacted.io
React에서 useState 리렌더링 문제
시작하기에 앞서 이 글은 개발을 공부하며 새로 알게된 내용을 제 나름의 해석대로 정리한 글입니다. 잘못된 부분이 있다면 저의 올바른 지식향상을 위해 지적해주시기 바라겠습니다.혹시 react
velog.io