리액트, useEffect

라용·2022년 9월 4일
0

위코드 - 스터디로그

목록 보기
29/100

위코드 파운데이션 과정을 들으며 정리한 내용입니다.

Side Effect 발생의 문제점

아래처럼 함수의 return 문 위에서 Side Effect 를 실행하면, Side Effect 가 렌더링을 블락킹할 수 있습니다.

const App = () => {
	const doSideEffect = () => {
		// do some side effect
	};
	
	doSideEffect();
	// 여기서 사이드 이펙트 실행!
	
	return <h1>Hello World</h1>
};

코드는 위에서 아래로 순차적으로 실행하므로, 사이드 이펙트 doSideEffect() 가 끝날 때까지 JSX 를 리턴하는 코드로 넘어가지 않습니다. 사이드 이펙트의 동작이 복잡하다면, 화면 UI 를 바로 렌더링 하지 못해, 사용자에게 안 좋은 경험을 줄 수 있습니다.

두번째로 매번 수행될 필요가 없는 사이드 이펙트가 매 렌더링마다 수행되는 것도 좋지 않습니다.

const App = () ={
	// 코드 생략

	getFeeds();
	// data fetching side effect
	
	return 피드리스트;
}

이 상황에서 화면에 그려지는 피드리스트에 좋아요 기능을 추가한다면, 좋아요를 클릭할 때마다 리렌더링을 실행하게 됩니다. 위와 같이 데이터를 받아오는 데이터 패칭에 대한 사이드 이펙트를 함수 본문에 넣어놨다면, 사소한 리 렌더링이 발생할 때마다 매번 데이터를 패칭하게 됩니다. (비효율적)

위 두가지 문제점을 개선하기 위해 useEffect를 사용합니다. 렌더링을 블락킹하지 않아야하고, 매 렌더링 마다 조건부로 실행을 콘트롤할 수 있어야 합니다.

useEffect 사용하기

리액트에서 useEffect 함수를 import 하고 함수를 인자로 받는 useEffect 함수를 실행합니다. 이 때 인짜로 사용한 콜백함수는 렌더링이 완료된 후(JSX 리턴 후) 실행됩니다.

import React, { useEffect } from "react";

function App() {
	console.log("side effect");
	
	const printConsole = () => {
		console.log("side effect with useEffect");
	};
	useEffect(printConsole);
	
	return <h1>Hello wecode</h1>;
}

export default App;

이제 매번 렌더링 되는 것을 해결하는 방법을 살펴봅니다. 아래처럼 setCount 나 setText 를 통해 count 나 text 상태값이 변할 때 리 렌더링이 진행되는 코드가 있다면, useEffect 를 사용해도 블락킹만 막고, 리 렌더링을 막지는 못합니다.

import React, { useEffect, useState } from "react";

function App() {
	const [count, setCount] = useState(0);
	const [text, setText] = useState("");
	
	useEffect(() => {console.log("change!")}); // 콜백함수 식을 바로 써도 됨
	
	return (
		<div>
			<div className="wrapper">
				<h1>Count: {count}</h1>
				<button onClick={() => setCount(count + 1)}>Up</button>
			</div>
			<input value={text}> onChange={(e)=>{setText(e.target.value)}} />
		</div>
	);
}

export default App;

이를 해결하기 위해서 useEffect 에 의존성 배열이라는 두번째 인자를 넣어주어야 합니다. 첫번째 렌더링에서는 무조건 콜백함수를 실행하지만 두번째 렌더링 부터는 의존성 배열의 값이 변해야 콜백함수를 호출합니다.

useEffect(()=>{
	console.log("count changed")
}, [count]);

위 코드는 count 값이 변했을 때만 콜백함수를 호출하고,

useEffect(()=>{
	console.log("all changed")
}, [count, text]);

위 코드는 count 와 text, 두가지 값중 하나면 변해도 이펙트를 실행합니다.

useEffect(()=>{
	console.log("only first render");
}, []);

이렇게 빈 배열을 넣어야 첫번째 실행 이후 콜백함수가 호출되지 않습니다. (변화를 감지할 값이 없으므로) 이펙트 렌더링 사이클을 한번 돌아보면,

1/ 컴포넌트 렌더링, 최초 렌더링 - mount
2/ useEffect 첫 번째 인자로 넘겨준 콜백 함수 호출 - Side Effect
3/ 컴포넌트의 state 또는 props 가 변경되면 리렌더링 발생 - update
4/ useEffect 두 번째 인자에 들어이는 의존성 배열 확인 - 상태값 변화를 감지하면 콜백함수 실행
5/ state 또는 props 가 변경되면 3-4 과정 반복
6/ 컴포넌트가 더 이상 필요없어지면 (화면에서 사라지면) - unmount

클린업

클린업은 불필요하게 남아있는 사이드 이펙트를 정리하고 치워줍니다.

useEffect(()={
	console.log("Hello World");
});

위와 같이 이후 코드 동작에 영향을 주지 않는 이펙트는 클린업이 필요없지만,

useEffect(()=>{
	setInterval(()=>{
		console.log("interval");
	}, 100);
});

이렇게 setInterval 메소드로 100ms 마다 콘솔을 찍어주는 이펙트는 다른 페이지로 이동해도 불필요하게 동작할 수 있습니다. 이런 동작들은 클린업해야 합니다. 아래 코드는 이펙트에 의존성 배열을 전달하지 않아서 매번 렌더링 될 때마다 이펙트가 발생해 이벤트 리스터를 추가하게 됩니다.

useEffect(()={
	const button = document.getElementById("consoleButton");
	
	const printConsole = () => {
		console.log("button clicked");
	};
	
	button.addEventListener("click", printConsole);
});

다음 이펙트가 발생하기 전에 기존에 부착한 이벤트리스너를 제거하고 새롭게 이펙트를 발생시키기 위해 클린업을 한다면, 아래처럼 동작하고 싶은 것을 함수형태로 만들어서 마지막에 리턴하면 됩니다. 이러면 컴포넌트가 언마운트 될 때도 클린업 함수를 실행해줍니다.

useEffect(()={
	const button = document.getElementById("consoleButton");
	
	const printConsole = () => {
		console.log("button clicked");
	};
	
	button.addEventListener("click", printConsole);
	
	return () => {
		button.removeEventListener("click", paintConsole);
	};
});

또 다른 예시입니다.

useEffect(() => {
	console.log("effect");
	
	const timerID = setInterval(()=>{
		console.log("interval");
	}, 1000);
	
	return () => {
		clearInterval(timerId); // setInterval 해제하는 함수 사용
	};
});

클린업은 불필요한 이펙트를 정리하고 치워줍니다. 클린업할 함수를 해제하는 함수를 리턴문에 넣어서 실행해주면 됩니다.

profile
Today I Learned

0개의 댓글