- Component Lifecycle부터 React.StricMode까지
firebase의 onSnapshot
으로 페이지가 렌더링 되었을 때 데이터를 불러오는 기능을 구현할 때였다.
데이터가 비어있으면 경고모달이 뜨게 하려고 warningEmptyData
함수를 만들었는데 이걸 어디서 호출해야할 지 모르겠는 것이다.
const [listData,setListData] = useState()
...
useEffect(() => {
const queryLists = query(
//code about query
);
const fetchLists = onSnapshot(queryLists, (snapshot) => {
const datas = snapshot.docs.map((doc) => doc.data());
setListsData(datas);
warningEmptyData(datas);1️⃣
});
const warningEmptyData = (data: any[]) => {
if (data.length < 1) {
Modal.warning({
//code about warning contents
}
};
return () => {
fetchLists();
warningEmptyData(datas);2️⃣
}, []);
문제가 해결된 지금 생각해보면 당연히 1️⃣이 맞지만 모를땐 모든 것이 정답처럼 보이므로..
warningEmptyData
를 1️⃣에 놓았을 때와 2️⃣에 놓았을 때 무슨 차이가 있는건지, 심지어 2️⃣에 놓으면 모달이 두번 뜨는데 그건 왜그런지 궁금했다. docs와 chatGPT 선생님의 도움으로 어찌어찌 코드는 작성했지만 작동원리에 대해 모호하게 이해하고 넘어간 부분들을 확실히 하고 싶어서 관련된 개념들을 사부작 사부작 공부해보았다.
onSnapshot
메소드의 스냅샷리스너onSnapshot(1️⃣doc,2️⃣()=>{...})
스냅샷 리스너가 호출되어 데이터베이스의 변경사항을 실시간으로 추적하고 값을 가져오는 메소드. 스냅샷 리스너가 뭔지는 정확히 모르겠으나 아무튼 함수가 호출될 때 같이 호출되는, 계속 데이터베이스를 추적하는 어떤것.. 정도로만 이해했다. 다음과 같은 두개의 인자를 받는다.
const [listData,setListData] = useState()
...
useEffect(() => {
const fetchLists = onSnapshot(queryLists, (snapshot) => {
const datas = snapshot.docs.map((doc) => doc.data());
setListsData(datas);
});
...
return () => {
fetchLists();
}, []);
사실 onSnapshot
메소드를 사용하는 코드를 GPT가 짜줬고 return 부분이 왜 있는지 모호했는데 이유는 스냅샷 리스너를 끄기 위해서였다. 더이상 데이터를 리슨할 필요가 없으면(데이터의 변경사항을 추적할 필요가 없다면) 2️⃣콜백이 호출되지 않도록 리스너를 분리해야(멈춰야)한다.이는 메모리 누수를 방지하는 효과가 있다.
const unsub = onSnapshot(doc,()=>{...}
unsub() 👈 이렇게 호출하면 리스너가 추적을 멈춘다.
onSnaphsot
함수는 리스너를 멈추게 하는 함수를 반환하므로 내 코드에서는 fetchLists()
를 호출한 것이다. 엄밀히 말하면 함수이름을 잘못 지었다. unsubscribe
의 의미를 담는 함수이름으로 고쳐야 한다.
❓그런데 왜 이 함수를 그냥 useEffect()
안에 넣으면 될 것이지 return안에서 호출하는거지?
위 질문의 답을 이해하기 위한 useEffect를 이해하기 위해서는 우선 리액트 컴포넌트의 생명주기, 즉 Lifecycle을 알아야 한다. 개념 하나를 이해하기 위해서 관련된 개념의 개념을 이해해야 하는 초보 개발자의 숙명..
리액트 컴포넌트가 화면에 그려지고 사라지는 것을 마운트(mount)와 언마운트(unmount)라고 한다.
컴포넌트가 브라우저에 나타나고(마운트) 업데이트되고(리렌더링) 사라지는(언마운트) 각각의 시점에 원하는 동작을 할 수 있도록 하는 메소드가 생명주기 LifeCycle 메소드이다. 이는 클래스class 컴포넌트에서 사용된다.
//component 내장메소드 라이프사이클 LifeCycle
//1️⃣ 화면이 모두 그려지고 나서 다음 실행
componentDidMount() {
console.log("그려지고 나서 실행");
}
//2️⃣ 리렌더 수정 완료 후 다음 실행
componentDidUpdate() {
console.log("변경되고 나서 실행");
}
//3️⃣ 해당 컴포넌트 사라지기 전에 마지막으로 실행하고 사라져라
//ex)나가기
componentWillUnmount() {
console.log("사라질 때 실행");
}
❗️그런데 이걸 함수형 컴포넌트에서 구현하고 싶을 때 쓸 수 있는 것이 바로..
함수형 컴포넌트의 생명주기 LifeCycle이 바로 useEffect
였다.
useEffect(1️⃣()=>{...2️⃣return () => {}},3️⃣[])
1️⃣ setup
2️⃣ 클린업 함수 return () => {}
3️⃣ 의존성 배열dependency array
[변수a,변수b]
변수가 포함될 경우 : a 또는 b가 바뀌었을 때만 실행[]
빈배열일 경우 : 컴포넌트 마운트시 한번만 실행결국 페이지가 언마운트 될 때 onSnapshot
의 리스너도 사라져야 하니까 클린업함수를 호출하는 자리인 return 뒤에서 호출하는 것이었다!!
const [listData,setListData] = useState()
...
useEffect(() => {
const queryLists = query(
//code about query
);
const fetchLists = onSnapshot(queryLists, (snapshot) => {
const datas = snapshot.docs.map((doc) => doc.data());
setListsData(datas);
warningEmptyData(datas);1️⃣
});
const warningEmptyData = (data: any[]) => {
if (data.length < 1) {
Modal.warning({
//code about warning contents
}
};
return () => {
fetchLists();
warningEmptyData(datas);2️⃣
}, []);
다시 처음으로 돌아가서 2️⃣에서 warningEmptyData(datas)
를 호출하면 안되겠는 이유를 알았다. 데이터가 없다는 걸 현재 페이지가 나타나자마자 알려줘야지 페이지를 나갈 때 알려주면 뭐하겠어..
그런데.. 내가 이모든 과정을 이해하기 전에 warningEmptyData(datas)
를 2️⃣번 자리에 놓았을 때도 페이지가 마운트되고난 다음에 모달이 떴고, 심지어 두번떴다.
처음에는 리액트 컴포넌트의 렌더링 방식때문인 것 같아 위의 개념들을 공부했던 거고, chatGPT와 Phind 양 선생님과 길고긴 면담을 진행했지만 두분 다 갈피를 못잡고 점점 이상한 소리만 하기 시작했다. 결국 이건 인간의 도움이 필요하단 생각이 들어 stack overflow에 질문을 올렸고 궁금증 해결! 인간 최고!
<React.StrictMode>
는 리액트에서 제공하는 검사도구로 개발모드일때만 디버그를 해서 렌더링을 두번씩 일어나게 한다.
app.js같이 리액트 전체 컴포넌트를 감싸고 있는 파일에서 찾을 수 있다. 이걸 중지시키니까 클린업함수에 넣어놓은 부분이 올바르게 클린업 시점에 작동했다. 렌더링도 한번만 그래서 모달도 한번만..!
참고
React - useEffect 훅 (이펙트 함수, 클린업 함수)
[React] console.log가 두번 실행된다고?
애먼 곳에서 오래 헤맸지만 결국 답을 찾았다.
애매하게 이해하고 어찌저찌 구현만 한 개념들을 더 확실히 머리에 넣었다.
문제를 이해 못하는게 문제,, 개념을 설명하는 개념을 이해 못하지만,, 그래도,, 할수있다,,!!!