본문 바로가기

React/Basic

React Hook - useEffect

* 목차

 - Intro

 - Example

 - Extension 1. Dependencies

 - Extension 2. Connecting to an external system

 - Extension 3. Controlling a non-React widget

 - 마치며

 

* Hook의 기본개념을 알고 싶으시다면 React 소개부터 보시길 바랍니다

 

React란?

* 바쁘신 분들을 위한 3줄 요약 1. React는 Meta(Facebook)에서 만든 Javascript 라이브러리이다. 2. JSX 문법을 지원하여 html tag를 마치 프로그래밍 하듯이 작성할 수 있다. 3. Hook, 가상 DOM을 통해 Web applicatio

tyoon9781.tistory.com

 


Intro

value의 life cycle를 다루는 Hook으로 useState가 있다면 Component는 useEffect가 있습니다. 

useEffect(setup, dependencies?)

 

간단한 예제로는 다음과 같이 작성할 수 있습니다. dependencies는 없어도 됩니다.

const ExampleComponent = () => {
  useEffect(() => {
    // component generate
    return () => {
      // component cleanup
    };
  }, [dependencies?]);
}

 

useEffect는 component의 life cycle를 다루다 보니 component 안에 작성해햐 합니다. 바로 예제로 들어가 봅시다.

 


Example

먼저 component 예제를 간단하게 작성해 봅시다.

import React, { useEffect, useState } from "react";
import "./App.css";


const ExampleComponent: React.FC = () => {
  const [count, setCount] = useState<number>(0);

  const handleIncrement = () => {
    setCount((prevCount) => prevCount + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleIncrement}>Increment</button>
    </div>
  );
};

const App = () => {
  return (
    <>
      <ExampleComponent />
    </>
  );
};

export default App;

 

그리고 ExampleComponent에 useEffect를 작성해서 ExampleComponent가 생성되고 소멸되는 시점을 Control 할 수 있도록 합니다.

const ExampleComponent: React.FC = () => {
  const [count, setCount] = useState<number>(0);
  
  useEffect(() => {
    console.log("ExampleComponent mounted");
    return () => {
      console.log("ExampleComponent unmounted");
    };
  }, []);

  const handleIncrement = () => {
    setCount((prevCount) => prevCount + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleIncrement}>Increment</button>
    </div>
  );
};

 

log가 중복해서 뜨는건 Strict.Mode때문입니다.

 

그리고 이번에는 ExampleComponent를 생성하고 소멸시킬 수 있도록 해보겠습니다.

const App = () => {
  const [componentState, setComponentState] = useState<boolean>(false);
  const handleComponentState = () => {
    setComponentState((prevState) => !prevState);
  };

  return (
    <>
      {componentState ? <ExampleComponent /> : <div>no Component</div>}
      <button onClick={handleComponentState}>
        {componentState ? "Turn off Component" : "Turn on Component"}
      </button>
    </>
  );
};

 

그러면 이제 Component를 mount, unmount할 때마다 console.log가 동작합니다.

 

이제 간단한 예제를 살펴보았으니 좀 더 어려운 예제도 살펴보도록 하겠습니다.

 


Extension 1. Dependencies

Dependencies는 useEffect의 state 뒤에 넣는 Array 형태입니다. Dependencies가 없다면 mount, unmount시에만 useEffect가 동작하지만, useEffect에 Dependencies를 추가한다면 Dependencies의 변화에도 useEffect의 state 함수가 동작하게 됩니다.

 

예를 들어 count가 변할 때마다 useEffect 함수를 실행한다고 하면 다음과 같이 진행할 수 있습니다.

  useEffect(() => {
    console.log(`ExampleComponent mounted. current Count : ${count}`);
    return () => {
      console.log("ExampleComponent unmounted");
    };
  }, [count]);

 

이렇게 작성한다면 이제 count를 변화시켜도 mounted라는 console.log가 동작합니다.

 


Extension 2. Connecting to an external system

어떤 Component들은 page에 표시되기 까지 network, browser API, 서드파티 라이브러리와 연결을 유지해야 합니다. 이런 시스템들은 React에서 관리할 수 없으므로 외부 시스템이라 할 수 있습니다. 

import { useEffect } from 'react';

const createConnection = (serverURL, roomID) => {
  return {
    connect() {
      console.log(`connecting to ${roomID} room at ${serverURL}`);
    },
    disconnect() {
      console.log(`disconnected from ${roomID} room at ${serverURL}`);
    }
  };
}

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [serverUrl, roomId]);
  // ...
}

 


Extention 3. Controlling a non-React widget

react로 non-React 서트파티 위젯을 다룰 때 useEffect를 활용해서 동기화 할 수 있습니다. 예제에서는 MapWidget을 만든 후 setZoom method를 만든 후에 Map component에서 useEffect에 map.setZoom으로 동기화 상태를 유지합니다.

 

* yarn add leaflet 명령어로 leaflet을 사전 설치해야 합니다.

import { useState, useRef, useEffect } from "react";
import "leaflet/dist/leaflet.css";
import * as L from "leaflet";


class MapWidget {
  constructor(domNode) {
    this.map = L.map(domNode, {
      zoomControl: false,
      doubleClickZoom: false,
      boxZoom: false,
      keyboard: false,
      scrollWheelZoom: false,
      zoomAnimation: false,
      touchZoom: false,
      zoomSnap: 0.1,
    });
    L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
      maxZoom: 19,
      attribution: "© OpenStreetMap",
    }).addTo(this.map);
    this.map.setView([0, 0], 0);
  }
  setZoom(level) {
    this.map.setZoom(level);
  }
}

const Map = ({ zoomLevel }) => {
  const containerRef = useRef(null);
  const mapRef = useRef(null);

  useEffect(() => {
    if (mapRef.current === null) {
      mapRef.current = new MapWidget(containerRef.current);
    }

    const map = mapRef.current;
    map.setZoom(zoomLevel);
  }, [zoomLevel]);

  return <div style={{ width: 600, height: 600 }} ref={containerRef} />;
}

export default function App() {
  const [zoomLevel, setZoomLevel] = useState(0);
  return (
    <>
      Zoom level: {zoomLevel}x
      <button onClick={() => setZoomLevel(zoomLevel + 1)}>+</button>
      <button onClick={() => setZoomLevel(zoomLevel - 1)}>-</button>
      <hr />
      <Map zoomLevel={zoomLevel} />
    </>
  );
}

non react app인 leaflet지도의 배율을 react로 control하여 동기화를 유지하고 있다.

 


마치며

useEffect는 참으로 편리합니다. 어떠한 변수의 변화가 생겼을 때 그 변화를 catch해서 component 최신화를 유지할 수 있는 기능을 따로 구현하지 않아도 된다는 점이 참 매력적입니다. 하지만 반대로 강력한 기능을 남용하거나 오용해서 Application의 성능에 큰 영향을 미칠 수도 있습니다. 불필요한 Component mount를 일으키거나 unmount를 제대로 설정하지 않아 component가 제거되었는데도 connection은 끊어지지 않아 불필요한 traffic을 일으킬 수 있습니다. 강력한 기능인 만큼 제대로 설계를 하여 useEffect를 사용하시길 바랍니다. 감사합니다.

 


*reference

https://react.dev/reference/react/useEffect

https://react.dev/learn/removing-effect-dependencies

'React > Basic' 카테고리의 다른 글

Redux로 React의 Props drilling 해결하기  (0) 2023.09.20
React Hook - useReducer  (0) 2023.07.24
React Hook - useState  (0) 2023.07.23
React Hook 알아보기  (0) 2023.07.23
Material UI - Tutorial  (0) 2023.07.09