Lifecycle과 useEffect

seul_velog·2022년 4월 4일
0

React

목록 보기
5/11
post-thumbnail

1. Lifecycle

  • 각각의 컴포넌트에는 라이프사이클 즉, 컴포넌트의 수명 주기가 존재한다.
  • 컴포넌트의 수명은 보통 페이지에서 렌더링되기 전인 준비 과정에서 시작하여 페이지에서 사라질 때 끝이 난다.
  • 메소드(함수)를 구현해 놓으면 리액트가 알아서 자동적으로 해당 상황에 따라 함수를 호출해 준다.



1-1. 라이프사이클의 분류

✨ 크게 세가지 유형으로 나눌 수 있다.

  • 생성될 때 (마운트) : DOM이 생성되고 웹 브라우저 상에 나타날 경우
    (컴포넌트가 보이는 순간, 페이지에 장착이되는 순간이라고도 표현한다.)

  • 업데이트할 때 (업데이트)
    : 업데이트는 다음과 같은 4가지 상황에서 발생

    • props가 바뀔 때
    • state가 바뀔 때
    • 부모 컴포넌트가 리렌더링 될 때
    • this.forceUpdate로 강제로 렌더링을 트리거할 때
  • 제거할 때 (언마운트) : DOM에서 제거될 경우

📌 Check!
리액트 컴포넌트는 기본적으로 부모컴포넌트가 리렌더링되면 바뀐 내용이 없더라도 자식 컴포넌트 또한 리렌더링된다.
실제 DOM 에 변화가 반영되는 것은 바뀐 내용이 있는 컴포넌트에만 해당하지만, Virtual DOM 에는 모든걸 다 렌더링하고 있다.



1-2. 라이프사이클 메서드

▲ 리액트의 라이프 사이클은 9개가 존재한다.

라이프사이클 메서드(Lifecycle Method)는 '생명주기 메서드'로, 컴포넌트가 브라우저상에 나타나고, 업데이트되고, 사라지게 될 때 (+컴포넌트에서 에러가 났을 때) 호출되는 메서드들이다.

생명주기 메서드는 클래스형 컴포넌트에서만 사용 할 수 있다. useEffect 랑 비슷하다고 생각하면 되지만 작동방식도 많이 다르고 커버하지 않는 기능들도 있다.
(자주 사용하지 않는 메서드도 있으니 어떤 것들이 있는지만 간단하게 살펴보고 위에서 세가지 유형으로 나뉜 도표를 위주로, useEffect 와 같이 공부하자 ✍️ )

1. constructor 🌟
컴포넌트를 만들 때 처음으로 실행된다. 초기 state를 정할 수 있다.
클래스형에서는 constructor, 함수형에서는 useState 를 사용해서 초기 상태를 설정한다.


2. getDerivedStateFromProps
props로 받아 온 값을 state에 동기화시키는 용도로 사용한다.


3. shouldComponentUpdate
props나 state를 변경했을 때, 리렌더링을 할지 말지 결정한다. true나 false를 반환해야 한다.

✍️ 보통은 클래스형 → PureComponent , 함수형 → props는 React.memo, state는 useMemo 를 활용하면 렌더링 성능을 개선할 수 있다고 한다.


4. render 🌟
컴포넌트를 렌더링할 때 필요하다. 필수 메서드이며, 함수형 컴포넌트에서는 render를 안쓰고도 컴포넌트를 렌더링할 수 있다.


5. getSnapshotBeforeUpdate
render에서 만들어진 결과가 브라우저에 실제로 반영되기 직전에 호출된다.


6. componentDidMount 🌟

  • 컴포넌트를 만들고 첫 렌더링을 마친 후 실행한다. 즉, 컴포넌트가 UI상에 등록이 되었을 때 (사용자에게 보여질 때)호출된다.
  • 컴포넌트가 마운트가 되고 나서 데이터를 받아오거나, 컴포넌트가 보여질 때 로딩스피너를 보여준다던지 등에 사용할 수 있다.
  • 함수형에서는 useEffect 를 활용하여 구현한다.
// Class
class Example extends React.Component {
    componentDidMount() {
        ...
    }
}

