useEffect 실수하기 좋은 부분 바로잡기

미마모코딩·2022년 10월 6일
0

리액트 기본기

목록 보기
3/6
post-thumbnail

오늘은 useEffect에대해 조금 밀도 있게 공부해보자.

먼저 컴포넌트 , react , 브라우저 이렇게 3단계로 잡아 useEffect의 랜더링 프로세스부터 공부해보자.

랜더링 프로세스

import "./App.css";
import { useEffect, useState } from "react";

function App() {
  const [number, setNumber] = useState(0);

  useEffect(() => {
    console.count("useEffect run!");
    document.title = `you clicked ${number} times`;
  });

  return (
    <div className="App">
      <h2> you clicked {number} times</h2>
      <button onClick={() => setNumber((prev) => prev + 1)}>Increase</button>
    </div>
  );
}

export default App;

위 코드에서 랜더링 프로세스 부터 분석해보자.

컴포넌트가 react! html결과를 보여주어야합니다! you clicked {number} times랑 버튼 보여주세요! 그리고 html결과를 다 보여줬다면 반드시 useEffect효과를 적용시키는것을 잊지마세요! 라고 말이다.

그리고 리액트는 브라우저에게 말한다. 브라우저 DOM을 변경하기 위해 몇가지 변경사항이 있다라고 말이다.

브라우저는 이러한 변경사항을 적용하여 화면에 표시한다.

이러한 랜더링 프로세스를 이해했다면 다음스텝을 넘어가보자.

의존성배열

import "./App.css";
import { useEffect, useState } from "react";

function App() {
  const [number, setNumber] = useState(0);
  const [input, setInput] = useState("");

  useEffect(() => {
    console.count("useEffect run!");
    document.title = `you clicked ${number} times`;
  });

  return (
    <div className="App">
      <h2> you clicked {number} times</h2>
      <button onClick={() => setNumber((prev) => prev + 1)}>Increase</button>
      <input
        onChange={(e) => {
          setInput(e.target.value);
        }}
      ></input>
    </div>
  );
}

export default App;

위 코드에서 중요한것은 의존성 배열을 넣지 않고 onChange를 하는 함수를 만들었고 상태를 변경하고있다.

어떤 결과가있을까?

onChange이벤트가 일어나는 순간마다 계속 useEffect를 통해 재 랜더링 되고있다 .

불필요하게 랜더링이 일어나 성능상 문제가 큰 코드가 될 것이다.

그렇게 때문에 의존성 배열을 넣어주어 개선할수있다.

useEffect(() => {
console.count("useEffect run!");
document.title = you clicked ${number} times;
},[number]); 로 개선 할 수 있을것이다.

업데이트함수

다음예제를 살펴보자.

import "./App.css";
import { useEffect, useState } from "react";

function App() {
const [number, setNumber] = useState(0);
useEffect(() => {
  setInterval(() => {
    setNumber(number + 1);
  }, 1000);
}, [number]);
return (
  <div className="App">
    <h1>{number}</h1>
  </div>
);
}

export default App;

클린업함수

useEffect안에 setInterval함수를 등록해 1초마다 스테이트의 값을 1씩 증가시킨다.

하지만 뭔가 이상하게 동작하는 것을 볼 수 있을것이다 . 콘솔로찍어보면 무한루프가 동작하는것을 볼 수 있을 것이다.

이런 문제는 어떻게 해결할까? 바로 전시간 포스팅에서 말했던것처럼 업데이트 함수를 등록해주는것이다.

import "./App.css";
import { useEffect, useState } from "react";

function App() {
const [number, setNumber] = useState(0);
useEffect(() => {
  setInterval(() => {
    setNumber(prev=>prev+1);
  }, 1000);
}, []);
return (
  <div className="App">
    <h1>{number}</h1>
  </div>
);
}

export default App;

setNumber(prev=>prev+1);로 말이다.

하지만 h1태그의 {number}뒤에 텍스트가 들어간다면?

ex)

  <h1>{number}asdas</h1> 처럼 말이다.

그럼 다시 우리가 업데이트 함수를 쓰기전과 동일하게 정상동작 하지 않는다.

우리는 그러면 어떻게 해결해야할까?

바로 클린업 함수를 이용하는것이다.

import "./App.css";
import { useEffect, useState } from "react";

function App() {
  const [toggle, setToggle] = useState(false);
  console.log(toggle);

  useEffect(() => {
    console.log("effect runs!");
    //toggle

    //return a clean up function
    return () => {
      console.log("wait!before running the effect , I should clean here!");
      //clear something from the previous effect
      console.log("okey done!you can run!");
    };
  }, [toggle]);
  return (
    <div className="App">
      <button
        onClick={() => {
          setToggle(!toggle);
        }}
      >
        toggle
      </button>
    </div>
  );
}

export default App;

위와같이 클린업 함수를 만들어서 토글을 해보면 최초1회에는 console.log("effect runs!");만 찍히게된다.

하지만 토클 버튼을 클릭하는 시점부터는

console.log("wait!before running the effect , I should clean here!");이 동작한다.

다음으로는

console.log("okey done!you can run!"); 이 동작한다.

그리고 마지막에

console.log("effect runs!"); 이동작한다.

즉 먼저 정리하는 기능부터 실행이되고 그 후에 effect효과가 일어나는것이다.

이 방식은 메모리 누수를 방지하고 애플리케이션을 훨씬 더 빠르게 동작하게 만든다.

다시 돌아가 setInterval을 개선해보자.

import "./App.css";
import { useEffect, useState } from "react";

function App() {
const [number, setNumber] = useState(0);
useEffect(() => {
  setInterval(() => {
    setNumber(prev=>prev+1);
  }, 1000);
}, []);
return (
  <div className="App">
    <h1>{number}</h1>
  </div>
);
}

export default App;

의 코드에서

import "./App.css";
import { useEffect, useState } from "react";

function App() {
const [number, setNumber] = useState(0);
useEffect(() => {
 const interval = setInterval(() => {
    setNumber(prev=>prev+1);
  }, 1000);
   return ()=>{
    clearInterval(interval)
}
}, []);
return (
  <div className="App">
    <h1>{number}</h1>
  </div>
);
}

export default App;

이렇게 클린업 함수를 사용해 정상동작하게 만들어주면 된다.

클린업 함수는 데이터를 패칭해오는 과정에서도 크게 도움이 되는데

네트워크 상태가 좋지않은 상태면 user 1 링크 user 2 링크 user3 링크가 있다 가정할때

useEffect(()=>{
fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
.then((res)=>res.json())
.then((data)=>setUser(data))
},[id])

위와 같은 코드로 데이터를 패칭한다고 가정해보자.

유저 2로 가는 링크를 클릭
유저 3으로 가는 링크를 클릭
이렇게 하게되면 네트워크 속도가 느리다는 가정하에 3으로 최종적으로 이동해야하는데
2가 아주 잠깐 나왔다가 3으로 가게된다.

이러한 순간에 우리는 클린업 함수를 등록해 상태를 취소하고 업데이트를 하게 만들어야한다.

 useEffect(() => {
  let unsubscribe = false;
  fetch(`http://jsonplaceholder.typicode.com/users/${id}`)
    .then((res) => res.json)
    .then((data) => {
      if (!unsubscribe) {
        setUser(data);
      }
    });
  return () => {
    unsubscribe = true;
  };
}, [id]);

위와 같은 방법으로 말이다

오늘은 useEffect의 조금 심화된 내용을 거쳐봤고 clean up 함수와, 업데이트함수 랜더링 프로세스, 의존성배열의 중요한 내용을 다루었다.

0개의 댓글