React LifeCycle에 관하여

이승훈·2022년 11월 9일
1

TIL

목록 보기
15/32
post-thumbnail

리액트 라이브러리에 대해 조각조각 흩어져잇던 개념들을 모으고싶었다.
부모컴포넌트가 리렌더링 될 때 자식컴포넌트도 함께 리렌더링이 된다는 지식,
state가 갱신되면 해당 컴포넌트가 리렌더링 된다는 지식,
useEffect를 사용하면 컴포넌트가 마운트, 언마운트, 의존성 배열안의 state가 변경되었을 때 특정작업을 실행시킬 수 있다는 지식들 말이다.
또한 이와같은 개념들이 리액트컴포넌트의 라이프사이클이라는 개념과 연관되어있다는 사실을 깨달았다.
그에 대해 정리해 보고자 한다.

✅ 라이프 사이클이 무엇인가?

라이프 사이클은 단어 그대로 생명주기를 뜻한다.
바로 리액트 컴포넌트의 생명주기 말이다.
그리고 그 생명주기는 아래와 같이 3가지 경우가 있다.

1.생성(mount)
2.업데이트(update)
3.제거(unmount)

✅ 라이프사이클이 왜 중요한가?

공부를 하기전에 이 공부를 왜하는지을 먼저 파악하는것이 중요하다고 생각한다.
라이프사이클에 대해 왜 공부를 해두어야 할까?

각각의 3가지 경우에 따라 어떤 과정이 발생하는지 파악하고 각 과정에서 어떠한 일을 처리해줘야 하는지 명확하게 지정해줘야 불필요한 렌더링 및 업데이트를 최소화하여 나의 웹어플리케이션의 퍼포먼스를 향상시킬 수 있기 때문이다.

각각의 라이프사이클에 따라 처리할 일을 지정해주는 방법은 클래스형 컴포넌트와 함수형 컴포넌트에 따라 다르다.

클래스형 컴포넌트는 라이프사이클 메서드를 사용하고,
함수형 컴포넌트는 리액트훅을 사용한다.

✅ 클래스형 컴포넌트에서의 라이프사이클 메소드

위에서 언급한대로 클래스형컴포넌트에서는 각각의 경우(마운트, 업데이트, 언마운트)에 따른 라이프사이클 메소드가 존재한다.
그리고 올바른 라이프사이클 메소드 사용을 통해서 원하는 작업을 처리할 수 있다.

1. 마운트

1. constructor()

javascript 클래스 생성자를 사용할 때 가장먼저 constructor() 메소드가 호출되었듯,
클래스형 컴포넌트에서도 컴포넌트를 새로 생성할 때 마다 constructor() 메소드가 호출된다.
this.props, this.state 에 접근하여 props를 받아오고 state의 초기값설정을 해줄 수 있다.

class TestComponent extends Component {
 constructor(props) {
   super(props);
   
   this.state = {
     testState1: props.testProps,
     testState2: 'test',
 }
}

2. getDerivedStateFromProps()

props로 받아온것을 state에 넣어줄 때 사용

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log("getDerivedStateFromProps");
    if (nextProps.color !== prevState.color) {
      return { color: nextProps.color };
    }
    return null;
  }

다른 라이프사이클 메소드와 달리 static 명령어를 필요로한다
이 메소드에서 return해주는 값은 해당 컴포넌트의 state로 설정된다.

static 은 클래스내에서 프로토타입에 메소드를 저장하는것이 아닌 클래스 함수 자체에 메소드를 설정할 때 사용하는 명령어 이다.
즉, 메서드를 프로퍼티 형태로 직접 할당하는 것과 동일한 일을 합니다.

3. render()

UI를 렌더링하는 메소드이다.

class TestComponent extends Component {
  render(){
    const {props1, props2} = this.props;
    return(
      <div>UI에 직접 렌더링하는 라이프사이클 메소드이다.</div>
    )
  }
}

4. componentDidMount()

컴포넌트의 첫번째 렌더링 후 호출되는 메소드 이다.
이 메소드가 호출된 시점에서는 이미 내가 만든 클래스형 컴포넌트가 화면에 나타나있는 상태이다.
주로 fetch나 axois를 통하여 ajax요청을 한 후 DOM 속성승ㄹ 직접 변경해주는 작업을 이 메소드에서 진행해준다.

2. 업데이트

업데이트가 되는 경우는 크게 3가지 경우이다.

  1. props가 변경되었을 때(props는 state이며 setState를 통해 올바르게 변경되어야 한다.)
  2. 해당 컴포넌트의 state가 변경 되었을 때
  3. 부모컴포넌트가 리렌더링 되었을 때

위와같이 3가지 경우에 컴포넌트의 업데이트가 진행되며 그 과정은 아래의 메소드를 통해 진행된다.

1. getDericedStateFromProps()