// Hooks
const Example = () => {
    useEffect(() => {
        ...
    }, []); // useEffect의 [] 의존성 배열을 비워야지만 똑같은 메소드를 구현할 수 있다.
}

7. ComponentDidUpdate 🌟
리렌더링을 완료한 후 실행한다. 업데이트가 끝난 직후이므로, DOM관련 처리를 해도 무방하다.

// Class
class Example extends React.Component {
    componentDidUpdate(prevProps, prevState) {
        ...
    }
}

// Hooks
const Example = () => {
    useEffect(() => {
        ...
    });
}

8. componentWillUnmount 🌟

  • 컴포넌트를 DOM에서 제거할 때 실행한다.(지우기 전에 호출이됨)
  • componentDidMount에서 등록한 이벤트가 있다면 여기서 제거 작업을 해야한다. 함수형에서는 useEffect CleanUp 함수를 통해 구현한다.
// Class
class Example extends React.Component {
    coomponentWillUnmount() {
        ...
    }
}

// Hooks
const Example = () => {
    useEffect(() => {
        return () => {
            ...
        }
    }, []);
}

9. componentDidCatch
컴포넌트 렌더링 도중 에러가 발생하면, 애플리케이션이 멈추지 않고 오류 UI를 보여줄 수 있게 해준다.




2. useEffect

useEffect 훅은 컴포넌트가 마운트 됐을 때(처음 나타났을 때), 언마운트 됐을 때(사라질 때), 업데이트 될 때(특정 porps가 바뀔 때) 특정 작업을 처리한다.

useState

  • class 컴포넌트의 state(상태)를 대체할 수 있다.

useEffect

  • useEffect 훅은 여러가지 기능을 할 수 있는데, 그 중 라이프사이클을 대체할 수도 있다. (❗️ 대체를 할 수 있지만 100% 일치하는 개념은 아니다. )

    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount

마운트 시에 주로 하는 작업들

  • props 로 받은 값을 컴포넌트의 로컬 상태로 설정
  • 외부 API 요청 (REST API 등)
  • axios,fetch등을 통해 ajax 요청
  • 라이브러리 사용 (D3, Video.js 등 DOM을 사용해야하는 외부 라이브러리 연동)
  • DOM의 속성을 읽거나 직접 변경하는 작업을 할 때
  • setInterval 을 통한 반복작업 혹은 setTimeout 을 통한 작업 예약

언마운트 시에 주로 하는 작업들

  • setInterval, setTimeout 을 사용하여 등록한 작업들 clear 하기(clearInterval, clearTimeout)
  • 라이브러리 인스턴스 제거



2-1. useEffect 구조

useEffect

  • useEffect 는 함수이다.
  • 첫번째 파라미터로 함수가 들어간다.
  • 두번째 파라미터로 deps(React.DependencyList) 를 추가할 수 있는데, 이것은 의존값이 들어있는 배열이다.

deps

  • [] 을 설정하지 않으면(생략하면) → 리렌더링 될 때마다 호출된다. 즉, 항상 렌더가 된 직후에는 이 함수를 실행하라는 의미이다.
    React.useEffect(() => {
        console.log('componentDidMount');
      });
  • [] 빈 배열을 설정하면 → 최초에만 실행이된다. 즉 컴포넌트가 처음 나타날 때만 useEffect 에 등록한 함수가 호출된다. (componentDidMount만 해당된다고 볼 수 있을 것 같다.)
      React.useEffect(() => {
        console.log('componentDidMount');
      }, []);
  • 특정 값을 넣으면 → 컴포넌트가 처음 마운트 될 때, 지정한 값이 업데이트될 때 모두 호출된다.
    deps 안에 특정 값이 있다면 언마운트시에도 호출이되고, 값이 바뀌기 직전에도 호출이 된다.
      React.useEffect(() => {
        console.log('componentDidMount');
      }, [count]);
  • useEffect 안에서 사용하는 상태나 props가 있다면, useEffectdeps 에 넣어주는 것이 규칙이다.
  • 만약 useEffect 안에서 사용하는 상태나 props를 deps 에 넣지 않으면 useEffect에 등록한 함수가 실행될 때 최신 props 상태를 가르키지 않게 된다.

