원문: https://betterprogramming.pub/understanding-the-closure-trap-of-react-hooks-6c560c408cde
좋은글 번역을 허락해주신 bytefish님께 감사드립니다.
현재 우리는 리액트 프로젝트를 개발할 때 일반적으로 훅을 사용합니다.
하지만 개발 과정에서 종종 몇 가지 문제에 직면합니다. 가장 고전적인 문제는 리액트 훅의 클로저 트랩입니다.
몇몇 분들은 비슷한 문제를 겪었을 수 있지만, 리액트의 기본 원리 측면에서 이 문제를 이해하지 못했을 수 있습니다. 자 이제 이 주제에 대해 얘기해 보죠.
여기 간단한 리액트 앱이 있습니다.
import { useEffect, useState } from 'react';
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
setCount(count + 1);
}, 1500);
}, []);
useEffect(() => {
setInterval(() => {
console.log(count);
}, 1500);
}, []);
return <div>Hello world</div>;
}
useState
를 사용해 count
상태를 생성하고, 첫 번째 useEffect
에서 count
의 값을 지속해서 증가시킵니다. 동시에 count
의 최신 값을 또 다른 useEffect
에서 출력합니다.
콘솔에 어떻게 출력될까요?
결과는 다음과 같습니다.
다음은 데모입니다.
콘솔에 우리가 예상했던 0, 1, 2, 3, ...
대신 0, 0, 0...
이 계속 출력됩니다.
이게 클로저 트랩입니다.
memorizedState
라는 속성이 있으며 이는 링크드 리스트(linked list)입니다.memorizedState
링크드 리스트의 노드에 해당하며 해당 노드에서 자신의 값에 접근합니다.예를 들어 위 예에서는 3개의 훅이 있으며 각각은 memorizedState
링크드 리스트의 노드에 해당합니다.
그다음 각 훅은 자신의 memorizedState
에 접근해 로직을 완료합니다.
훅에는 마운트와 업데이트 두 단계가 있습니다.
마운트 함수는 훅이 처음 생성될 때 실행되고, 이후 훅이 업데이트될 때마다 업데이트 함수가 실행됩니다.
다음은 useEffect
의 구현입니다.
여기서 우리는 매개변수 deps
를 어떻게 처리하는지에 주목해야 합니다. 만약 deps
가 undefined라면 deps
는 null로 처리됩니다.
그다음 새로 전달된 deps
와 memorizedState
의 기존 deps
를 비교합니다. 둘이 같다면 이전에 주어진 함수가 그대로 사용되며, 같지 않은 경우 새 함수가 생성됩니다.
두 deps
가 같은지 비교하기 위한 로직은 매우 간단합니다. 만약 이전 deps
가 null
이라면 둘이 같지 않다는 의미로 false
를 즉시 반환합니다. 그렇지 않다면 배열을 순회하며 차례로 비교합니다.
따라서 우리는 세 가지 결론을 얻을 수 있습니다.
useEffect
의 deps
매개변수가 undefined 또는 null인 경우 모든 리렌더링에서 콜백 함수가 재생성되고 실행됩니다.deps
가 빈 배열인 경우 이펙트는 한 번만 실행됩니다.deps
의 각 요소가 변경되었는지 비교해 이펙트의 실행 여부를 결정합니다.이 글을 읽기 전 이미 이러한 결론을 알고 있으셨을지도 모르겠지만, 여기서는 소스코드 관점에서 이해합니다.
useMemo
나 useCallback
같은 훅도 같은 방식으로 deps
를 처리합니다.
위에서 살펴본 내용들로부터 저희는 두 가지를 알 수 있습니다.
useEffect
와 같은 훅은 memriorizedState
의 데이터에 접근합니다.deps
가 같은지 비교해 콜백 함수의 실행 여부를 결정합니다.이제 클로저에 대한 질문으로 돌아옵시다. 다음과 같이 코드를 작성합니다.
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 500);
}, []);
useEffect(() => {
const timer = setInterval(() => {
console.log(count);
}, 500);
}, []);
deps
가 빈 배열이므로 이펙트는 한 번만 실행됩니다.
해당 소스 코드의 구현은 다음과 같습니다.
실행해야 할 이펙트는 HasEffect
로 표시된 후 나중에 실행됩니다.
여기서는 deps
가 빈 배열이므로 HasEffect
플래그가 없습니다. 이펙트는 더 이상 실행되지 않습니다.
그러므로 setInterval
타이머는 한 번만 설정됩니다.. 따라서 콜백 함수가 참조하는 상태는 항상 초기 상태이며 최신 상태를 얻을 수 없습니다.
최신 상태를 얻기 위해서는 리렌더링 할 때마다 fn
을 실행해야 합니다. 즉, count
를 종속성 배열에 넣어야 합니다.
useEffect(() => {
setInterval(() => {
setCount(count + 1);
}, 1500);
}, [count]);
useEffect(() => {
setInterval(() => {
console.log(count);
}, 1500);
}, [count]);
결과는 다음과 같습니다.
fn
이 최신 상태를 얻는 걸로 보이지만, 왜 콘솔에 출력되는 결과는 엉망인 걸까요?
이는 각 이펙트가 타임 인터벌(time interval)을 생성하기 때문입니다. 따라서 이펙트에서 이전 타임 인터벌을 정리해야 합니다.
코드
useEffect(() => {
let timer = setInterval(() => {
setCount(count + 1);
}, 1500);
return () => clearInterval(timer);
}, [count]);
useEffect(() => {
let timer = setInterval(() => {
console.log(count);
}, 1500);
return () => clearInterval(timer);
}, [count]);
온라인 데모
이렇게 마침내 클로저 트랩을 해결했습니다.
memorizedState
라는 링크드 리스트가 파이버 노드에 저장됩니다. 링크드 리스트의 노드는 각각 하나의 훅에 대응하며, 각 훅은 해당 노드의 데이터에 접근합니다.
useEffect
, useMemo
그리고 useCallback
과 같은 훅은 모두 deps
매개변수를 갖습니다. 리렌더링될때마다 새 dep
와 이전 deps
를 비교하며, deps
가 변경되면 콜백 함수가 다시 실행됩니다.
따라서 deps
매개변수가 undefined
혹은 null
인 훅은 매 렌더링마다 실행되고, []
인 훅은 한 번만 실행되며, [state]
인 훅은 상태가 변경될 때만 다시 실행됩니다.
클로저 트랩이 발생하는 이유는 useEffect
와 같은 훅에서 특정 상태를 사용함에도 deps
배열에 추가하지 않아 상태가 변경되어도 콜백 함수가 다시 실행되지 않고 기존 상태를 계속 참조하기 때문입니다.
클로저 트랩은 고치기 쉽습니다. deps
배열을 올바르게 설정하기만 하면 됩니다. 이렇게 하면 상태가 변경될 때마다 콜백 함수가 다시 실행되며 새로운 상태를 참조하게 됩니다. 그렇지만 이전 타이머, 이벤트 리스너 등을 정리하는 데에도 주의를 기울여야 합니다.
다이어그램과 함께 설명하는 자바스크립트 클로저 챌린지 10선
시니어 리액트 개발자를 위한 리액트 훅 챌린지
🚀 한국어로 된 프런트엔드 아티클을 빠르게 받아보고 싶다면 Korean FE Article(https://kofearticle.substack.com/)을 구독해주세요!
Looking for fun and engaging websites? Start by chatting for free at https://chatgratuito.online. For music lovers, create beats with https://incrediboxsprunki.pro. Racing enthusiasts can enjoy high-speed action at https://motox3munblocked.pro or test their parkour skills at https://parkourcivilization.pro. Love winter sports? Glide through snow-filled adventures at https://sledrider3d.com or https://snow-rider-3d.pro. Immerse yourself in creative gaming worlds at https://sprunkigame.pro and https://sprunked.pro. Stickman fans can check out https://stickmanhookunblocked.pro, and casual gamers will love the endless baking fun at https://cookie-clicker-unblocked.pro/.
Escape the ordinary with exciting puzzles at https://escaperoad.fun, and don’t miss out on new discoveries at https://abgerny.buzz!
Immerse yourself in creative gaming worlds at https://sprunki-phase.online
When playing online games, there are plenty of fun options to explore. First, try out https://block-blast.pro/, a free online Tetris puzzle game. Next, enjoy https://trafficracer.online/, where you can play free 3D racing car games. Don’t miss https://sprunkiparasite.com, which offers exciting Parasprunki Mod games, and finally, check out https://footballbros.online/, where you can dive into a world of football games and endless soccer fun.
Play https://sprunkiretake.online Incredibox Phases Horror Mod Games
Sprunki Mustard 🕹 Play Incredibox Mustard Sprunki Mod Games https://sprunki-mustard.online/
Download https://pvz-fusion.online PVZ Fusion Super Hybrid Mod APK & PC Game
Get https://airlinebutler.fun Airline Butler Streaming App for Movies & TV Shows
Play https://myfemboy-roommate.com My Femboy Roommate Online, Free Download APK & PC
Download https://homicipher.fun Homicipher APK Game for Android & PC
All Active Anime Reborn Codes https://animereborncodes.com , November 2024 🕹 Latest Code
The Rise of the Golden Idol https://theriseofthegoldenidol.com/ 🕹 Walkthrough & Play The Case
Five Hearts Under One Roof https://fiveheartsunderoneroof.com
잘 봤습니다! 번역 감사드립니다