프론트엔드/React

React Lifecycle - class & hook 1부

.log('FE') 2021. 12. 16. 13:00
728x90
반응형

리액트의 생명주기에 대해서 정리하려고합니다. 자주 사용하는것만 당연하게 사용하고있다보니 자세히 설명하려고 하면 항상 공식문서나 다른 자료들을 참고하게되어서 이참에 그냥 블로그에 정리를 한번 해두고 필요할때마다 해당 글만 볼 수 있도록 하려는 목적입니다.

 

리액트의 컴포넌트는 두가지 방식으로 작성할 수 있습니다. class 컴포넌트와 function 컴포넌트 입니다. function 컴포넌트는 hook 이 공식적으로 리액트에서 사용됨에 따라서 class => function 컴포넌트로 많이 넘어오고 있습니다.

 

class 와 function 을 함께 사용하고 있거나 마이그레이션을 하려면 두 방식에 대한 생명주기를 모두 이해하고 있어야 합니다. 그래서 두가지 방식을 비교하면서 작성합니다.

 

React lifecycle diagram

 

리액트의 라이프 사이클은 크게 3단계로 볼 수 있습니다.

  • 생성 - 1부
  • 업데이트 - 2부
  • 제거 - 3부

생성 (Mount)

컴포넌트 인스턴스가 생성되면 아래와 같은 순서대로 호출됩니다.

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

constructor(props)

class User extends React.Component {
  constructor(props) {
     super(props)
     this.state = {}
     this.handleClick = this.handleClick.bind(this)
  }
}

 

constructor 에는 state 의 초기값을 설정하고 메서드에 바인딩을 설정해 줄 수 있습니다. 만약 해당 작업이 필요없다면 필수로 사용하지 않아도 됩니다. 리액트 컴포넌트의 생성자(constructor) 는 마운트 되기전에 가장 먼저 호출됩니다. React.Component 를 상속한 컴포넌트의 생성자를 구현할때는 super(props) 를 먼저 호출해야합니다. 그렇지않으면 this.props가 생성자내에서 정의되지 않습니다.

 

주의할점

생성자 내부에서 setState 를 호출하면 안됩니다. 

// BAD
constucrot(props) {
   super(props)
   this.state = { count: 0 }
   this.setState({ count: 1 })
}

생성자 내부는 유일하게 state 에 값을 직접 할당할 수 있는 공간입니다.

setStatestate를 업데이트 하기위한 메소드이고 비동기로 동작합니다. 때문에 생성자 내부에서 setState 를 호출하더라도 원하는 결과를 얻을 수 없습니다.

 

state 에 props 를 복사하면 안됩니다. 

props 의 변경사항을 반영하지 않습니다.

// BAD
constucrot(props) {
   super(props)
   this.state = { count: props.count }
}

 

가장 많이 자주하는 실수중 하나라고 볼 수 있을것같습니다. 저도 실무에서 저런식으로 사용한적이 몇번 있었는데 해당 사용법은 리액트에 대해서 제대로 이해하지 못한상태로 사용하고 있다고 볼 수 있습니다.

 

리액트에서는 stateprops 의 변경에 따라 리렌더링이 수행되고 변경사항이 반영됩니다. 즉 의도적으로 props.count 의 변경사항에 대해서 반영하지 않겠다 라는 이유가 아니라면 해당 코드는 componentDidMount 에서 수행되어야 합니다.

 

static getDerivedStateFromProps() - 덜사용함

해당 메소드는 자주 사용되거나 중요하지는 않습니다. 다만 잘 모르고 사용할 경우 복잡도를 높이거나 안티패턴을 만들어 낼 수 있기때문에 정말 특별한 경우가 아니라면 공식문서에서 제안하는 대체방법들을 사용하기를 권장합니다.

static getDerivedStateFromProps(props, state)

 

최초 마운트시와 갱신시에 모두 호출되며 render 메소드를 호출하기 직전에 수행됩니다. state를 갱신하기위한 객체를 반환하거나 null 을 리턴하여 아무것도 갱신하지 않을 수 있습니다.

 

