오늘 useEffect
를 사용해서 채팅을 개발을 하던 중 무언가 이상한 점이 생겼다. useEffect
로 update 해주는 state가 리랜더링 되는 시간이 이상할 정도로 오래 걸리고 심지어 아예 실행 되지도 않아서 엥. 이게 뭐람. 싶었다.
나는 messages 라는 state가 update 될 때마다 리렌더링 하고자 한다. 그래서 useEffect
를 사용하며 useEffect(func, [messages])
의 형태로 코드를 짰다.
const [message, setMessage] = useState('')
const [messages, setMessages] = useState([])
useEffect(() => {
socket.on('message', (message) => {
setMessages([...messages, message])
})
}, [messages])
위 코드를 간략히 설명하자면, message를 받을 때마다, messages 리스트에 push 해주는 코드이다. 그러나 이 코드는 처음에는 빠른 속도로 messages 리스트가 update 되었으나 점점 느려지고 나중에는 아예 작동하지 않았다.
페이스북 짱. 나와 같은 문제는 이미 리액트 공식문서에 나와 있었다. 그리고 아래와 같은 글을 확인할 수 있었다.
그니까 즉, 내가 개발하고 있던 것은 채팅이다. 그리고 나는 채팅 룸의 사용자들의 모든 대화를 messages라는 state에서 관리하고 있다. 그럼... 채팅의 특성 상 messages 배열은 자주 변경되게 될 것이고, 그래서 종속적인 state가 자주 변경되어서 무한루프에 빠지고 만 것이었다.
빛이스북... 이런 경우 어떻게 해결해야 하는지도 공식문서에 친절하게 설명되어 있다. 그럼 우선 함수적 갱신을 짚고 넘어가자.
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
이전 state를 사용해서 새로운 state를 계산하는 경우 함수를 setState 로 전달할 수 있다. 그 함수는 이전 값을 받아 갱신된 값을 반환한다. 위는 여기에 setState의 양쪽 형태를 사용한 카운터 컴포넌트의 예시다.
빈 종속성 세트, []
는 컴포넌트가 mount 될 때마다 effect가 한 번만 실행되고 매번 렌더링 시에는 실행되지 않음을 의미한다.
useEffect(() => {
socket.on('message', (message) => {
setMessages(preMessages => [...preMessages, message])
})
}, [])
messages state에 의존하는 것이 아니라, 정확한 이전 state를 제공하여 함수 컴포넌트 업데이트 폼으로 state를 update 해주도록 해주면 버그가 싹 사라진다. 편-안. 오늘도 새삼 다시 느끼는 공식 문서의 중요성.