마운트 때와 마찬가지로 받아온 props를 state에 담아주는 기능을 하며,
props의 변화에 따라 state값을 변경해줄 때 주로 사용된다.
호출되는 시점은 업데이트가 시작하기전에 호출 된다.

2. shouldComponentUpdate()

해당 컴포넌트는 아무것도 변하지 않아 새로 렌더링 할 필요가 전혀 없는데,
부모컴포넌트가 변경되었다고 같이 리렌더링 되면 이는 리소스의 낭비다.
이러한 경우에 사용하는것이 바로 SCU 메소드이다.
return값이 true라면 이후의 라이프사이클 메소드를 계속 실행하며 컴포넌트를 리렌더링하고,
return값이 false라면 업데이트 라이프사이클을 중지 후 컴포넌트를 리렌더링하지 않는다.

3. render()

마운트때와 마찬가지로 컴포넌트를 리렌더링하는 라이프사이클 메소드이다.

4. getSnapshotBeforeUpdate()

Update가 발생하기 이전의 DOM의 state값과 props값을 가져오는 함수이다.
어쩌면 스냅샷이라는 이름을 사용한것이 Update가 발생하기전 DOM을 찰칵하고 찍어 그안의 state와 props값을 가져온다는 의미가 아닐까 싶다.

이 메소드의 return값은 이후 componentDidUpdate() 메소드의 3번째 인자로 전달된다.

5. componentDidUpdate()

컴포넌트의 업데이트 작업이 끝난 후 호출하는 메소드이다.
마운트 라이프사이클에서 componentDidMount()와 유사한 역할을 한다.

3. 언마운트

컴포넌트를 DOM에서 제거하는 과정이다.
즉, 화면에서 지워주는 과정이다,

1. componentWillUnmount

컴포넌트를 DOM에서 제거할 때 실행하는 메소드.
이후에틑 컴포넌트가 다시 렌더링 되지 않고 사라지므로 여기서 setState()를 호출하면 안된다.

✅ 함수형 컴포넌트에서의 리액트 훅

일단 훅이 있기전에는 함수형 컴포넌트는 사용되지 않았고, 클래스형 컴포넌트가 사용되었다.
클래스는 그 자체가 새로운 인스턴스를 반환하고 그 인스턴스는 this를 저장하고 있기 때문에 그 인스턴스 자체가 react에서 알고 있는 값이 되므로 react에서 제거하지 않는 이상, 페이지가 새로 렌더링 되더라도 해당 인스턴스를 기억하고 있기때문에, 상태관리를 자유롭게 할 수 있개 때문이다.

하지만 함수형 컴포넌트는 javascript에서 함수의 특성상 return이 되고 나면 메모리상에서 제거되기 때문에 함수형 컴포넌트는 자신의 동작이 끝나면 DOM요소를 return 후 제거되고 이는 다시 접근할 때 이전의 상태값을 기억하지 못한다는 의미이다.
즉, 상태관리가 불가능했다는 뜻이다.

리액트 훅의 존재이유
(훅을 만들어서 함수형 컴포넌트를 사용하는 이유)

1. 라이프사이클 메소드의 대체

리액트 훅은 함수형 컴포넌트에서 클래스형 컴포넌트에서 사용하던 라이프사이클 메소드의 기능을 대체할 수 있게 해준다. useState 훅을 통해서 state를 기억할 수 있게 되었고, useEffect 훅을 통해서 각각의 라이프사이클에 따라 처리해야할 일을 지정해 줄 수 있게 되었다.

2. 상태관리 로직의 재활용성

클래스형 컴포넌트에선 상태관련 로직을 재사용하기가 까다롭다.
클래스 내부에서 만들어준 함수는 클래스 내부에 종속된 메소드가 되며, 이 메소드를 다른 클래스에서 가져다 사용하기도 까다롭거니와 이를 위해 사용하는 고차컴포넌트 같은 기술은 코드의 추적을 어렵게 만들기 때문이다.
-> 고차컴포넌트(HOC)는 많은 wrapper 컴포넌트 구조를 가지기 때문에 코드구조 파악이 어렵다.

하지만 함수형 컴포넌트에서 훅을 사용함으로서 라이프사이클 메소드를 대체할 수 있다면 함수형 컴포넌트의 특징인 상태관리로직의 재사용성의 뛰어남을 누릴 수 있다.
재사용성이 뛰어나다는것은 원하는 기능을 함수로 만든 후 필요한 곳에 props로 집어 넣어주기만 하면 되기 때문에 로직의 재활용성이 뛰어나다는 뜻이다.

즉, 상태관련 로직을 추상화하여 독립적인 테스트와 재사용이 가능해진다는 뜻이다.

3. 컴포넌트를 로직단위로 쪼갤 수 있다.