또한 컴포넌트의 인스턴스에 접근 할 수 없고 오로지 propsstate 에 대한 처리만 가능합니다. 해당 메소드를 잘못 사용하게되면 의도하지않은 잦은 리렌더링을 발생 시킬 수 있고 의도된 변경사항에 대해서 제대로 반영이 안될 수도 있습니다. 공식문서에서는 아래와 같은 세가지 상황을 예로들면서 대안방법을 제시합니다.

 

  1. props 의 변경에 따라 부수효과를 발생시켜야 한다면 componentDidUpdate 를 사용할 수 있습니다.
  2. props 의 변경에 일부 데이터만 다시 변경하고 싶다면 PureComponent 를 사용할 수 있습니다.
  3. props 의 변경에 일부 state 에 대해서 재설정하고싶다면 완전제어컴포넌트 또는 key를 사용한 완전 비제어 컴포넌트 로 만들어 사용 할 수 있습니다.
// 완전 제어 컴포넌트
// props 에의해서 완전히 제어됨
function EmailInput(props) {
     return <input onChange={props.onChange} value={props.email} />; 
}

// 완전 비제어 컴포넌트
// 최초 props 값 이후에는 내부 상태 변경에 따라 제어
class EmailInput extends Component {
   state = { email: this.props.defaultEmail };

   handleChange = event => {
      this.setState({ email: event.target.value });
   };

   render() {
      return <input onChange={this.handleChange} value={this.state.email} />;
   } 
}

 

render()

render 는 클래스 컴포넌트 내에서 필수적으로 구현되어야 하는 유일한 메소드 입니다. 

  • render 는 순수함수여야 합니다.
  • 컴포넌트의 state를 변경하지 않아야 합니다. setState 를 render 내에서 호출하면 안됩니다. (무한 루프 생성)
  • 호출 될때마다 동일한 결과를 반환해야 하며 브라우저와 직접적으로 상호작용 하지 않습니다.
  • shouldComponentUpdate()false 를 반환하면 render() 는 호출되지 않습니다.

 

componentDidMount()

컴포넌트가 마운트된 직후 실행됩니다. 때문에 DOM 노드가 있어야 하는 초기작업을 수행해야 한다면 해당 메소드내에서 사용할 수 있습니다. api 네트워크 호출같은 작업을 수행하기에 적절한 위치입니다. 마운트시에만 호출되면 업데이트시에는 호출되지않습니다.

해당 메소드 내에서 this.setState() 를 수행할 수 있는데 해당 방법은 render 를 두번 호출하게 하고 성능상 이슈를 발생 시킬 수 있어 주의해서 사용해야 합니다.

 


Hook 을 사용한 Function 컴포넌트 생성

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

export default function User() {
   const [count, setCount] = useState(null);
   useEffect(() => {
         
   }, [])

   return (
       <div>test</div>
   )
}

 

Hook 은 리액트 16.8 버전부터 새 요소로 추가되었습니다. RN 같은경우 v0.59 부터 hook 을 지원합니다.

Hook 을 도입하게 된 계기는 아래와 같습니다.

  • 컴포넌트 사이에 상태 로직을 재사용하기 어려움
  • 복잡한 컴포넌트들을 이해하기 어렵다 - 상태관리를 위해 사용되는 복잡하고 긴 라이프사이클
  • class 의 사용은 사람과 기계를 혼동시킨다. - this 키워드 사용에 대한 혼란

Hook 은 함수 내에서 사용가능하고 결국 함수 이지만 올바르게 사용하기위해서는 2가지 규칙에 대해서 이해하고 있어야합니다.

  1. 최상위에서만 Hook 을 호출 해야 합니다.
  2. React 함수 내에서만 Hook 을 호출해야합니다.