cleanup 함수

  • useEffect 에서는 함수를 반환 할 수 있는데 이를 cleanup 함수라고 부른다. 즉, useEffect 안에서 return 할 때 실행된다.
  • deps 가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup 함수가 호출된다.
  • useEffect 코드가 실행이 되기 전에 먼저 실행이 되는 특성을 활용한다.
    • 리액트의 특성상 렌더링이 잦은데, 만약 useEffect 에 타이머 함수를 둔다면 재렌더링이 될 때마다 예상치 않게 ( ,[] 를 안썼을경우) 여러개의 타이머가 생길 수 있다. 이때 기존 타이머를 제거해달라는 코드를 cleanup 코드 안에 넣을 수 있다.
    • 마찬가지로 서버로 데이터를 요청하는 코드(n초 소요) 또한 요청이 완료되기 전에 재렌더링이 발생해서 또 요청하게 되면 기존요청과 충돌될 수 있다. 이 때 기존 데이터 요청을 제거하는 코드를 cleanup 함수에 적어둘 수 있다. 😀

useEffect의 동작

  • useEffect 는 무조건 최초에 한 번은 실행이 된다.
  • 그리고 아래 예시처럼 count가 변했을 때 즉, 컴포넌트 렌더가 업데이트 된 것에 의해서 useEffect 가 실행된다.
    이러한 개념때문에 라이프사이클과 useEffect는 100% 일치하는 것은 아니라고 한다.
  • 따라서 useEffect 는 render와 deps와 밀접한 관련이 있다.
    React.useEffect(() => {
      console.log('componentDidMount & componentDidpdate by count');
    }, [count]);


2-2. useEffect 사용해보기

✍️ 라이프사이클 메서드와 useEffect를 코드를 통해서 비교&이해하기

export default function DepsTest() {
  const [count, setCount] = useState(0);
  React.useEffect(() => {
     ...
  }); // 1) 두번째 파라미터로 의존값이 들어있는 배열인 deps를 추가할 수 있다.

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={click}>Click !!</button>
    </div>
  );
  function click() {
    setCount(count + 1);
  }
}

1-1) [] 을 생략하고 호출해본다.

  React.useEffect(() => {
    console.log('componentDidMount & componentDidUpdate', count);
  });
  • componenetDidMount일 때도 실행, componentDidUpdate일 때도 실행된다. → 렌더직후 항상 실행



1-2) [] 을 빈 배열로 호출해본다.

  React.useEffect(() => {
    console.log('componentDidMount', count); // 1)
  },[]);.
  • componentDidMount일 때만 실행된다. → 최초에만 실행
  • ❗️ deps 배열자리에 있는 값이 변해서 렌더가 될 때, 즉, 배열안의 값으로 인해서 return 함수가 실행될 때 그 직후 useEffect를 실행하기위한 역할로써 사용되기 때문이다. (비어져 있으므로 어떤것에 의해 렌더가 되더라도 이 함수는 최초 말고는 다시 실행될 수 없다.)
  • depsuseEffect 의 첫번째 파라미터 함수가 실행될 타이밍을 조절한다고 한다.
  • dpes 는 보통 비워두지 않으며, 만약 deps에 영향을 주는 1)count와 같은 것을 사용함에도 비워둔다면 Lint가 밑줄을 그어서 알려준다.🧐



1-3) [] 에 특정값을 준다.

  React.useEffect(() => {
    console.log('componentDidMount, componentDidUpdate by count', count);
  }, [count]);
  • 최초에 한번은 실행되므로 componentDidMount이면서,
  • deps에 count가 있으므로 count가 변했을 때만 컴포넌트 렌더가 업데이트된 것에 의해 useEffect 가 실행되므로 componentDidMount by count 의 의미를 갖는다.
  • 즉, useEffect 는 render + deps와 밀접한 관계가 있다. 따라서 lifecycle과 완전히 같다고 볼 수 없다고 한다.



