useEffectiveEvent - experimental

김동규·2023년 3월 5일

React 공부하기

목록 보기
10/10

useEffectiveEvent()

useEffectiveEvent() 훅은 현재 experimental 버전에서만 지원합니다.

const onSomething = useEffectEvent(callback)

sideEffect를 가지고 있으면서 개발자가 원치 않는 상황에서 리렌더링해야하는 문제를 해결하기 위한 hook이다.

useEffectiveEvent의 등장 배경

아래 코드는 url이 변화할 때마다 새로운 채팅방에 연결하는 컴포넌트다.

import React from 'react';

export function ChatRoom({ url, loggingOptions }) {
  	function onConnected(url) {
      logConnnection(`Connected to ${url}`);
    }
  
  	React.useEffect(() => {
    	const room = connnectToRoom(url);
      	room.onConnected(() => {
        	onConnected(url);
        })
      
      	return () => {
        	room.disconnect();
        }
    }, [url])
  
  	return null;
}

그러나 onConnnected에 props로 받은 loggingOptions를 추가한다면...

import React from 'react';

export function ChatRoom({ url, loggingOptions }) {
  	/* onConnected함수에 loggingOptions를 추가하면 missing dependancy 경고 출력 */
  	function onConnected(url) {
      logConnnection(`Connected to ${url}, loggingOptions`);
    }
  
  	React.useEffect(() => {
    	const room = connnectToRoom(url);
      	room.onConnected(() => {
        	onConnected(url);
        })
      
      	return () => {
        	room.disconnect();
        }
        /* 때문에 의존성 배열에 onConnected를 추가해야한다.  */
    }, [url, onConnected])
  
  	return null;
}

이제 새로운 문제는 컴포넌트가 갱신될 때마다 onConnected함수가 새로 선언된다는 점이다. 이럴 때의 해결법은 보통 두 가지인데, 두 가지 모두 별개의 잠재적인 문제점이 있다. 하나씩 살펴보자

  1. onConnected함수를 useEffect내부로 옮기기
import React from 'react';

/* 1. onConnected함수를 useEffect내부로 옮기기 */
export function ChatRoom({ url, loggingOptions }) {  
  	React.useEffect(() => {
       	function onConnected(url) {
     		logConnnection(`Connected to ${url}, loggingOptions`);
    	}
    	const room = connnectToRoom(url);
      	room.onConnected(() => {
        	onConnected(url);
        })
      
      	return () => {
        	room.disconnect();
        }
        /* 
        * 이제 depencyarray에 loggingOptions을 요구한다.
        * 문제는, 프로그래머는 loggingOptions가 변경될 때마다 채팅방에 다시 연결할 의도가 없다는 점이다.
        */
    }, [url])
  
  	return null;
}
  1. useCallback으로 onConnected함수를 감싸기
import React from 'react';

/* 2. useCallback으로 onConnected함수를 감싸기 */
export function ChatRoom({ url, loggingOptions }) {  
  	
  	/* 
    * 같은 문제에 봉착한다. loggingOptions가 변화할 때마다 onConnected함수가 바뀌고
    */
  	const onConnected = React.useCallback((url) => {
        logConnnection(`Connected to ${url}, loggingOptions`);
    }, [loggingOptions])
  
  	React.useEffect(() => {
    	const room = connnectToRoom(url);
      	room.onConnected(() => {
        	onConnected(url);
        })
      
      	return () => {
        	room.disconnect();
        }
        /* 
        * 그리고 onConnected가 바뀌었으므로 useEffect가 동작한다.
        */
    }, [url, onConnected])
  
  	return null;
}

useEffectiveEvent의 활용법

import React, { experimental_useEffectEvent as useEffectEvent } from 'react';

export function ChatRoom({ url, loggingOptions }) {
  	const onConnected = useEffectEvent(url => {
      logConnnection(`Connected to ${url}, loggingOptions`);
    })
  
  	React.useEffect(() => {
    	const room = connnectToRoom(url);
      	room.onConnected(() => {
        	onConnected(url);
        })
      
      	return () => {
        	room.disconnect();
        }
    }, [url])
  
  	return null;
}

useEffectiveEvent동작 원리

  1. useEffectiveEvent는 내부의 콜백함수를 캡슐화하기 때문에 useEffect는 콜백 함수에서 무슨 일이 일어나는지 알지 못한다. 따라서 의존성 배열에 갱신하기를 원하지 않는 값을 넘길 필요가 없다.
  2. useEffectiveEvent내부의 콜백함수에서 props, state들은 항상 갱신된 최신의 값으로 유지된다.

만약 useEffectiveEvent콜백에서 url을 argument에서 url을 제외한다면...?

현재 useEffectEvent의 콜백함수에서는 loggginOptions를 파라미터로 받고 있지 않다. 만약 url도 제거하면 어떻게 될까?

import React, { experimental_useEffectEvent as useEffectEvent } from 'react';

export function ChatRoom({ url, loggingOptions }) {
  	/*
    * 다음과 같은 코드라도 동작은 하지만 잠재적인 문제의 여지가 있다.
    * 만약 아주 빠른 속도로 여러번  전환한다면 (before = url, after = newUrl) 
    * props로 전달받은 loggingOptions값은 항상 최신 상태로 유지되므로 
    * 컴포넌트가 렌더링되는 와중에 다수의 요청이 모두 newUrl으로 진행될 가능성이 있다.
    * 실제 useEffect에서 Sync를 맞춰야 하는 값이 있다면 반드시 명시적으로 콜백함수에 넘겨주자.
    */
  	const onConnected = useEffectEvent(() => {
      logConnnection(`Connected to ${url}, loggingOptions`);
    })
  
  	React.useEffect(() => {
    	const room = connnectToRoom(url);
      	room.onConnected(() => {
        	onConnected();
        })
      
      	return () => {
        	room.disconnect();
        }
    }, [url])
  
  	return null;
}

참고

New React useEffectEvent Hook Crash Course

profile
공식문서를 사랑하는 프론트엔드 주니어 개발자

0개의 댓글