최상위에서만 호출해야 동일한 순서로 Hook 이 호출되는것을 보장 할 수 있습니다. useStateuseEffect 는 여러번 호출 될 수 있는데 해당 규칙을 따라야 순서를 보장 할 수 있습니다. 최상위에서 호출하라는 말이 감이 안올 수 있는데 훅을 조건문이나 중첩된 함수 내에서 사용하면 안됩니다.

//BAD
export default function Test() {
   if (true) {
      const [count, setCount] = useState(0)
   }
}

//BAD
export default function Test() {
   const test = () => {
      const [count, setCount] = useState(0)
   }
}

 

어떤 개념이던 등장 배경과 공식 문서에 작성된 기본적인 규칙과 철학에 대해서 이해하고 사용하는걸 개인적으로 선호하기때문에 해당 내용에 대해서 먼저 정리했습니다.

 

useState()

useState 는 class 컴포넌트에서 생성자 내부에 this.state 로 초기값을 선언하는것과 유사한 역할을 합니다. 다른점은 useState 는 상태가 추가될때마다 새로운 useState 를 선언하여 사용할 수 있습니다. (하나의 useState 에 여러 상태를 사용할 수도 있습니다.)

// class
constructor(props) {
   super(props)
   this.state = { count: 0, name: 'Lee' } 
}
console.log(this.state.count) // use
this.setState({ count: 1 }) // update

// hook
function App {
   const [count, setCount] = useState(0)
   const [name, setName] = useState('Lee')
   console.log(count) // use
   setCount(1) // update
}

 

useState 에서 초기값을 할당하는 방식은 배열을 활용하고 있는데 해당 방식을 배열 구조 분해 라고 합니다. 배열구조분해를 사용하지않고 useState 를 사용하면 아래와 같습니다.

const countState = useState(0)
const count = countState[0];
const setCount = countState[1];

useState 는 배열을 리턴하는데 첫번째 배열은 값을 리턴하고 두번째 배열값으로 콜백함수를 리턴합니다. 콜백함수를 배열 구조 분해로 사용할때 관용적으로 set 이란 이름을 붙여서 사용합니다. 다른 이름 방식으로 사용해도 되지만 공식문서에서도 해당 방식으로 예제를 설명하고 있어서 국룰처럼 사용하고 있습니다.

 

커스텀 hook 을 만들때에도 hook 에서 관용표현처럼 사용되는 use 를 붙여서 사용합니다.

 

useEffect()

useEffect 는 class 에서의 componentDidMount()componentDidUpdate() , componentWillUnmount() 와 유사한 동작을 수행할 수 있습니다.

useEffect 의 특징으로는 두번째 매개변수로 의존성 배열을 갖습니다. 또 다른 특징으로는 useEffect 는 최초 렌더링시 무조건 한번은 호출된다는 특징을 갖고있습니다.

useEffect(() => {}, []) // componentDidmount 와 유사, 업데이트시엔 호출되지 않음
useEffect(() => {}, [count]) // count 의 변경이 발생하면 콜백함수 호출
useEffect(() => {
  subscibe() //  구독 or 이벤트 할당
  return unSubscibe() // 구독해지 or 이벤트 해제
})

 

의존성 배열이 없다면 업데이트가 발생할때마다 콜백함수를 호출합니다. 때문에 잘못 사용하게되면 무한루프에 빠지게되는 경우가 있으니 올바른 이해를 바탕으로 사용해야합니다.

 

기존 class 에서 라이프사이클을 사용할때는 각 생명주기마다 메소드를 호출해서 사용해야 했지만 hook 에서는 useEffect() 로 사용할 수 있고 개별 상태에 따른 업데이트를 선택적으로 할 수 있습니다.

 

호출 순서는 아래와 같습니다

useState

render

useEffect

 

생성에 대한 내용만 작성하려다가 업데이트까지 일부 포함되었는데 2부에서 좀더 자세히 다뤄보도록 하겠습니다.

728x90
반응형

'프론트엔드 > React' 카테고리의 다른 글

forwardRef 알아보기  (0) 2022.03.11
React Lifecycle - class & hook 2부  (0) 2021.12.17
React 동작 이해하기  (0) 2021.10.18