본문 바로가기
언어 Language/리액트 React

[React] 클래스형 컴포넌트와 함수형 컴포넌트

by 이땡칠 2022. 7. 26.

 

클래스형 컴포넌트와 함수형 컴포넌트 (Feat. 클래스형만 파본다...)

 

컴포넌트는 데이터가 주어졌을 때 이에 맞추어 UI를 만들어 주는 기능을 하는 것은 물론, 라이프 사이클 API를 통해 컴포넌트가 화면에 나타날 때, 사라질 때, 변할 때 작업들을 수행할 수도 있습니다. 컴포넌트의 목적에 따라 프리젠테이션(presentational) 컴포넌트와 컨테이너(container) 컴포넌트로 나누기도 합니다.

 

 

리액트를 사용하여 프론트 개발을 할 때  클래스형, 함수형 두 가지 방법으로 컴포넌트를 선언할 수가 있습니다.

 

과거엔 클래스형 컴포넌트를 주로 사용했지만, 2019년 v16.8 부터 함수형 컴포넌트에 리액트 훅(hook)을 지원해 주어서 현재는 공식 문서에서 함수형 컴포넌트와 훅을 함께 사용할 것을 권장하고 있습니다. 

 

두 가지 방법에 대해 모두 다 잘 알고 있으며, 필요한 상황에 맞게 사용하는 것이 중요하므로 각각의 방법에 대해서 좀 더 깊이있게 알고 있어야 합니다.

 

 

[참고, 공식문서] hook 이 만들어진 배경

1. 컴포넌트 사이에서 상태 로직을 재사용하기 어렵습니다.

React는 컴포넌트간에 재사용 가능한 로직을 붙이는 방법을 제공하지 않습니다. (예를 들어, 스토어에 연결하는 것) 이전부터 React를 사용해왔다면, render props이나 고차 컴포넌트와 같은 패턴을 통해 이러한 문제를 해결하는 방법에 익숙할 것입니다. 그러나 이런 패턴의 사용은 컴포넌트의 재구성을 강요하며, 코드의 추적을 어렵게 만듭니다. React 개발자 도구에서 React 애플리케이션을 본다면, providers, consumers, 고차 컴포넌트, render props 그리고 다른 추상화에 대한 레이어로 둘러싸인 “래퍼 지옥(wrapper hell)“을 볼 가능성이 높습니다. 개발자 도구에서 걸러낼 수 있지만, 이 문제의 요점은 심층적이었습니다. React는 상태 관련 로직을 공유하기 위해 좀 더 좋은 기초 요소가 필요했습니다.

 

Hook을 사용하면 컴포넌트로부터 상태 관련 로직을 추상화할 수 있습니다. 이를 이용해 독립적인 테스트와 재사용이 가능합니다. Hook은 계층의 변화 없이 상태 관련 로직을 재사용할 수 있도록 도와줍니다. 이것은 많은 컴포넌트 혹은 커뮤니티 사이에서 Hook을 공유하기 쉽게 만들어줍니다.

 

2. 복잡한 컴포넌트들은 이해하기 어렵습니다.

우리는 때론 간단하게 시작했지만 관리하기가 힘들어지는 상태 관련 로직들과 사이드 이펙트가 있는 컴포넌트들을 유지보수해야 합니다. 각 생명주기 메서드에는 자주 관련 없는 로직이 섞여들어가고는 합니다.

 

예시로 componentDidMount  componentDidUpdate는 컴포넌트안에서 데이터를 가져오는 작업을 수행할 때 사용 되어야 하지만, 같은 componentDidMount에서 이벤트 리스너를 설정하는 것과 같은 관계없는 로직이 포함되기도 하며, componentWillUnmount에서 cleanup 로직을 수행하기도 합니다. 함께 변경되는 상호 관련 코드는 분리되지만 이와 연관 없는 코드들은 단일 메서드로 결합합니다. 이로 인해 버그가 쉽게 발생하고 무결성을 너무나 쉽게 해칩니다.

 

위와 같은 예시에서, 상태 관련 로직은 한 공간안에 묶여 있기 때문에 이런 컴포넌트들을 작게 분리하는 것은 불가능하며 테스트하기도 어렵습니다. 이 때문에 많은 사용자들은 React를 별도의 상태 관리 라이브러리와 함께 결합해서 사용해왔습니다. 그러나, 이런 상태 관리 라이브러리는 종종 너무 많은 추상화를 하고, 서로 다른 파일들 사이에서 건너뛰기를 요구하며 컴포넌트 재사용을 더욱더 어렵게 만들었습니다.

 