이는 어쩌면 2번의 이유에 종속되기도 한다.
클래스형 컴포넌트의 경우 라이프사이클 메소드 하나 안에서 연관성이 없는 컴포넌트와 로직들을 묶어서 작성해야하는 경우가 많았다.
하지만 함수형 컴포넌트의 경우 useEffect를 사용하면 로직기반으로 함수를 세세히 나눌 수 있기 때문에 코드의 가독성 및 유지보수성이 훨씬 증가하게 된다.

훅의 사용 규칙

1. 컴포넌트의 최상위에서만 훅을 호출해야한다.,

반복문, 조건문, 중첩된 고차함수내에서 훅을 호출하면 안된다.

2. 리액트 함수 컴포넌트에서만 훅을 호출해야한다.

일반 Javascript 함수내에서 훅을 호출해선 안된다.

대표적인 훅들

1. useState

변경되는 값을 관리할 때 사용한다.
즉, 이 useState훅을 통해서 기존에 함수형 컴포넌트의 최대 약점 중 하나인 컴포넌트에 다시 접근했을 때 이전의 상태값을 기억하지 못하는점을 극복한것이다.

통상적인 사용 방법은 아래와 같으며 상태 유지 값, 상태를 갱신하는 함수를 반환한다.

const [state, setState] = useState(초기값으로 설정하고자 하는 값);

컴포넌트의 리렌더링 시에 useState를 통해 반환받은 첫번째 값은 개선된 최신 state가 된다.
useState를 통해 주어진 초기값은 맨 첫 렌더링 시에만 사용된다.

2. useEffect

컴포넌트가 렌더링 될 때마다 처리해야할 일을 수행해주도록 도와주는 훅.
컴포넌트의 라이프사이클에 맞게 처리해줄 작업을 지정해줄 수 있다.
바로 이 useEffect 훅이 클래스형 컴포넌트의 라이프사이클 메소드를 대체해준다.

사용방법은 아래와 같으며 인자로 callBack Function과 dependencis를 받는다.

useEffect(()=> {
  처리해주어야할 일
  예를 들면 
  console.log('컴포넌트가 렌더링 되었습니다.');
  return (()=>{
    console.log('컴포넌트가 언마운트 되었습니다.')
  })
},[state1, state2...])

useEffect의 콜백함수는 네가지 경우에 대해 호출이 가능하다.

1. useEffect(()=>{})

useEffect의 두번째 인자를 지정해주지 않으면 컴포넌트가 렌더링될 때 마다 콜백함수를 호출한다.

2. useEffect(()=>,[])

useEffect의 두번째 인자로 빈 배열을 입력해주면 컴포넌트가 최초 렌더링 될 때만 콜백함수가 실행된다.

3. useEffect(()=>{},[state1, state2])

의존성 배열안 state를 입력해준 경우 넣어둔 state가 setState에 의해 변경된 경우 콜백함수가 실행된다.

4. useEffect(()=>{return()=>{}})

useEffect의 첫번쨰 인자인 callBack함수의 return 값이 있는 경우 컴포넌트가 unMount 될 때 return값 안의 콜백함수가 실행된다.

useEffect가 호출되는 시점

useEffect의 첫번쨰 인자인 콜백함수는
컴포넌트 렌더링 -> 화면 업데이트 -> useEffect리행 순으로 실행된다.

✅ 결론

만일 누군가가 나에게 라이프사이클에 대해 말해보라 한다면 뭐라 말해야할까라는 고민으로 이 포스팅을 시작하였다.

그리고 많은 고민을하며 포스팅을 작성 후 내린 나의 대답은 아래와 같다.

라이프사이클은 컴포넌트의 생명주기를 의미한다.
즉, 컴포넌트의 생성(마운트), 업데이트, 제거(언마운트) 3가지 경우에따라 컴포넌트가 겪는 과정을 뜻한다.

생성은 컴포넌트가 처음으로 브라우저에 렌더링 될 때 발생,
업데이트는 부모컴포넌트가 렌더링 될 경우, props가 바뀌는 경우, state가 바뀌는경우에 발생, 제거는 컴포넌트가 브라우저에서 사라질 때 발생한다.

과거 리액트 훅이 등장하기전 클래스형 컴포넌트에서는 라이프사이클 메소드를 사용하여 컴포넌트의 라이프사이클에 맞추어 처리해야할 일을 지정해주었다.
이러한 이유로 라이프사이클 메소드 의존적으로 컴포넌트를 작성해주어야 하기에 컴포넌트를 기능단위로 분리하기가 어려웠다.

하지만 리액트 훅이 등장 후 함수형 컴포넌트에서 라이프사이클 메소드를 대체할 수 있게 되었고, 또한 useEffect 훅의 기능 과 함수의 특성으로 인해 컴포넌트를 기능단위로 분리하기에 용이해졌다.

profile
Beyond the wall

2개의 댓글

comment-user-thumbnail
2022년 11월 9일

승훈님의 포스트는 항상 감동적입니다.🤠

답글 달기
comment-user-thumbnail
2022년 11월 9일

와...라이프사이클... ㄷ ㄷ

답글 달기