📌 useEffect는 리액트에서 함수형 컴포넌트, sideEffect를 잘 처리하기 위해 제공되는 훅이다.
- 자세한 설명은 아래 링크 참고.
- https://ko.react.dev/reference/react/useEffect
useEffect – React
The library for web and native user interfaces
ko.react.dev
- 생명주기 메소드를 하나로 통합해서 수행할 수 있게 해준다라고 생각하면 되겠다.
- useEffect는 보통 데이터를 가져올 때 많이 활용하고,이벤트 리스너를 윈도우나 document에 스크롤, 마우스, 키보드 등과 관련된 걸 등록을 했으면, 그것들을 remove 리스너를 통해 언마운트 해주는 역할도 한다고 볼 수 있다.
- 그리고 웹 소켓이나 스트리밍, 푸시 같은 것을 할 때, 데이터를 구독하고, 조작을 직접 할 때도 많이 사용한다.
- 컴포넌트가 렌더링되는 것과 별개로, 다른 연산을 하고 렌더 트리에 영향을 미치는 작업을 하는 것을 '부수 효과'라고 부른다.
- 이번 강의에서는 02-useEffect 폴더를 생성해서 작업을 하는 것 같다.
- 아래는 예제를 위해 바꾼 코드이다.
📌App.tsx
import UseEffectPage from './02-useEffect/UseEffectPage';
import './App.css';
export default function App() {
return (
<>
<UseEffectPage />
</>
)
}
- 별 차이는 없겠지만, 좀더 원활한 이해를 위해서 강의와 똑같이 pnmp을 사용했다.
- 이번에도 tailwind css 사용을 위해, index.css에 @import 'tailwindcss'를 적어주고, vite.config.ts에 plugins: [react(), tailwindcss()], 를 적어주면 된다.
📌아래는 예제이다.
import { useState } from "react"
export default function UseEffectPage() {
const [count, setCount] = useState(0);
const handleIncrease = () => {
setCount((prev) => prev +1);
console.log(count);
};
return (
<div>
<h3>
UseEffectPage
</h3>
<h1>{count}</h1>
<button onClick={handleIncrease}>증가</button>
</div>
)
}
- 이 예제에서는 증가를 비동기적으로 처리되게 하는 걸 알 수 있다.
- handleIncrease 작업이 화면에 업데이트 되기 전의 값이 기억이 되는 형태이다.
- 근데, 화면이 업데이트되고 난 후에 값을 받아보고 싶다면, 아래 코드처럼 바꾸면 된다.
- 자세히 설명하면 일단, useEffect는 다음의 형태를 취한다.
useEffect(() => {}, []);
- {} 이 함수 내부에 실행하고 싶은 코드를 넣으면 된다.
- [] 이것은 dependency array(의존성 배열)이라는 것인데 이는 useEffect에서 매우 중요한 부분으로, 빈 값일 경우는 새로고침을 눌렀을 때 (화면이 새로 켜졌을 때-mount 됐을 때) 한번 부수효과가 일어난다.
[-> useState를 통해 값을 불러옴 -> useEffect가 있다는 것을 리액트에 알림 -> JSX 컴포넌트가 반환이 됨 -> count도 당연히 찍힘 -> 그 다음, console.log가 실행이 됨.] 이 순서가 중요하다.
- useEffect 안에 console.log(count);를 통해 실행을 해보면, 증가 눌러도 아무 동작을 안 할 것이다.
- 여기서 알아야 될 사실이 있다면, 리렌더링 조건이다.
- 리렌더링 조건은 상태가 변화해야 한다. 근데 useEffect도 여러번 실행을 시키고 싶으면 의존성 배열에 조건문을 추가해야 한다.
- 즉, count를 업데이트할 때마다 useEffect 효과를 계속 실행시켜주고 싶다 하면 의존성 배열에 count를 넣어주면 된다. 이러면 이제 증가를 눌러도 바로바로 업데이트 되는 걸 알 수 있다.
=> 결과적으로, handleIncrease에 넣은 것과 다르게, useEffect를 활용한 곳에서는 이전 값이 찍히는 것이 아니라 화면이 업데이트 된 이후의 setState로 업데이트한 값을 반환한 이후에 생성하기 때문에 화면이 항상 최신 값으로 찍히게 된다. 이 동작 과정을 이해하는 것이 상당히 중요하다. 아래가 지금까지 실습한 코드이다.
import { useEffect, useState } from "react"
export default function UseEffectPage() {
const [count, setCount] = useState(0);
const handleIncrease = () => {
setCount((prev) => prev +1);
console.log('setState', count);
};
useEffect(() => {
//실행하고 싶은 코드
console.log(count);
// 의존성 배열이 빈 배열일 경우
// 의존성 배열 (dependency array)
}, [count]); // []: 빈 배열
return (
<div>
<h3>
UseEffectPage
</h3>
<h1>{count}</h1>
<button onClick={handleIncrease}>증가</button>
</div>
)
}
📌 또한, useEffect를 활용할 때 return 함수를 적게 될 텐데((optional) return funciton을 적는데), 이 useEffect 같은 경우는 여러번 동작할 수도 있다.
- 일단, 자세히 알아보기 위해 return function이랑 비슷한 역할을 하는 cleanup function을 이용해 볼 것이다. 이는 청소하는 함수라고 생각하면 되는데,
return () => {
console.log('청소하는 함수입니다.')
}
- 이걸 실행하면 useEffect에서 종속성 값이 변경이 될 때마다 useEffect 훅의 동작 방식은 destroy가 된다. 그때, clenup function을 통해 종료를 하게 되는데 만약 위의 cleanup 함수 없이 종료를 시킬 경우는 상태가 변경됐을 때 useEffect 코드전체가 destroy 됐다가 다시 만들어지기 때문에 계속 값이 겹치면서 실행이 될 것이다.
📌 더욱 자세한 내용은 UseEffectCounterPage.tsx에서 설명한다.
- 여기서는 컴포넌트 2개를 이용할 건데 분리하면 헷갈리기 때문에 하나에 적는다. (export는 2개 있으면 안 되서 하나에만.)
import { useEffect, useState } from "react"
export default function Parent() {
const [visible, seVisible] = useState(false);
return (
<>
<h1>같이 배우는 리액트 #2 useEffect</h1>
<button onClick={() => seVisible(!visible)}>
{visible ? '숨기기' : '보이기'}
</button>
{visible && <Child/>}
</>
)
}
function Child() {
useEffect(() => {
let i = 0;
const countInterval = setInterval(() => {
console.log('Number => ' + i);
i++;
}, 1_000); // 1초마다 타이머가 동작하는 걸 볼 수 있다.
}, []);
// 마진 20 텍스트 4xl
return <div className='mt-20 text-4xl'>Child</div>
}
- 이렇게 되는데 만약에 숨겼을 때, 타이머가 안 동작하게 하고 싶다면, 메모리 상에 남아서 계속 실행되고 있을 것이다.
- 근데 여기서 mount를 한번 더 시킨다면? 한번 더 동작을 할 것이다. 여기서 이제 문제가 발생되는 걸 알 수 있다. 보이기를 하면 타이머 2개가 작동해서 흐름이 꼬이는 현상을 볼 수 있을 것이다.
- 이 현상이 일어나는 이유가 cleanup function을 사용하지 않아서 생기는 문제라는 걸 알 수 있다.
- 이걸 해결하기 위해서 위의 코드를 바꿔준다.
import { useEffect, useState } from "react"
export default function Parent() {
const [visible, seVisible] = useState(false);
return (
<>
<h1>같이 배우는 리액트 #2 useEffect</h1>
<button onClick={() => seVisible(!visible)}>
{visible ? '숨기기' : '보이기'}
</button>
{visible && <Child/>}
</>
)
}
function Child() {
useEffect(() => {
let i = 0;
const countInterval = setInterval(() => {
console.log('Number => ' + i);
i++;
}, 1_000); // 1초마다 타이머가 동작하는 걸 볼 수 있다.
return () => {
console.log('언마운트 될 때 실행됩니다.');
clearInterval(countInterval);
}
}, []);
// 마진 20 텍스트 4xl
return <div className='mt-20 text-4xl'>Child</div>
}
- 이렇게 하면 useEffect 함수가 destroy되기 전에 cleanup function이 작동해서 작동을 하는 걸 알 수 있고, cleanup function에 clearInterval(counterInterval);을 해주면 숨기기를 했을 때 타이머가 동작하지 않도록 할 수 있다. 거기에, 다시 보이기를 누르면 다시 1개의 타이머만 동작하는 걸 볼 수 있다.
📌 다음은 useEffectError.tsx이다.
- useEffect에서는 절대 하면 안 되는 원칙이 하나 있다.
import { useEffect, useState } from "react";
export default function UseEffectError() {
const [counter, setCounter] = useState(0);
const handleIncrease = () => {
setCounter((counter) => counter + 1);
};
useEffect(() => {
setCounter((counter) => counter + 1);
});
return (
<div onClick={handleIncrease}>{counter}</div>
)
}
- 이 코드의 useEffect 함수를 보면 저런 식으로 상태를 업데이트시켜주는 것을 직접 넣으면 안 된다.
- 이런 식으로 할 경우, 값이 무한으로 증가가 된다. (무한 렌더링 발생.)
- 일단, 의존성 배열을 빈 배열로 써주면, 화면이 업데이트 될 때 딱 한번만 즉, setCounter로 counter를 한번 증가시켜주겠다는 것이다.
- 여기에 [counter]를 넣어주게 되면, 아래같은 경우가 발생할 것이다.
import { useEffect, useState } from "react";
export default function UseEffectError() {
const [counter, setCounter] = useState(0);
const handleIncrease = () => {
setCounter((counter) => counter + 1);
};
useEffect(() => {
// 1. 초기 렌더링 시작(counter ++)
setCounter((counter) => counter + 1);
// 2. counter 값이 변경될 때마다 실행
}, [counter]);
// 1번과 2번 과정이 반복해서 일어나니까, 무한 렌더링 사태가 일어남.
return (
<div onClick={handleIncrease}>{counter}</div>
)
}
- 만약에, 데이터가 요청하는 코드가 들어갔다고 생각해보면, 요청이 무한히 가고 DB에서는 우리의 접근을 막게 될 것이다. (이에 관한 처리가 있으면..)
- 그러므로, 이런 패턴은 절대 쓰면 안 되는 패턴이다.
- 즉, useEffect에서 상태를 업데이트하는 함수와 그 함수에 대한 변수를 같이 넣을 경우, 무한 렌더링이 발생하며 에러가 발생하게 되므로 이런 식으로 절대 쓰면 안 된다.
'UMC 8th Web 워크북' 카테고리의 다른 글
🎞️ 영화 리스트 데이터 불러오기 (UMC 3주차 강의 정리) (0) | 2025.04.06 |
---|---|
📜 useEffect 공식문서 간단 정리 (0) | 2025.04.05 |
🌐 다크 모드 적용 (UMC 워크북 강의 참고.) (0) | 2025.03.30 |
🌐TodoList - ts + vite + yarn 세팅(tailwind css 적용.) (0) | 2025.03.29 |
🌐Tailwind Css 파헤쳐 보기💿 (0) | 2025.03.25 |