2) deps는 여러개 사용이 가능하다.

  React.useEffect(() => {
    console.log('먼저 실행');
  }, []);

  React.useEffect(() => {
    console.log('다음 실행');
  }, []);
  • useEffect는 여러개를 사용할 수 있고 순차적으로 실행된다.
  • 따라서 deps 상황에 맞을 때만 해당 useEffect 첫번째 파라미터 함수가 실행된다.



3) cleanup

ex. 1)

export default function DepsTest() {
  
  const [count, setCount] = useState(0);
  React.useEffect(() => {
    console.log('componentDidMount');   // 1)

    return () => { // 2)
      // cleanup
      // componentWillUnmount
      console.log('cleanup');
    };
  }, []); // 빈 배열일 경우 최초만 실행
  
  retrun( // 렌더
  ...
  )
}
  • useEffect 는 return을 할 수 있는데 이 공간을 cleanup 이라고 부른다.
  • 1) 렌더가 된 직후, 다음 렌더시 deps 의해서 useEffect가 실행될 때 직전에 2)가 먼저 실행, 그 다음 1)을 실행한다.
  • cleanup 함수는 mount시 실행되지 않는다. unmount시 실행된다.
  • deps 를 빈 배열로 둔다면 최초에만 실행이 된다. 이때 cleanup이 일어나려면 결국 DepsTest 자체가 사라질 때 말고는 없다.
    → 따라서 이 때 cleanup 위치는 정확하게 componentWillUnmount의 역할을 하게된다.

ex. 2)

 React.useEffect(() => {
   // 1) 렌더가 된 직후 
   console.log('componentDidMount, componentDidUpdate by count'); 

   return () => {  // 2)
        console.log('cleanup'); // cleanup
      };
  },[count]);
  • 1) 렌더가 된 직후, 다음 렌더시 deps 의해서 useEffect가 실행될 때 직전에 2)가 먼저 실행, 그 다음 1)을 실행한다.
  • 그러므로 ex.2) 의 2) 경우 componentWillUnmount와 일치하는 것이 아니라 다음 componentDidUpdate가 되기 직전에 정리하고 떠나는 역할을 한다.

  • 먼저 최초의 렌더 직후 useEffect 가 실행된다. 카운트 click 버튼을 누르면 새로운 카운트 값이 아니고 이전 카운트 값으로 ▼
    console.log('cleanup by count', count)
    가 실행되고 나서 렌더 후 ▼
    console.log('componentDidMount, componentDidUpdate by count', count)
    가 실행된다.
    → 정리하자면 다음번 useEffect 함수가 실행될 때 그 전에 미리 이전 deps 값으로 리턴 함수를 실행하고 그 다음으로 넘어간다. 😀


📌 Check!
1. useEffect(()=>{ ... }) : 재렌더링마다 코드실행
2. useEffect(()=>{ ... }, []) : mount시 1회 코드실행
3. useEffect(()=>{ return(()=>{...}) }, []) : unmount시 1회 코드실행
4. return()=>{...} : useEffect 실행 전에 먼저 실행할 코드가 필요하다면 언제나 return문
5. useEffect(()=>{ ... }, [state명]) : mount시 + 특정 state 변경시에만 실행


❓Lifecycle을 왜 알아야할까?
: 내가 lifecycle을 알고 있으면 컴포넌트의 Lifecycle(mount, update, unmount)에 간섭을 할 수가 있게된다. 😀

✍️ 최근에는 Hooks를 활용해서 함수형 컴포넌트를 사용하는 방향으로 나아가고 있다고 한다. 기존 라이프사이클의 개념에 대해 이해하고, Hooks를 활용해서 라이프 사이클을 구현하는 것에 대해서 익숙해져야겠다.

어려울 땐 ▼ 사이트 참고하기 !

📌 useEffect 완벽 가이드

(Dan Abramov가 작성한 ‘A Complete Guide to useEffect’의 번역문)




reference)
react-lifecycle_method
vlpt-useEffect
vlpt-lifecycle_method
Lifecycle이해
fastcampus-react
dreamcoding
codingapple

profile
기억보단 기록을 ✨

0개의 댓글