이같은 문제를 해결하기위해, 생명주기 메서드를 기반으로 쪼개는 것 보다는, Hook을 통해 서로 비슷한 것을 하는 작은 함수의 묶음으로 컴포넌트를 나누는 방법을 사용할 수 있습니다. (구독 설정 및 데이터를 불러오는 것과 같은 로직) 또한 이러한 로직의 추적을 쉽게 할 수 있도록 리듀서를 활용해 컴포넌트의 지역 상태 값을 관리하도록 할 수 있습니다.

 

3. Class 는 사람과 기계를 혼동시킵니다.

React 에서의 Class 사용을 위해서는 JavaScript의 this 키워드가 어떻게 작동하는지 알아야만 합니다. JavaScript의 this키워드는 대부분의 다른 언어에서와는 다르게 작동함으로 사용자에게 큰 혼란을 주었으며, 코드의 재사용성과 구성을 매우 어렵게 만들고는 했습니다. 또한 class의 사용을 위해 이벤트 핸들러가 등록되는 방법을 정확히 파악해야 했으며, 이는 불안정한 문법 제안들의 도움이 없을 시엔, 코드를 매우 장황하게 만들었습니다. 사용자들은 props, state, 그리고 top-down 데이터 흐름을 완벽하게 하고도, Class의 이해에는 어려움을 겪고는 했습니다. React 내의 함수와 Class 컴포넌트의 구별, 각 요소의 사용 타이밍 등은 숙련된 React 개발자 사이에서도 의견이 일치하지 않습니다.

 

 

 

1. 클래스형 & 함수형 주요 사항 비교

구분 클래스형 컴포넌트 함수형 컴포넌트
state  설정 가능 useState hook을 사용해서 가능 
lifeCycle API 사용 가능

useEffect hook을 사용해서 가능
*100% 구현은 아님
메모리 자원 사용 상대적으로 더 사용 상대적으로 덜 사용
커스텀 훅   가능


* getSnapshotBeforeUpdate, getDerivedStateFromError  componentDidCatch 생명주기에 해당하는 Hook은 아직 없음

 

 

2. 차이 뜯어보기

클래스형 컴포넌트

ES6 class 를 사용하여 클래스형 컴포넌트를 정의할 수 있습니다.  

class Welcome extends React.Component {
  constructor(props) {
    super(props);
    this.state = {name: 땡칠이};
  }
  
  componentDidMount() {
  }

  componentDidUpdate() {
  }
  
  componentWillUnmount() {
  }
  
  
  
  render() {
    return <h1>Hello, {this.state.name}</h1>;
  }
}

 

1) Constructor  

- class 키워드로 시작합니다. 

- class 에 로컬 state 를 추가하기 위해서는 class constructor 를 추가합니다. constructor는 컴포넌트의 생성자 메서드, 컴포넌트가 만들어지면 가장 먼저 실행되는 메서드입니다. 컴포넌트가 브라우저에 나타나기전, 즉, render()전에 호출이되는 라이프 사이클 함수입니다.

- 클래스 컴포넌트는 항상 props로 기본 constructor를 호출해야 합니다.

- constructor 안에서 this.state 초기 값 설정으로 state 값을 설정할 수 있습니다.

- 클래스형 컴포넌트의 state는 객체 형식입니다.

- this.setState 함수로 state의 값을 변경할 수 있습니다.

 

 

2) render 

컴포넌트를 렌더링하는 메서드로 컴포넌트를 정의할 때 반드시 작성해야 합니다.

render 메서드에서는 부수 효과를 발생시키면 안 됩니다. 서버와 통신하기, 브라우저의 쿠키에 저장하기 등은 부수 효과이므로 render 메서드 내부에서는 피해야 합니다. 만약 부수 효과가 필요하다면 다른 생명 주기 메서드에서 하면 됩니다.

 

3) componentDidMount() 

componentDidMount  메서드는 컴포넌트 출력물이 DOM에 렌더링 된 후에 실행됩니다. 

render 메서드에서 반환한 리액트 요소가 돔에 반영되어야 알 수 있는 값을 얻을 수 있기 때문에, 해당 컴포넌트에서 필요로 하는 데이터를 요청하기 위해 axios, fetch등을 이용해 외부 api를 호출하는 작업을 진행합니다.

 

4) componentWillUnmount() 

componentDidUpdate 메서드는 업데이트 단계에서 마지막으로 호출되는 생명 주기 메서드입니다. 이 메서드는 가상 돔이 실제 돔에 반영된 후 호출됩니다. 따라서 componentDidUpdate 는 새로 반영된 돔의 상태값을 가장 빠르게 가져올 수 있는 메서드입니다.

 

5) componentWillUnmount() 

