React

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

prefer2 2022. 9. 14. 15:00

 

 

 

 

 

함수형 컴포넌트와 클래스형 컴포넌트, 두 코드는 완전히 동일할까?

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

사실 직접 경험해 보기 전까지는 거의 동일하지 않나...?라고 생각했었다. 하지만 동일하다고 하기에는 명확한 차이가 있다!

 

 

프로젝트를 진행하며 한 가지 신기한 현상을 마주치게 되었다. resize에 대한 eventListener를 한 번 만 등록해주기 위하여 useEffect의 의존성 배열을 빈 값으로 하여 원하는 함수를 등록해주었다. 콜백 함수는 messageList에 대한 계산을 해주는 함수이다. messageList를 prop으로 받고 있기 때문에 이벤트만 등록해주면 prop값을 가져와 계산해 줄 것이라고 생각했다.

하지만! 클로저 현상이 발생하여 이벤트를 등록했던 순간의 props로 보존이 되었다 😱 (사실 자바스크립트의 스코프와 클로저를 생각하면 당연한 결과이다)

const useSliceMessageList = (messageList: Message[]) => {
  const [slicedMessageLists, setSlicedMessageLists] = useState<Message[][]>(
    Array.from(Array(4), () => [])
  );

  const updateSlicedMessageListByWindowWidth = () => {
 
   // messageList를 사용하여 계산하는 코드 -> newSlicedMessageList

    setSlicedMessageLists(newSlicedMessageList);
  };

  useEffect(() => {
    updateSlicedMessageListByWindowWidth();
  }, [messageList]);

  useEffect(() => {
    window.addEventListener("resize", updateSlicedMessageListByWindowWidth);
    return () =>
      window.removeEventListener(
        "resize",
        updateSlicedMessageListByWindowWidth
      );
  }, []);

  return slicedMessageLists;
};

 

다시 위의 함수형과 클래스형으로 작성된 코드를 생각해보자. 함수형과 클래스형 컴포넌트의 가장 큰 차이는 this이다. 코드를 직접 실행시키면서 그 차이를 느껴보자

1. Follow 버튼을 누르고

2. 3초가 지나기 전에 선택된 프로필을 바꾼다

3. 알림창의 이름을 확인해본다

👉 함수형: Dan의 프로필에서 Sohpie의 프로필로 이동하면 'Follow Dan'이 나타난다.

👉 클래스형: Dan의 프로필에서 Sophie의 프로필로 이동하면 'Follow Sophie'가 나타난다.

클래스형이 보여주는 것은 옳바르지 않다. 어떤 사람을 팔로우하고 탭을 이동했더라도 팔로우 한 사람만을 보여줘야 한다. 이는 클래스의 this가 변경 가능하며(mutable), 조작할 수 있기 때문에 일어나는 일이다. 클래스에서 prop은 this에 종속적이다. 리렌더링 될 때 render가 실행되며, 이에 따라 this 값이 바뀐다.

함수형은 이에 반해 리렌더링 될 때마다 새로운 함수가 불러진다. 따라서 변화된 prop의 값을 함수가 읽어오는 것은 불가능하다.

 

만약 나의 예시처럼 특정 render에 종속된 값이 아닌 가장 최근의 state나 props를 읽고 싶다면 어떻게 해야할까?

함수형에서는 서로 다른 render끼리 공유할 수 있는 친구인 ref를 사용하면 된다. useEffect를 통해 최신 값을 불러오고 싶을 때 마다 ref로 최신 값을 불러오고, 이 값을 사용하자. 하지만 될 수 있다면 props나 state를 고정시키는 것이 좋다.

const useSliceMessageList = (messageList: Message[]) => {
  const messageListRef = useRef(messageList);
  const [slicedMessageLists, setSlicedMessageLists] = useState<Message[][]>(
    Array.from(Array(4), () => [])
  );

  const updateSlicedMessageListByWindowWidth = () => {
  
    // messageList를 사용하여 계산하는 코드 -> newSlicedMessageList
    // ref에 담은 messageList 사용
    
    setSlicedMessageLists(newSlicedMessageList);
  };

  useEffect(() => {
    messageListRef.current = messageList; // messageList가 변할 때마다 ref update
    updateSlicedMessageListByWindowWidth(); 
  }, [messageList]);

  useEffect(() => {
    window.addEventListener("resize", updateSlicedMessageListByWindowWidth);
    return () =>
      window.removeEventListener(
        "resize",
        updateSlicedMessageListByWindowWidth
      );
  }, []);

  return slicedMessageLists;
};

 

 

+) useRef & typescript

@types/react의 index.d.ts를 보면 useRef에 3개의 정의가 오버로딩되어 있다는 것을 확인할 수 있다. 

1️⃣ useRef<T>(initialValue: T): MutableRefObject<T> // mutable하고 인자와 제네릭 타입이 T로 일치
2️⃣ useRef<T>(initialValue: T | null): RefObject<T> // 인자 타입에 null을 허용. 수정 불가
3️⃣ useRef<T = undefined>(): MutableRefObject<T | undefined> // 제네릭 타입이 undefined인 경우.

 

👉 로컬 변수로 useRef를 사용하는 경우

const localVarRef = useRef<number>(0);

로컬 변수 용으로 useRef를 사용하는 경우, MutableRefObject<T>를 사용해야 하므로 generic과 같은 타입의 초기값을 넣어주자.

 

👉 DOM을 조작하기 위해 useRef를 사용하는 경우

const inputRef = useRef<HTMLInputElement>(null);

ref 프로퍼티는 RefObject형만 받기 때문에, MutableRefObject를 사용할 수 없다. RefObject<T>를 사용해야 하므로 초기값에 null을 주자.

 

이전까지는 ref를 DOM에만 주로 사용하면서, ref가 비밀 주머니라는 말이 크게 와닿지 않았던 것 같다. 문제를 해결해보며 함수형 컴포넌트와 ref에 대해 다시 알아갈 수 있었다.

 

Ref

Ref? Ref는 render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공합니다 When you want a component to “remember” some information, but you don’t want that information to trigge..

prefer2.tistory.com

 

 

참고

https://overreacted.io/ko/how-are-function-components-different-from-classes/

https://dev.to/scastiel/react-hooks-get-the-current-state-back-to-the-future-3op2

https://driip.me/7126d5d5-1937-44a8-98ed-f9065a7c35b5

 

 

반응형

'React' 카테고리의 다른 글

[React] Virtual DOM and Rendering  (2) 2022.05.29
Ref  (0) 2022.05.04
[React] Props, State  (0) 2021.11.12
[React] JSX?  (0) 2021.09.02