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

kich555·2021년 9월 30일
4

React

목록 보기
1/1
post-thumbnail

리액트에 대해 조금만 공부하다보면, 클래스형 컴포넌트를 사용하는 것 보다 함수형 컴포넌트 + hook 사용하는 것을 권장하는 글을 종종 볼 수 있다. 이번 시간에는 왜 함수 컴포넌트 + hook조합을 사용해야 하는가? 에 대해 알아보도록 하자.

함수'형' 컴포넌트의 오해

본 단락은 아래의 글을 읽고, 좀 더 명확히 이해하기 위해 아래의 글을 다시 작성해보았다.
React에는 '함수형 컴포넌트'가 없다

구글링을 하다보면 꽤나 많은 글에서 함수'형' 컴포넌트에 대한 글을 접할 수 있다.
하지만 리액트 공식문서를 아무리 뒤져도 함수'형' 컴포넌트에 대한 글은 찾을 수 없다.
리액트 공식 문서

함수형 ?

그러면 함수형 컴포넌트에 대해 태클걸기 전에 애초에 함수형 프로그래밍 이 무엇인지 간단하게 알아보자.

함수형 프로그래밍이란 선언형 프로그래밍의 일종으로써 요즘 대두되고 있는 프로그래밍 패러다임의 한 갈래이다.

함수형 프로그램이라는 패러다임 전체를 관통하는 한가지 고유한 성질을 말해보라고 하면 그것은 바로

if f(a) == f(b) then a == b 

일 것이다.

함수형 프로그래밍에서는 어떤 값으로 어떤 함수를 호출하면, 그 값은 immutable 즉 변하지 않는다.
언제든 같은 결과를 얻는다는 소리이며 이러한 성질을 참조 투명성이라고 부른다.

이는 조금 깊게 들어가면 참조 투명성을 지키려면, 함수 f()는 자신의 결과를 변화시킬 수 있는 전역 상태는 변경하면 안된다는 것을 뜻한다. (즉 Side Effect가 없어야 한다)

function double(x) {
  const y = x * 2; // 함수 반환값 생성
  return y; 
};

function loggingDouble(x) {
  const y = x * 2; // 함수 반환값 생성
  // bad!!
  console.log(y); // 외부 함수인 console.log()호출 <- Side Effect 
  return y;
};

double(x)loggingDouble(x) 모두 같은 매개변수 x에 대해 항상 같은 값을 반환하지만 loggingDouble(x) 함수는 반환값 외에 console.log(y)라는 외부 함수(Side Effect)를 호출한다.

double(x) 와 같은 순수함수는 위에서 말했듯이 언제든 같은 결과를 얻는다. 조금만 깊게 들어가면

console.log(double(3)) // 6
//은
console.log(6) // 6
//과 완전히 같다. 
//하지만
console.log(loggingDouble(3)) // 6 6
//loggingDouble의 반환값 6과 
//내부에서 선언한 console.log()의 반환값 6이 동시에 출력된다.

double(3)6으로 대체하여 사용하여도 프로그램 동작 상 변화는 없다.

이는 다시말해서 우리가 선언하고 사용하는 함수에 대해 예측이 쉬워진다는 장점이 있다.

리액트에 함수형 컴포넌트가 없는 이유

useEffect는 리액트가 함수형 컴포넌트가 아닌 함수 컴포넌트라고 정의하는 이유 중 하나이다.