componentWillUnmount 메서드는 컴포넌트가 화면에서 사라지기 직전에 호출됩니다.

주로 사전에 해당 컴포넌트에 등록했었던 이벤트 및 연관 외부 라이브러리 호출등을 제거할 때 사용됩니다.

 

 

함수형 컴포넌트

함수형 컴포넌트는 함수 문법을 사용해서 정의할 수 있습니다.

그리고 hook 을 통해 클래스형 컴포넌트가 가진 다양한 기능들을 구현할 수 있습니다.

 

Hook 이 뭔가요?

Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수입니다. Hook은 class 안에서는 동작하지 않습니다. 대신 class 없이 React를 사용할 수 있게 해주는 것입니다.

 

예제

function Welcome(props) {
  const [state, setSteat] = useState();
  
  // 1. 컴포넌트가 렌더링 될 때마다 effect 가 실행
  useEffect(() => {
  // effect 구문
  })
  
  // 2. 컴포넌트가 마운트, 언마운트 된 다음에 1회만 effect 가 실행
  useEffect(() => {
    // effect 구문
  }, [])
  
  // 3. 컴포넌트의 state 가 변경되어 리렌더링 된 이후에 effect 실행
  useEffect(() => {
    // effect 구문
  }, [state])
  
  
  // 4. 컴포넌트가 마운트 된 다음에 1회 effect 실행, 컴포넌트가 unmount 되기 직전에 return 구문 실행
  useEffect(() => {
  // effect 구문
  return 
  // clean up 구문
  }, [])
  
  
  return <h1>Hello, {props.name}</h1>;
}

 

1) useState

  •  함수형 컴포넌트에서는 useState 로 상태를 선언하고 사용할 수 있습니다. 
  • seState 함수를 호출하면 배열이 반환되는데
  • 첫 번째 원소는 현재 상태이고, 두 번째 원소는 상태를 바꾸어 주는 함수입니다.

 

2) useEffect

  • useEffect는 함수 컴포넌트 내에서 side effects를 수행할 수 있게 해줍니다.
  • React class의 componentDidMount 나 componentDidUpdate, componentWillUnmount와 같은 목적으로 제공되지만, 하나의 API로 통합된 것입니다.
  • useEffect 내에 있는 함수는 컴포넌트 렌더링이 모두 마친 후 실행됩니다. 
  • useEffect 내에 있는 return 구문은 컴포넌트가 브라우저에서 사라질 때 실행됩니다. (clean up 함수)

 

 

3. 공식문서

클래스형 컴포넌트 전체적으로 이해해보기 

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

 

시계는 매초 째깍거립니다.

현재 어떤 상황이고 메서드가 어떻게 호출되는지 순서대로 빠르게 요약해 보겠습니다.

  1. <Clock />가 ReactDOM.render()로 전달되었을 때 React는 Clock 컴포넌트의 constructor를 호출합니다. Clock이 현재 시각을 표시해야 하기 때문에 현재 시각이 포함된 객체로 this.state를 초기화합니다. 나중에 이 state를 업데이트할 것입니다.
  2. React는 Clock 컴포넌트의 render() 메서드를 호출합니다. 이를 통해 React는 화면에 표시되어야 할 내용을 알게 됩니다. 그 다음 React는 Clock의 렌더링 출력값을 일치시키기 위해 DOM을 업데이트합니다.
  3. Clock 출력값이 DOM에 삽입되면, React는 componentDidMount() 생명주기 메서드를 호출합니다. 그 안에서 Clock 컴포넌트는 매초 컴포넌트의 tick() 메서드를 호출하기 위한 타이머를 설정하도록 브라우저에 요청합니다.
  4. 매초 브라우저가 tick() 메서드를 호출합니다. 그 안에서 Clock 컴포넌트는 setState()에 현재 시각을 포함하는 객체를 호출하면서 UI 업데이트를 진행합니다. setState() 호출 덕분에 React는 state가 변경된 것을 인지하고 화면에 표시될 내용을 알아내기 위해 render() 메서드를 다시 호출합니다. 이 때 render() 메서드 안의 this.state.date가 달라지고 렌더링 출력값은 업데이트된 시각을 포함합니다. React는 이에 따라 DOM을 업데이트합니다.
  5. Clock 컴포넌트가 DOM으로부터 한 번이라도 삭제된 적이 있다면 React는 타이머를 멈추기 위해 componentWillUnmount() 생명주기 메서드를 호출합니다.

 

 

 

 

 

출처

공식문서 https://ko.reactjs.org/docs/state-and-lifecycle.html

[React] 클래스형 컴포넌트와 함수형 컴포넌트

클래스형과 함수형 차이

댓글