일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 백준nodejs
- 백준구현
- 프로그래머스코테
- 포이마웹
- 백준js
- 프로그래머스JS
- 익스프레스
- HTML5
- 다이나믹프로그래밍
- 코테
- 코딩테스트
- HTML
- JS프로그래머스
- 백준
- 안드로이드 스튜디오
- 프로그래머스
- dp알고리즘
- 백준구현문제
- JS
- 리액트커뮤니티
- js코테
- 알고리즘
- 몽고DB
- 리액트
- 리액트댓글기능
- 백준골드
- css기초
- 백준알고리즘
- CSS
- 자바스크립트
- Today
- Total
개발새발 로그
왜 useState의 set함수는 useEffect의 의존성 배열에 넣어도 무한반복이 안 일어날까? 본문
useEffect의 의존성 배열의 경고로부터 매일 혼나다가 의문이 하나 생겼다.
먼저 아래 코드를 보자
useEffect(() => {
setIsEmailDuplicate(true);
setEmailDuplicateCheckMessage('');
}, [values.email, setEmailDuplicateCheckMessage, setIsEmailDuplicate]);
실제 내가 사용했던 코드인데
set함수가 useEffect내에 들어가 있고,
set함수를 의존성 배열에 넣어줬다.
왜 set함수를 의존성 배열에 안넣었어?
의존성 배열에 넣으라는 eslint 경고가 떴다.
근데 나는 이렇게 생각했다.
set함수도 함수니까 의존성 배열에 넣으면 무한 반복 되는거 아니야?
그래서 useCallBack이나 이런걸 써줘야하는 거 아닐까?
근데 위 코드를 그대로 사용했는데도 정상적으로 작동했다!
여기서 의문점이 생긴다.
일반 함수랑 set함수가 무엇이 다르길래 무한 반복이 안될까?
일단 함수는 객체다
컴포넌트가 리-렌더링 되면 참조값이 바뀐다.
그러면 참조값이 바뀌기 때문에 useEffect의 의존성 배열에 일반 함수를 넣으면 무한 반복이 생긴다.
이게 정말 확실한지 테스트 해봤다.
const [count, setCount] = useState(0);
const increment = () => {
console.log('Incrementing count');
setCount(c => c + 1);
};
useEffect(() => {
setCount(c => c + 1);
}, [increment]);
이 코드를 실행 시켜보면 아래와 같이 eslint 경고가 뜨고, 브라우저에서는 무한 반복 오류가 생긴다.
그러면 set함수로 바꿔보자
const [count, setCount] = useState(0);
// 사용안함
const increment = () => {
console.log('Incrementing count');
setCount(c => c + 1);
};
useEffect(() => {
setCount(c => c + 1);
}, [setCount]);
이 코드를 실행하게 되면 정상적으로 작동하게 된다!
도대체 무슨 차이일까?
나는 이 부분이 궁금했다.
일반함수랑 set함수가 어떤 차이점이 있길래 다른 결과를 나타내는 걸까?
일단 추측했을 때 set함수의 참조값이 바뀌지 않는다는 것이었다.
왜냐하면 일반함수는 리-렌더링 후 참조값이 바뀌니까 무한 반복이 일어나는건데
set함수는 리-렌더링 이후에도 참조값이 바뀌지 않는 것이다.
이 추측이 맞는지 보기위해 아래와 같이 테스트 해봤다.
import { useEffect, useState } from 'react';
let previousReferenceValue;
export const useStateTest = () => {
const [count, setCount] = useState(0);
const increment = () => {
console.log('Incrementing count');
setCount(c => c + 1);
};
useEffect(() => {
console.log('increment === increment : ' + (increment === increment)); // 항상 true
}, []);
useEffect(() => {
if (previousReferenceValue) {
console.log('첫 렌더링 이후 : ' + (previousReferenceValue === increment)); // 첫 번째 렌더링 후 true
}
previousReferenceValue = increment;
});
return null;
};
간단하게 설명하면previousReferenceValue라는 전역 변수를 선언하고,
처음 렌더링 이후에 increment의 참조값을 넣어줬다.
그러면 previousReferenceValue에는 increment의 이전 메모리 주소 값이 들어간다.
그리고 다시 렌더링이되어서 previousReferenceValue와 increment 함수의 메모리 참조 값을 비교하면 아래와 같이 다르다고 나온다.
이건 우리가 예상한 결과다
렌더링 이후에는 함수의 참조값이 달라진다.
그러면 useState의 set함수는 어떨까? 너무 궁금했다.
let previousReferenceValue;
export const useStateTest = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('increment === increment : ' + (setCount === setCount)); // 항상 true
}, []);
useEffect(() => {
if (previousReferenceValue) {
console.log('첫 렌더링 이후 : ' + (previousReferenceValue === setCount)); // 첫 번째 렌더링 후 true
}
previousReferenceValue = setCount;
});
return null;
};
이전과 똑같다고 하고있다!
그렇다면 추측이 맞는 것이다.
set함수는 참조 값이 변하지 않고 있다.
그러면 더 궁금해진다.
set함수는 어떻게 해서 참조값이 변하지 않는 것일까?
일단 챗-GPT한테 물어봤다.
React 내부에서는useState를 사용할 때 생성되는 상태 설정 함수가 특정 컴포넌트 인스턴스에 바인딩되어 있으며, 이 함수는 컴포넌트의 라이프사이클 동안 동일한 참조를 유지합니다.
이는 React의 훅 관리 시스템과 상태 저장 메커니즘이 해당 함수를 재사용하고, 컴포넌트의 상태를 올바르게 관리할 수 있도록 설계되었기 때문입니다.
React의 내부 설계때문인가..?
그래서 계속 물어봤다.
setCount와 같은 상태 설정 함수(setter함수)의 참조가 React에서 안정적으로 유지되는 것은 React의 내부 구현과 설계 철학 때문입니다.
이러한 설계는 개발자가 성능 최적화를 직접 관리하는 복잡성을 줄이고, React 애플리케이션의 예측 가능성과 유지 보수성을 향상시키기 위한 것입니다.
React의 불변성 보장
React는 컴포넌트의 상태 관리를 위해 불변성(immutability)을 중요하게 여깁니다. 상태를 직접 변경하지 않고, 상태 변경이 필요할 때마다 새로운 상태를 생성하여 사용하는 것이 불변성의 핵심 원칙입니다. 이 원칙을 따르기 위해, React는 useState 훅을 통해 제공되는 상태 설정 함수의 참조를 변하지 않게 유지합니다. 이를 통해 상태 설정 함수를 안심하고 사용할 수 있으며, 함수의 참조가 변하지 않기 때문에 의존성 배열에 포함시켜도 문제가 발생하지 않습니다.
클로저와 함수 참조
useState에서 반환되는 상태 설정 함수는 클로저를 활용합니다. 클로저는 함수가 생성될 때 그 함수의 스코프에 있는 모든 변수를 "기억"하는 기능을 가지고 있습니다. 이러한 특성 덕분에 상태 설정 함수는 컴포넌트의 재렌더링이 일어나더라도 현재의 상태에 접근할 수 있고, 상태를 올바르게 업데이트할 수 있습니다. React는 이러한 상태 설정 함수를 최적화하여, 함수 자체의 참조가 컴포넌트의 라이프사이클 동안 변하지 않도록 합니다.
성능 최적화
상태 설정 함수의 참조가 변하지 않는 설계는 성능 최적화와 직접적인 관련이 있습니다. 만약 상태 설정 함수의 참조가 컴포넌트의 각 렌더링마다 변한다면, 이 함수를 의존성 배열에 포함하는 모든 useEffect, useCallback, useMemo 등이 불필요하게 재실행될 수 있습니다. 이는 애플리케이션의 성능에 부정적인 영향을 미칠 수 있습니다. 따라서, 상태 설정 함수의 참조를 안정적으로 유지함으로써, React는 불필요한 재계산과 재렌더링을 방지하고 애플리케이션의 성능을 향상시킵니다.
결론적으로, setCount와 같은 상태 설정 함수의 안정적인 참조 유지는 React의 설계 철학과 성능 최적화 전략의 일부입니다. 이를 통해 개발자는 상태 관리의 복잡성을 줄이고, 애플리케이션의 성능과 유지 보수성을 향상시킬 수 있습니다.
사실 이게 맞는 말인지는 확실하지 않지만 일단 리액트 내부에서 그렇게 처리하고 있다는 뜻이었다.
그래서 내부를 뜯어보려고하기 전에
클로저때문이라는 얘기가 있어서 useState를 간단하게 만들어봤다.
let _value;
export function useStateCustom(init) {
if (_value === undefined) {
_value = init;
}
const setState = newValue => {
_value = newValue;
};
return [_value, setState];
}
지금 리액트의 useState가 위처럼 간단하게 만들어진다는 것은 아니지만
클로저 때문인지 확인하기 위해 간단하게 useState을 한번 만들어 보았다.
그리고 아래와 같이 적용해봤다.
let previousReferenceValue = null;
export const useStateTest = () => {
const [count, setCount] = useStateCustom(0);
useEffect(() => {
console.log('increment === increment : ' + (setCount === setCount));
}, []);
useEffect(() => {
if (previousReferenceValue) {
console.log('첫 렌더링 이후 : ' + (previousReferenceValue === setCount));
}
previousReferenceValue = setCount;
});
return null;
};
사실 당연히 false가 뜰 것이라 예상했다.
클로저이기 때문에 해당 함수의 참조값이 유지된다는 것은 들어보지 못했다.
그러면 리액트 내부의 코드를 뜯어봐야하나?
https://goidle.github.io/react/in-depth-react-hooks_1/
useState가 어떻게 구성되는지 찾아보자
여기로 들어가서 useState를 검색해보자
이렇게 간단하게 구성되어있다.
뭔가 너무 없지않나? 생각될 것이다.
그럼 dispatcher을 만드는 resolveDispatcher()로 가보자
또 어디서 dispatcher을 가져오고 에러처리를 하고 있다.
그럼 다시 ReactCurrentDispatcher로 가보자
그냥 객체 뿐이다..
아무 것도 없는 것을 볼 수 있는데 여기서 중요하게 볼 점은 전역으로 설정된 녀석이라는 것이다.
전역으로 설정되었다는 것은 클로저 방식을 이용한다는 것이다.
이 녀석이 나중에 외부에서 추가되고 하는 것인데 이후 부분은 다시 공부해서 적어야 될 것 같다..! (꼭 추후에 수정해서 포스팅하겠습니다..!)
현재로서는 setState와 state가 전역으로 설정되는 것으로 이해했다..
'React' 카테고리의 다른 글
객체 구조 분해 할당과 객체 전개 연산자가 번들링 크기를 크게 만든다고?? (0) | 2024.04.03 |
---|---|
너희 Modal은 어떻게 만들어? (0) | 2024.03.31 |
React - 회원가입, 로그인 Form을 좀 더 편하게 쓸 수 없을까? (0) | 2024.03.29 |
NextJS app router에서 router.back()를 사용할 때 알면 좋은 것 (0) | 2024.03.26 |
Modal이 꺼질 때 배경에 있던 요소들이 튀어오르는 문제 (0) | 2024.03.26 |