function UserMessage({ name }) {
  const message = '${name}님 안녕하세요!'; // 함수 반환 값 생성
  useEffect(() => {document.title = message}); // Side Effect
  return <h1>{message}</h1>;

useEffect는 이름에서 알 수 있듯이 함수 컴포넌트에서 Side Effect 를 이용하는 도구이다.

이처럼 리액트의 함수 컴포넌트는 무조건 참조 투명성을 가지는 함수형 프로그래밍과는 다르게 선택적 참조 투명성을 가진다. 이는 사용자에게 오해를 일으킬 수 있기 때문에 공식홈페이지에서는 Functional Component라는 이름 대신
Function Component라는 표현을 쓴다.


그럼에도 함수 컴포넌트

위 내용만 듣고보면 함수형도, 객체 지향형도 아닌 어중간한 모습으로 볼 수 있겠지만 그럼에도 함수 컴포넌트를 사용할때 오는 장점들에 대해 한번 이야기해보자.

직관적이다.

함수 컴포넌트는 Vanilla Js와 같은 기본적인 function구조를 사용하기 때문에 상대적으로 더 직관적이며 추상적이다.

import React, { Component } from 'react';

class Test extends Component {
  render() {
    return (
      <div>
        
      </div>
    );
  }
}

export default Test;
// render(), extends Component, constructor 등 
//기본적으로 클래스 컴포넌트는 함수 컴포넌트보다 무겁다.

import React from 'react';

const Test = () => {
  return (
    <div>
      
    </div>
  );
};

export default Test;
// JS에서 평소 사용하던 함수와 크게 다를것이 없어 좀 더 직관적이다.

함수형 컴포넌트는 렌더링된 값들을 고정시킨다

function DeleteComment(props) {
  const showAlert = () => {
    alert(`${props.user}`의 댓글을 삭제합니다.);
  };

  const handleClick = () => {
    setTimeout(showAlert, 5000);
  };

  return (
    <button onClick={handleClick}>삭제</button>
  );
}

class DeleteComment extends React.Component {
  showAlert = () => {
    alert(`${this.props.user}`의 댓글을 삭제합니다.);
  };

  handleClick = () => {
    setTimeout(this.showAlert, 5000);
  };
  render() {
  return (
    <button onClick={this.handleClick}>삭제</button>
  )};
}

위의 두 코드는 마치 하나의 기능을 함수/클래스 컴포넌트를 사용한 것으로 보인다.

그럼 위의 두 코드는 정말 같은 동작을 할까?

정답은 아니요이고 그 이유는 바로 This의 변경 가능한 특징 때문이다.

위 클래스 컴포넌트는

this.props.user

로 값을 받아온다. 이때 props불변(Immutable)의 값이지만, this는 그렇지 않다. this조작할 수 있으며 변경이 가능하다.

이는 Life cycle methodrender를 호출할 때 업데이트된 값을 읽어 올 수 있는 중요한 기능이기도 하지만,

위의 코드와 같은 상황에서는 어떠한 문제를 일으키는지 같이 살펴보도록 하자.

class DeleteComment extends React.Component {
  showAlert = () => {
    alert(`${this.props.user}`의 댓글을 삭제합니다.);
  };

  handleClick = () => {
    setTimeout(this.showAlert, 5000);
  };
  render() {
  return (
    <button onClick={this.handleClick}>삭제</button>
  )};
}
  1. Kich란 계정에서 삭제 버튼을 클릭하여 this.handleClick 함수를 실행시킨다

  2. this.handleClick 함수는 콜백함수로써 5초 뒤 showAlert를 실행시킨다.

  3. 이때 5초가 지나기 전에, (showAlert함수가 실행되기 전에) Kitty의 프로필로 들어가면 어떻게 될까?

즉 props.user가 Kich -> Kitty 로 바뀌어 rerendering이 된다면?

  1. 이런 경우 새로운 render가 호출되었음으로 this 는 업데이트된 props.user (Kitty)를 불러온다.

  2. showAlert 함수는 의도치 않게 업데이트 된 props.user (Kitty) 를 가지고 alert()를 실행시킨다.

showAlert 메서드가 “새로운” props의 user를 읽는 것이다.

이는 이벤트 핸들러 또한 시각적 컴포넌트와 같이 렌더링 결과의 한 부분이라는 것이다. 즉 이벤트 핸들러가 어떤 props와 state를 가진 render에종속이 되므로 props가 불변적이더라도 더 상위개체인 render의 변경 시 해당 props또한 변경된다.

즉 비동기 함수인 콜백 함수가 실행되는 순간 showAlert라는 콜백은 this에 묶여 고정된 props를 가지지 못한, setTimeout에서 정한 5초동안 마치 둥둥 떠다니는, 어디에든 종속될 수 있는 불안한 상태를 가지게되는 것이다.


함수형 컴포넌트는 render 될 때의 값들을 유지한다.

function MessageThread() {
  const [message, setMessage] = useState('');

  const showMessage = () => {
    alert('You said: ' + message);
  };

  const handleSendClick = () => {
    setTimeout(showMessage, 3000);
  };

  const handleMessageChange = (e) => {
    setMessage(e.target.value);
  };

  return (
    <>
      <input value={message} onChange={handleMessageChange} />
      <button onClick={handleSendClick}>Send</button>
    </>
  );
}

class MessageThread extends React.Component {
  state = {
    message: ""
  };

  showMessage = () => {
    alert("You said: " + this.state.message);
  };

  handleSendClick = () => {
    setTimeout(this.showMessage, 3000);
  };

  handleMessageChange = (e) => {
    this.setState({
      message: e.target.value
    });
  };

  render() {
    const { message } = this.state;
    return (
      <>
        <input value={message} onChange={this.handleMessageChange} />
        <button onClick={this.handleSendClick}>Send</button>
      </>
    );
  }
}

또 다른 예시를 보자.

위의 코드는 함수형 컴포넌트와 클래스, 어떤 차이가 존재할까? by Dan Abramov.
의 게시글에 있는 코드를 복사해 온것이며,

아래 코드는 내가 테스트를 위해 dan의 코드를 class형으로 변환시켜본 것이다.

Dan 의 예시 를 테스트 해 본 뒤 아래의 코드를 복사 붙여넣기하여 class 컴포넌트에서는 어떠한 일이 벌어지는지 확인해 보자.

Dan의 예시에서는 send 버튼을 클릭한 순간 의 input값을 보여주지만,

클래스 컴포넌트에서는 showMessage가 실행되기 직전까지의 input값을 보여준다.

그 이유는 함수 컴포넌트에서 {message}handleSendClick가 호출되었을 때의 state를 고정시키지만,

클래스 컴포넌트에서 {message}this에 묶여있는 state 덕분에 input이 rerendering 될때마다 바뀌는, mutable한 값이 되어버리기 때문이다.


결론

솔직히 아직 React는 걸음마 수준이기 때문에 공식 홈페이지에서 설명하는 그런 this에 의한 이슈들을 직접 경험해본 적은 없다.

하지만 앞으로 class 컴포넌트를 사용할 시 this의 mutable한 성질에 대해 항상 유의하며 coding을 해야겠다고 느꼈으며, React공부 덕분에 뒷전으로 밀려난 JS 이론에 대해서도 경각심이 들었던, 그런 뜻깊은 시간이었다.
(마침 어떻게 실행 콘텍스트와 this에 대해 공부하기 직전에 이런 일이....)

(추가로 공식 홈페이지에 있는 class컴포넌트의 다른 이슈들에 대해선 차차 추가해나갈 예정이다)

profile
const isInChallenge = true; const hasStrongWill = true; (() => { while (isInChallenge) { if(hasStrongWill) {return 'Success' } })();

2개의 댓글

comment-user-thumbnail
2022년 9월 22일

잘봤습니다! 함수형 < - > 함수 컴포넌트 네이밍에 대해서 잘 알게 됐어요

답글 달기
comment-user-thumbnail
2024년 1월 23일

CertsTopics offers SAP SAP Certified Professional - SAP Enterprise Architect real exam questions and practice test engine with real questions and verified answers. Try SAP Certified Professional - SAP Enterprise Architect exam questions for free. You can also download a free PDF demo of SAP SAP Certified Professional - SAP Enterprise Architect exam.
SAP P_SAPEA_2023 real dumps

답글 달기