#back_to_basic
다시 처음부터!라는 마음으로 공부해보자! 아자아자!
공부하다가 계속 나오는 '라이프 사이클', '마운트', '렌더링'....다시 개념을 잡아보자!
1. 리액트의 라이프사이클
React 컴포넌트는 생명주기(라이프사이클 또는 생애주기)이 있다.
생성(mounting) => 업데이트(updating) => 제거(unmounting)의 주기를 갖는다.
리액트 Class Component는 라이프 사이클 메서드를 사용하고,
함수형 컴포넌트는 Hook을 사용한다.
1) Class Component 생명주기
Functional Component에 대해 자세히 알아보기 위해, 의미가 좀 더 잘 전달되는 메소드를 통해 이해하기 위해 Class Component에서 사용되는 생명주기 라이프 사이클 먼저 메서드를 알아보자.
각 시점에는 여러 메서드들이 호출되지만, 그 중 주요한 것만 적어두겠다.
(자세한 것은 여기를 참고)
- Mounting(마운트)
- componentDidMount:
컴포넌트가 마운트 됨, 컴포넌트의 첫번째 렌더링이 마치면 호출되는 메서드
이 메서드가 호출되는 시점에는 화면에 컴포넌트가 나타난 상태이다.
함수형 컴포넌트에서 보면 아래와 같은 경우이다.
- componentDidMount:
- Updating(업데이트)
- shouldComponentUpdate:
컴포넌트가 리렌더링 할 지 말지를 경정하는 메서드
React.memo와 유사하다. - componentDidUpdate:
컴포넌트가 업데이트 되고 난 후에 발생
함수형 컴포넌트에서는 의존성 배열이 변할 때만 useEffect가 실행하는 것과 같다.
- shouldComponentUpdate:
- Unmount(언마운트)
언마운트라는 것은 컴포넌트가 화면에서 사라지는 것을 의미한다.
언마운트에 관련된 생명주기 메서드는 componentWillUnmount 하나이다.
- componentWillUnmount:
컴포넌트가 화면에서 사라지기 직전에 호출된다.
여기서 주로 DOM에 등록했었던 이벤트를 제거하고, 소켓 호출을 끝내거나, clearTimeout을 통해 clean-up을 한다.
- componentWillUnmount:
2) Functional Component 생명주기
리액트에서 Hook은 함수형 컴포넌트에서 React state와 생명주기 기능을 사용할 수 있게 해주는 메서드이다.
❓ 리액트 훅이 왜 도입되었을까:
- 기존의 라이프사이클 메서드 기반이 아닌 로직 기반으로 나눌 수 있어 컴포넌트를 함수 단위로 잘게 쪼갤 수 있다는 이점이 있음
- 라이프사이클 메서드에는 관련 없는 로직이 자주 섞여 들어가는데, 이로 인해 버그가 쉽게 발생하고 무결성을 쉽게 해치기때문
☢️ 주의사항:
- 최상위 컴포넌트에서만 Hook을 호출할 것
- 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하면 안됨
**참고: render vs mount
맨 처음 컴포넌트가 render 될 때는 mount 과정을 거친다.
그러나 props나 state가 변경되어 '업데이트'가 될 때는 mount를 거치지 않는다.
즉, mount는 DOM이 생성되고, 웹 브라우저상에 처음으로 나타나는 과정을 의미한다.
2. Class Component 라이프 사이클 메서드를 통해 Functional Component useEffect를 이해해보자
(나는 이 부분을 공부하면서 완전히 라이프 사이클을 통해 컴포넌트가 업데이트되는 것을 이해한 것 같다.
나같은 주니어들은 길이 글어도 꼭 참고 읽어보자)
컴포넌트에 중심이 맞춰진 class 컴포넌트 라이프사이클과 다르게,
함수 컴포넌트에서는 특정 데이터에 대해서 라이프 사이클이 진행된다.
(아래 모든 예시들은 이 글 과 이 글을 참고하여 작성한 것입니다)
1) componentDidMount + componentDidUpdate의 역할
useEffect(() => {
console.log('hidden changed');
}, [hidden]);
위 경우는 hidden이 바뀌는 것에 따라 라이프사이클을 정할 수 있다.
위처럼 작성을 하면,
컴포넌트가 첫 렌더링될 때 한번 실행(componentDidMount) => 그 다음 부터는 hidden이 바뀔 때마다 실행(componentDidUpdate)된다.
2) componentWillUnmount의 역할
useEffect(() => {
console.log('hidden changed');
return () => {
console.log('hidden이 바뀔 예정입니다.');
};
}, [hidden]);
위 경우 return으로 함수를 제공하면 componentWillUnmount 역할도 담당할 수 있다.
useEffect 안에서의 returndms 정리함수(clean-up)를 사용하기 위해 쓰여지는데
- 메모리 누수 방지를 위해 UI에서 컴포넌트를 제거하기 전에 수행
- 컴포넌트가 여러번 렌더링 된다면 다음 effect가 수행되기 전에 이전 effect가 정리됨
** componentWillMount와 componentWillUpdate는 없어졌으므로, useEffect에 해당하는 경우는 없다.
즉, 정리하자면 useEffect 하나로 => comonentDidMount + componentDidUpdate + componentWillUnmount가 합쳐진 코드를 작성할 수 있다.
3. useEffect는 언제 실행되는가
useEffect는 위 라이프사이클 메서드와 매듭지은 것처럼 '마운트 된 이후에', '업데이트 된 이후에', '언마운트하기 전에' 호출된다.
모든 경우에 호출되는 것이 아니라, 아래처럼 경우에 따라 다르게 호출되는데,
위 경우를 보면 알 수 있듯이 useEffect에서는 두번째 인자로 '의존성 배열'이 들어간다.
의존성 배열이 비어있는 채로 들어가는 경우, 아예 안들어가는 경우, 여러 경우에 따라 다르게 호출된다.
useEffect(() => {}); // 렌더링 결과가 실제 돔에 반영된 후마다 호출
useEffect(() => {}, []); // 컴포넌트가 처음 나타날때 한 번 호출
useEffect(() => {}, [의존성1, 의존성2, ..]); // 조건부 effect 발생, 의존성 중 하나가 변경된다면 effect는 항상 재생성됩니다.
// 출처: https://velog.io/@minbr0ther/React.js-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4life-cycle-%EC%88%9C%EC%84%9C-%EC%97%AD%ED%95%A0
즉,
첫번째 경우) 작성하게 된다면, 렌더링 될 때마다! 실행하게 된다.
두번째 경우) 처음 나타날 때 한번만(== 업데이트될 때는 실행 안됨) 실행된다.
세번째 경우) 처음 나타날 때 + 의존성 배열에 해당하는 데이터가 변할 때마다 실행된다.
시각적으로 아!하고 알기 위해서는 예시를 보는게 좋다.
여기 좋은 예시가 있다.
간단하게 위 블로그의 코드를 참고하여, 나도 코드를 작성해보았다.
❓ 왜 did mount가 마지막에 한번 더 출력되는가 ❓
내 예상은 의존성 배열에 빈배열( [ ] )을 줬기 때문에, did mount가 한번 출력되고 will unmount가 출력되는 줄 알았다.
이는 React의 Strict Mode 때문인데, StrictMode는 React에서 제공되는 검사 도구이다.
안전하지 않은 생명 주기를 가진 컴포넌트, 권장되지 않은 부분, 배포 후 문제가 될 수 있는 부분들까지 미리 확인하기 위함으로 제일 루트되는 Index.js에서 <React.StrictMode>에 감싸져있는 것을 볼 수 있다.
참고: [개발일지 #3] UseEffect는 왜 두번 실행되는걸까?
*Strinct Mode를 사용하지 않는 것은 권장되지 않지만, 호출 시점을 알아보기 위해 일단은 index.js에서 <StrictMode>를 지워주겠다*
아래는 <StringMode>를 지우고, 같은 코드를 실행한 결과이다.
아하, 현재는 did mount가 한번만 일어나는 것을 볼 수 있다.
그렇다면? 위에서는 [ ] 을 줬고, 아무런 업데이트가 없었는데도 unmount가 출력된 이유는 'strict mode 때문에 useEffect를 다시 호출했기 때문'이라는 것을 알 수 있다.
자, 그러면 컴포넌트가 리렌더링 될 수 있게 해놓고, 의존성 배열로 빈 배열을 줘보자.
input에 변화를 주어 state를 업데이트해주어도, useEffect는 실행되지 않았다.
[] 빈배열을 주어 '첫 마운트일 때만 실행'이라는 조건을 주었기 때문이다.
그렇다면, 빈배열도 주지 않고 useEffect(() => {} );로 작성을 한다면,
input에 변화가 있을 때 state에 계속 업데이트가 일어나기 때문에 => 컴포넌트가 리렌더링이 될 것이다.
그렇다면, 아무것도 주지 않았기 때문에 input에 변화가 일어날 때마다 호출돼야한다.
예상했던 것처럼,
세번의 입력에 따라 총 세번의 리렌더링이 일어나면서
will unmount가 찍히고, 그 다음 컴포넌트가 렌더링되며 did mount가 찍힌다.
즉, 정리하자면
컴포넌트가 리렌더링되면 그 앞의 useEffect를 정리해주기 위한 unmount => 그 다음 렌더링되며 mount가 찍히며 업데이트 되는 것을 알 수 있다.
물론 이는 의존성 배열에 [inputValue]를 넣어도 똑같은 결과를 가져온다.
이까지 따라왔고, 잘 이해가 된다면 이제 감은 잡았다고 볼 수 있겠다!
(재밌다)
4. useEffect를 바르게 사용하기 위해서는
자세히 추후 정리할 기회가 있다면 정리하면 좋을 것 같다.
사실 useEffect를 '바르게' 사용하는 것에 대해서는 고려해본 적이 없었는데,
아래 블로그의 글을 읽고 난 뒤 생각이 바뀌었다.
https://velog.io/@jay/you-might-need-useEffect-diet
프론트엔드 개발자라면 '화면이 쓸데없이 리렌더링되는 것'을 항상 고려하고 지양해야한다.
위 블로그를 읽어보면 useEffect를 바르게 사용하는 법을 알 수 있다! 그리고 더불어 고혈압도 치료가능하다(!ㅎㅎ)
References
- https://www.zerocho.com/category/React/post/579b5ec26958781500ed9955
- https://crmrelease.tistory.com/29
- https://www.zerocho.com/category/React/post/5f9a6ef507be1d0004347305
화이팅이다!
[다음 공부할 것]
'Web' 카테고리의 다른 글
[FE 기술 면접 준비] (0) | 2023.12.19 |
---|---|
[BB] useRef가 사용되는 경우 (0) | 2023.12.08 |
[BB] Javascript: 불변성(immutability) (0) | 2023.10.26 |
[BB] 브라우저의 렌더링과 Virtual DOM (1) | 2023.10.25 |
[BB] Javascript: 비동기 프로그래밍 (0) | 2023.10.25 |