클래스형 컴포넌트 함수형 컴포넌트 Deep Dive

정성연·2022년 8월 12일
2
post-thumbnail

개발을 하는 도중 functional Component의 callback에서는 state나 props가 바뀌지 않고, Class Component에서는 state나 props가 바뀌는 경우를 확인했다. 왜 이런현상이 발생하는지 의문이 들어 글을 작성한다.

여기서 설명하는 것

  • hook의 사용 이유
  • 클래스형 컴포넌트와 함수형 컴포넌트의 근본적 차이

Hook란 무엇인가? 왜 생겨났을까 ?

react에서 hook은 클래스형 컴포넌트에서 밖에 할 수 없었던, 함수형 컴포넌트에서 상태값을 조정하거나, 라이프사이클 이벤트를 등록할 수 있게 해준다.

그렇다면 클래스형 컴포넌트가 있는데 함수형 컴포넌트의 hook는 왜 생겨 난 것일까 ?

클래스형 컴포넌트에서 여러 컴포넌트에서 사용되는 비즈니스 로직을 재 사용하기 위해서는 HOC방식 또는 render props방식을 이용했는데, 이 방식의 단점으로는 컴포넌트의 계층레벨이 하나 더 생긴다는 단점이 있다.
하지만 hook를 사용한다면 컴포넌트의 계층을 추가하지 않고 로직을 재사용 하는것이 가능해진다.

또한, 클래스 컴포넌트의 라이프사이클 이벤트에서 관련된 로직을 각 생명주기에 따로 할당해주게 되는 방식이였다.

componentDidMount() {
  	// 리스너 등록
}
componentWillUnmount() {
  	// 리스너 해제
}

위와 같은 방식은 이벤트 해제를 진행하지 않아, GC에의해 메모리 정리를 진행하지 못하는 휴먼에러가 나타나기 쉽다.

useEffect(()=> {
  // 리스너 등록
  return //리스너 해제
},[])

하지만 hook에서는 위와 같은 방법으로 하나의 라이프사이클 함수에 하나의 관련된 로직을 넣을 수 있게 되었다.

추가적으로 다음과 같은 이유들이 있다.

  • prepack을 사용한 방식에서 class 컴포넌트는 최적화에 좋지 않은 성능을 낸다.
  • class 컴포넌트의 this키워드의 사용법을 이해하고 있어야 하며, Functional 컴포넌트에 비해 장황한 코드를 만들어 낸다.

그렇다면 함수형 컴포넌트가 좋은것인가 ?

클래스형 컴포넌트와 함수형 컴포넌트의 차이는 성능적으로 측정하기 힘들다. 사용되는 방식에 따라 다른 결과를 내기때문에 지금까지 나온 벤치마킹 자료는 신뢰 할 수 없다.
또한 둘의 성능 차이가 나는 경우에도 무시할 수 있을 정도로 작은 편이다.

그렇다면 무엇이 다를까 ?

둘은 구조가 다르고 그에 따른 최적화 방식이 다르다. 그럼으로 기본적으로 작성한 클래스혐 컴포넌트를 함수형 컴포넌트로 굳이 바꿀 필요는 없다.

이제 무엇이 다른지 알아보자.

  1. 함수형 컴포넌트는 props값 고정한다. ?

먼저 아래 같은 기능을 하는 함수형 컴포넌트와 클래스형 컴포넌트를 보자

function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed ' + props.user);
  };

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

  return (
    <button onClick={handleClick}>Follow</button>
  );
}
class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };

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

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

둘의 어떤 차이가 있는지 1분정도 생각 할 시간을 가지면 좋겠다.

둘은 어떤 차이가 있을까 ?

둘 모두 기능은 버튼 클릭시 3초뒤 props의 값을 출력해주는 로직이다.

하지만 둘의 차이는 버튼 클릭 후 3초 사이에 props.user 값이 변경 되었을때 나온다.

클래스형 컴포넌트는 this를 통해서 props에 접근한다.
react는 this가 가리키는 props를 변경하기에 props.user값이 변경된 props를 참조하고, 변경된 후의 user값을 출력한다.
(참고로 constructor에서 this를 바인딩 하더라도, this의 참조값이 변경되는 현상임으로 같은 결과를 가져온다)

함수형 컴포넌트는 props를 인자로 전달 받기에 props.user 값이 도중에 변경되더라도 3초 후 변경 되기 전 user값을 출력한다.
(부모 컴포넌트가 새로운 함수를 호출하더라도 이미 호출된 함수는 이전 함수형 컴포넌트에 종속되어 있는 상태다)

이러한 이유로 함수형 컴포넌트의 props가 변경전 props를 가리키거나, 클래스 컴포넌트에서 변경 후의 props를 가리키는 경우가 생기게 된다.

그렇다면 클래스형 컴포넌트에서 최신 값이 아닌 이전 값을 사용하려면 어떻게 해야할까 ?

가장 쉬운 방법으로는 props의 primitive한 값을 인자로 넘겨주는 것이다.

class ProfilePage extends React.Component {
  showMessage = (user) => {
    alert('Followed ' + user); // user를 인자로 받아서 사용.
  };

  handleClick = () => {
    const {user} = this.props; // user를 원시값으로 추출
    setTimeout(() => this.showMessage(user), 3000); // 인자로 user를 넘겨줌
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

하지만 위 방법을 사용시, 함수의 깊이가 깊어질 수록 인자를 넘겨주는 깊이가 깊어진다는 단점이 있다. 이는 코드를 복잡하게 만들며, 시간이 지날수록 에러에 노출될 가능성이 크다.

  1. 함수형 컴포넌트는 state값을 고정한다. ?

이러한 현상은 props가 아닌 경우에도 같은 현상이 일어난다.

 const [count, setCount] = useState(1);
 useEffect(() => {
  let timer = setInterval(() => {
    console.log(count);
    setCount(count + 1);
  }, 1000);
  return () => clearTimeout(timer);
}, []);

위 코드의 console.log는 아래와 같이 보여질 것으로 예상이 된다.

1
2
3
...

하지만 아래처럼 1만 계속 출력되는 것을 확인 할 수 있다.

이러한 이유가 발생하는 이유는 useEffect의 콜백에 이전 count값이 캡쳐되어 stale closure를 만들기 때문에 발생한다.

이를 해결 하기위해 useEffect의 두번째 인자로 count를 넘겨 주면 되지만, 하지만 이렇게 하면 매번 이벤트를 등록하고 해제하는 불필요한 작업을 하게 된다.

 const [count, setCount] = useState(1);
 useEffect(() => {
  let timer = setInterval(() => {
    console.log(count);
    setCount(count + 1);
  }, 1000);
  return () => clearTimeout(timer);
}, [count]); // 의존성 배열에 count추가

다른 방식으로는 useRef를 사용하는 방식이다.

이와 같은 방식은 일반적인 방식에서 기본동작으로 두는 것은 비효율적인 방법이다.
최신값을 유지하고 싶다면 위와 같은 방식을 사용 할 수 있다.

(위와 같은 현상은 클로저에 의한 문제라기 보다 props와 state가 변하지 않은 상태에서 사용되어 발생하는 문제로 보인다)

출처

https://www.netlify.com/blog/2019/03/11/deep-dive-how-do-react-hooks-really-work/
https://overreacted.io/ko/how-are-function-components-different-from-classes/

profile
개발자

0개의 댓글