React 기초공부-6

솔다·2023년 2월 18일
0
post-thumbnail

함수형 컴포넌트와 클래스형 컴포넌트의 차이

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

이게 무슨 소리인가? 아래의 예제를 보면서 확인해보자.

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>;
  }
}

이 두개는 겉으로 보기에는 완전히 똑같아 보인다. 하지만 실행을 시켜보면 완전히 다른 것 알고 있는가?

위 그림을 보면, 클래스형과 함수형으로 다른 버튼을 생성해서 위와같이 alert()를 호출한 예제이다. 그런데 5초 후에 알람이 뜨는데, 중간에 프로필을 바꿨을 때 클래스에서는 바뀐 이름이 나오고 함수형에서는 버튼을 눌렀을 때를 기준으로 나온다. 이게 무슨 차이가 있을까?

클래스형 메서드를 살펴보면 아래와 같다.

class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };

이메서드는 props를 가져와서 실행을 하는데, Props는 리액트에서 불변(immutable)값이다. 하지만, this는 변경 가능하며(mutable)/조작도 할 수 있다.

그것이 this가 클래스에서 존재하는 목적이다. 리액트가 시간이 지남에 따라 이를 변경하기 때문에, render나 라이프 사이클 메서드를 호출할 때마다 업데이트된 값을 읽어올 수 있는 것이다. alert()요청이 진행중인상황에서, 새롭게 렌더링 된다면, this.props도 바뀌어서 새로운 값이 나타나는 것이다.

이를 통해서 UI의 흥미로운 특징을 알 수 있는데, 만약 UI가 현재 애플리케이션 상태를 보여주는 함수라 한다면, 이벤트 핸들러 또한 시각적 컴포넌트와 같이 렌더링 결과의 한 부분에 불과하다는 것이다. 즉, 이벤트 핸들러가 어떤 props와 state를 가진 render에 종속된다는 것이다.

this.props를 읽는 콜백을 가진 timeout이 사용되면서 종속관계가 깨져버렸다. this로부터 값을 읽어오는 동작이 종속관계도 부수고, 올바른 props도 잃게 된 것이다.

이런 문제를 해결하는 방법은?

함수형 컴포넌트를 쓰지 않는다는 가정하에 가장 직관적인 방법은, this.propsalert가 뜨는 순단에 호출하지 않고, 미리 호출해서 저장해놓는 방법이다.

이런 방법으로는 단기적인 문제는 해결되지만, 코드가 점점 더 복잡해진다. 이를 해결하기 위한 다른, 더 효율적인 방법은 없을까?

자바스크립트의 클로저를 사용하는 방법이다. render()에서 props와 state를 클로저로 감싸주면, 문젝젝

class ProfilePage extends React.Component {
  render() {
    // props의 값을 고정!
    const props = this.props;

    // Note: 여긴 *render 안에* 존재하는 곳이다!
    // 클래스의 메서드가 아닌 render의 메서드
    const showMessage = () => {
      alert('Followed ' + props.user);
    };

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

    return <button onClick={handleClick}>Follow</button>;
  }
}

이런 함수들을 클래스 메서드로 넣는게 아니라 render의 메서드로 넣는 방법으로 해결할 수 있는 것이다. 이렇게 하면 render될 때의 props를 그대로 유지하기 때문에 문제가 없다!

그렇다면 함수형은?

함수형 컴포넌트에서는 어떨까

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

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

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

위의 코드는 함수형 컴포넌트로 작성된 코드이다. props값이 인자로 전달되었기 때문에, props는 보존된다. 클래스의 this와는 다르게 함수가 받는 인자는 리액트가 변경할 수 없다. 이전에 버튼을 눌러서 alert를 띄우는 경우에는 그 값들이 이전 render에 유지되어 있기 때문에, props값이 변경되어 새로 렌더링 되어도 문제가 발생하지 않는다.

함수형 컴포넌트는 렌더링 될 때의 값들을 그대로 유지한다.

함수형 컴포넌트의 Hooks

함수형 컴포넌트의 Hooks의 state에서도 같은 원리가 적용된다.

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>
    </>
  );
}

위의 경우에는, 메세지를 보낸걸 alert로 띄우는 예제인데, 이 경우 내가 보낸 다음에 내용을 변경해도 render안에 미리 전에 값을 입력해두었던 걸 잘 저장하고 있음을 알 수 있다.

근데 함수형 컴포넌트에서도 this처럼 변할 수 있는 값으로 쓸 수 있는 녀석이 있다. 이녀석은 ref라고 한다.

함수형 컴포넌트의 useRef()this와는 다르게 직접 관리해줘야 한다. 그리고 무언가를 넣을 수 있는 박스라고 봐도 좋다.

this.somethingsomething.current와 비슷한 기능을 한다. 같은 개념의 값이다.

리액트의 ref는 자동으로 propsstate를 최신값으로 유지하는 것은 아니다. 일반적으로 이러한 기능을 쓰는게 드물어서 기본동작으로 두는 것은 비효율적이다. 만약 ref를 이용해 최신값을 유지하고 싶다면, 아래의 방법을 쓰면 된다.

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

  const showMessage = () => {
    alert('You said: ' + latestMessage.current);
  };

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

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

우리가 showMessage()로부터 message를 읽는다면, 우리가 send를 눌렀을 때의 message를 볼 수 있다. 하지만, latestMessage.current를 읽으면, 가장 최근에 보내진 메시지 값을 읽어올 수 있다.

ref는 고정된 값이 아니기 때문에 렌더링 도중에 읽거나 쓰는 것은 피하는 것이 좋다. 렌더링 내에서는 예측 가능한 일들만 일어나는 것이 관정되기 때문이다. 하지만, 특정 prop과 state의 최신값을 불러오고 싶을 때마다 ref를 수동으로 처리하는 것은 내키지 않는다. HooksEffect를 이용해서 이를 자동화 할 수 있다.

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

  // 최신값을 쫓아간다
  const latestMessage = useRef('');
  useEffect(() => {
    latestMessage.current = message;
  });

  const showMessage = () => {
    alert('You said: ' + latestMessage.current);
  };

위의 코드는 내부 DOM이 업데이트 될 때마다 ref값이 변하도록 설정해줬다. 이렇게 하면 값이 렌더링이 되었을 때만 업데이트된 값을 받아 수행하는 렌더리 의존적인 Time Slicing 이나 Suspense 같은 기능들이 피해를 받지 않도록 할 수 있다.

ref는 가능하면 쓰지말고, porps나 state를 고정시키는 편이 좋다. 하지만 interval이나 subscription같은 명령형 API를다룰 때는 ref가 유용하게 쓰일 수 있다.

(출처: https://overreacted.io/ko/how-are-function-components-different-from-classes/)

0개의 댓글