useEffect

Chaerin Kim·2023년 11월 27일

컴포넌트를 외부 시스템과 동기화할 수 있는 React Hook

useEffect(setup, dependencies?)

Reference

useEffect(setup, dependencies?)

컴포넌트의 최상위 레벨에서 useEffect를 호출하여 Effect를 선언함:

import { useEffect } from 'react';
import { createConnection } from './chat.js';

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

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

Parameters

  • setup: Effect의 로직이 포함된 함수. Setup 함수는 선택적으로 cleanup 함수를 반환할 수도 있음. 컴포넌트가 DOM에 추가되면 React는 setup 함수를 실행함. 변경된 dependencies로 다시 렌더링할 때마다 React는 이전 값으로 cleanup 함수(제공한 경우)를 실행한 다음 새 값으로 setup 함수를 실행함. 컴포넌트가 DOM에서 제거된 후 React는 cleanup 함수를 실행함.

  • dependencies (optional): setup 코드 내부에서 참조된 모든 반응형 값의 목록. 반응형 값에는 props, state, 컴포넌트 본문 내부에서 직접 선언된 모든 변수와 함수가 포함됨. Linter가 React용으로 구성된 경우, 모든 반응형 값이 dependency로 올바르게 지정되었는지 확인함. dependencies 목록에는 일정한 수의 항목이 있어야 하며 [dep1, dep2, dep3]과 같이 인라인으로 작성해야 함. React는 Object.is 비교를 사용하여 각 dependency를 이전 값과 비교함. 이 인수를 생략하면 컴포넌트를 다시 렌더링할 때마다 Effect가 다시 실행됨.

참고: dependencies에 배열을 전달하는 것, 빈 배열을 전달하는 것, 아무것도 전달하지 않는 것의 차이

Returns

반환값이 없음.

Ceveats

  • useEffect는 Hook이므로 컴포넌트의 최상위 레벨이나 커스텀 Hook에서만 호출할 수 있음. 루프나 조건 내부에서는 호출할 수 없음. 필요한 경우 새 컴포넌트를 추출하고 state를 그 안으로 옮겨야함.

  • 외부 시스템과 동기화하려는 것이 아니라면 Effect가 필요하지 않을 것.

  • Strict Mode가 켜져 있으면 React는 첫 번째 실제 setup 전에 개발 전용 setup + cleanup 사이클을 한 번 더 실행함. 이는 cleanup 로직이 setup 로직을 "미러링"하고 setup이 수행 중인 모든 작업을 중지하거나 취소하는지 확인하는 스트레스 테스트임. 이것이 문제를 일으키면 cleanup 함수를 구현해야함.

  • dependencies 중 일부가 컴포넌트 내부에 정의된 객체 또는 함수인 경우 Effect가 필요 이상으로 자주 다시 실행될 위험이 있음. 이 문제를 해결하려면 불필요한 객체함수 dependencies를 제거해야함. 또한 state 업데이트비반응형 로직을 Effect 외부로 추출할 수도 있음.

  • 클릭과 같은 상호작용으로 인해 Effect가 발생한 것이 아니라면, React는 일반적으로 브라우저가 Effect를 실행하기 전에 업데이트된 화면을 먼저 그리도록 함. Effect가 시각적인 작업(예: 툴팁 위치 지정)을 하고 있는데 지연이 눈에 띄는 경우(예: 깜빡임), useEffect를 useLayoutEffect로 대체해야함.

  • 클릭과 같은 상호작용으로 인해 Effect가 발생했더라도 브라우저는 Effect 내부의 state 업데이트를 처리하기 전에 화면을 다시 그릴 수 있음. 일반적으로 이는 사용자가 원하는 것. 그러나 브라우저가 화면을 다시 그리지 못하도록 차단해야 하는 경우 useEffect를 useLayoutEffect로 대체해야함.

  • Effect는 클라이언트에서만 실행됨. 서버 렌더링 중에는 실행되지 않음.


Useage

Connecting to an external system

일부 컴포넌트는 페이지에 표시되는 동안 네트워크, 일부 브라우저 API 또는 서드파티 라이브러리에 연결된 상태를 유지해야함. 이러한 시스템은 React에 의해 제어되지 않으므로 '외부(external)'라고 함.

컴포넌트를 외부 시스템에 연결하기 위해 컴포넌트의 최상위 수준에서 useEffect를 호출:

import { useEffect } from 'react';
import { createConnection } from './chat.js';

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

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

useEffect에 두 개의 인수를 전달해야 함:

  1. 해당 시스템에 연결하는 setup 코드가 포함된 setup 함수.

    • 해당 시스템과의 연결을 끊는 cleanup 코드가 포함된 cleanup 함수를 반환해야함.
  2. 해당 함수 내부에서 사용되는 컴포넌트의 모든 값을 포함한 dependencies 목록.

React는 필요할 때마다 setup 및 cleanup 함수를 호출하며, 이는 여러 번 발생할 수 있음:

  1. 컴포넌트가 페이지에 추가(mount)될 때 setup 코드가 실행됨.
  2. dependencies가 변경된 컴포넌트를 다시 렌더링할 때마다:
    • 먼저, cleanup 코드가 이전 props와 state로 실행됨.
    • 그런 다음 setup 코드가 새 props와 state로 실행됨.
  3. cleanup 코드는 컴포넌트가 페이지에서 제거(unmount)된 후 마지막으로 한 번 실행됨.

위의 예시로 이 순서를 설명하면,

ChatRoom 컴포넌트가 페이지에 추가되면 초기 serverUrlroomId를 사용하여 채팅방에 연결함. 다시 렌더링한 결과 serverUrl 또는 roomId가 변경되면(예: 사용자가 드롭다운에서 다른 채팅방을 선택하는 경우) Effect는 이전 채팅방과의 연결을 끊고 다음 채팅방에 연결함. ChatRoom 컴포넌트가 페이지에서 제거되면 Effect가 마지막으로 연결을 끊음.

버그를 찾는 데 도움을 주기 위해 개발 환경에서 React는 setup 전에 setup 및 cleanup을 한 번 더 실행함. 이는 Effect의 로직이 올바르게 구현되었는지 확인하는 스트레스 테스트임. 이로 인해 눈에 보이는 문제가 발생하면 cleanup 함수에 일부 로직이 누락된 것. Cleanup 함수는 setup 함수가 수행하던 작업을 중지하거나 실행 취소해야함. 사용자는 프로덕션 환경에서 한 번 호출되는 setup과 개발 환경에서 setup → cleanup → setup 과정을 구분할 수 없어야함.

참고: 일반적인 해결 방법

모든 Effect를 독립적인 프로세스로 작성하고 한 번에 하나의 setup/cleanup 주기를 생각할 것. 컴포넌트를 mount, update 또는 unmount하는 것은 상관없어야 함. Cleanup 로직이 setup 로직을 올바르게 '미러링'하면 Effect는 필요한 만큼 자주 setup 및 cleanup을 실행할 수 있음.

Note

Effect를 사용하면 컴포넌트를 외부 시스템(예: 채팅 서비스)과 동기화할 수 있음. 여기서 외부 시스템이란 React가 제어하지 않는 모든 코드를 의미함:

  • setInterval()clearInterval()로 관리되는 타이머.
  • window.addEventListener()window.removeEventListener()를 사용하는 이벤트 구독.
  • animation.start()animation.reset()과 같은 API를 사용하는 타사 애니메이션 라이브러리.

외부 시스템에 연결하지 않는 경우 Effect가 필요하지 않을 수 있음.

Wrapping Effects in custom Hooks

Effect는 "탈출구(escape hatch)"임. "React를 벗어나야 할 때", 그리고 더 나은 내장 솔루션이 없을 때 사용해야함. Effects를 수동으로 작성해야 하는 경우가 자주 발생한다면, 일반적으로 컴포넌트가 의존하는 공통된 동작을 커스텀 Hook으로 추출해야 한다는 신호임.

예를 들어, 이 useChatRoom 커스텀 Hook은 선언적인 API 뒤에 Effect의 로직을 "숨김":

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

그런 다음, 다음과 같이 모든 컴포넌트에서 사용할 수 있음:

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

  useChatRoom({
    roomId: roomId,
    serverUrl: serverUrl
  });
  // ...

또한 React 생태계에는 모든 용도에 맞는 훌륭한 커스텀 Hook이 많이 있음.

참고: 커스텀 Hook으로 Effect를 래핑하기

Controlling a non-React widget

외부 시스템을 컴포넌트의 일부 props나 state와 동기화하고 싶을 때가 있음.

예를 들어, React 없이 작성된 타사 맵 위젯이나 비디오 플레이어 컴포넌트가 있는 경우, Effect를 사용하여 해당 state를 React 컴포넌트의 현재 state와 일치시키는 메서드를 호출할 수 있음. 다음 예제의 Effect는 map-widget.js에 정의된 MapWidget 클래스의 인스턴스를 생성함. Map 컴포넌트의 zoomLevel prop을 변경하면 Effect는 클래스 인스턴스에서 setZoom()을 호출하여 동기화 상태를 유지함:

// Map.js
import { useRef, useEffect } from 'react';
import { MapWidget } from './map-widget.js';

export default function 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: 200, height: 200 }}
      ref={containerRef}
    />
  );
}
// map-widget.js
import 'leaflet/dist/leaflet.css';
import * as L from 'leaflet';

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

이 예제에서는 MapWidget 클래스가 자신에게 전달된 DOM 노드만 관리하기 때문에 cleanup 함수가 필요하지 않음. Map 리액트 컴포넌트가 트리에서 제거된 후, DOM 노드와 MapWidget 클래스 인스턴스는 브라우저 자바스크립트 엔진에 의해 자동으로 garbage-collect됨.

Fetching data with Effects

Effect를 사용하여 컴포넌트의 데이터를 가져올 수 있음. 프레임워크를 사용하는 경우 프레임워크의 데이터 fetching 메커니즘을 사용하는 것이 수동으로 Effect를 작성하는 것보다 훨씬 효율적!

Effect에서 데이터를 수동으로 가져오려는 경우:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);

  useEffect(() => {
    let ignore = false;
    setBio(null);
    fetchBio(person).then(result => {
      if (!ignore) {
        setBio(result);
      }
    });
    return () => {
      ignore = true;
    };
  }, [person]);

  // ...

ignore 변수는 false로 초기화되고 cleanup하는 동안 true으로 설정됨. 이렇게 하면 네트워크 응답이 요청을 보낸 순서와 다른 순서로 도착할 수 있는 'race conditions'가 코드에 발생하지 않음.

async / await 구문을 사용하여 다시 작성할 수도 있지만, 여전히 cleanup 함수를 제공해야함:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);

  useEffect(() => {
    async function startFetching() {
      setBio(null);
      const result = await fetchBio(person);
      if (!ignore) {
        setBio(result);
      }
    }

    let ignore = false;
    startFetching();
    return () => {
      ignore = true;
    }
  }, [person]);

  // ...

Effects에서 직접 데이터를 불러오는 작업을 반복적으로 작성하면 나중에 캐싱 및 서버 렌더링과 같은 최적화를 추가하기가 어려움. 직접 만들거나 커뮤니티에서 유지 관리하는 커스텀 Hook을 사용하는 것이 더 쉬움.

Specifying reactive dependencies

Effect의 dependencies를 "선택"할 수 없다는 점에 유의할 것. Effect의 코드에서 사용하는 모든 반응형 값은 dependencies로 선언해야함. Effect의 dependencies 목록은 주변 코드에 의해 결정됨:

function ChatRoom({ roomId }) { // This is a reactive value
  const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // This is a reactive value too

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId); // This Effect reads these reactive values
    connection.connect();
    return () => connection.disconnect();
  }, [serverUrl, roomId]); // ✅ So you must specify them as dependencies of your Effect
  // ...
}

serverUrl이나 roomId가 변경되면 Effect는 새 값을 사용하여 채팅에 다시 연결함.

반응형 값에는 props와 컴포넌트 내부에서 직접 선언된 모든 변수 및 함수가 포함됨. roomIdserverUrl은 반응형 값이므로 dependencies에서 제거할 수 없음. 이 값을 생략하려고 할 때 linter가 React에 대해 올바르게 구성되어있으면 linter는 이를 수정해야 하는 실수라고 알려줌:

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');
  
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, []); // 🔴 React Hook useEffect has missing dependencies: 'roomId' and 'serverUrl'
  // ...
}

Dependency를 제거하려면 해당 컴포넌트가 dependency가 될 필요가 없다는 것을 linter에게 "증명"해야함. 예를 들어 serverUrl을 컴포넌트 밖으로 이동하여 리액티브하지 않고 리렌더링 시 변경되지 않음을 증명할 수 있음:

const serverUrl = 'https://localhost:1234'; // Not a reactive value anymore

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // ✅ All dependencies declared
  // ...
}

이제 serverUrl은 반응형 값이 아니므로(그리고 다시 렌더링할 때 변경할 수 없으므로) dependency가 될 필요가 없음. Effect의 코드가 반응형 값을 사용하지 않는다면 해당 dependency 목록은 비어 있어야 함([]):

const serverUrl = 'https://localhost:1234'; // Not a reactive value anymore
const roomId = 'music'; // Not a reactive value anymore

function ChatRoom() {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, []); // ✅ All dependencies declared
  // ...
}

빈 dependencies가 있는 Effect는 컴포넌트의 props나 state가 변경되어도 다시 실행되지 않음.

Pitfall

기존 코드베이스가 있는 경우 다음과 같이 lint를 억제하는 Effect가 있을 수 있음:

useEffect(() => {
  // ...
  // 🔴 Avoid suppressing the linter like this:
  // eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

Dependencies가 코드와 일치하지 않으면 버그가 발생할 위험이 높음. Linter를 억제하면 Effect가 의존하는 값에 대해 React에 "거짓말"을 하는 것! 대신 불필요하다는 것을 증명할 것.

Updating state based on previous state from an Effect

Effect의 이전 state를 기반으로 state를 업데이트하려는 경우 문제가 발생할 수 있음:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1); // You want to increment the counter every second...
    }, 1000)
    return () => clearInterval(intervalId);
  }, [count]); // 🚩 ... but specifying `count` as a dependency always resets the interval.
  // ...
}

count는 반응형 값이므로 dependency 목록에 지정해야함. 그러나 이렇게 하면 count가 변경될 때마다 Effect를 cleanup하고 다시 setup해야함. 이는 이상적이지 않음.

이 문제를 해결하려면 c => c + 1 상태 updatersetCount에 전달하면 됨:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c + 1); // ✅ Pass a state updater
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ Now count is not a dependency
  // ...
}

이제 count + 1 대신 c => c + 1을 전달하므로 Effect는 더 이상 count에 의존할 필요가 없음. 따라서 count가 변경될 때마다 interval을 다시 cleanup하고 setup할 필요가 없음.

Removing unnecessary object dependencies

Effect가 렌더링 중에 생성된 객체 또는 함수에 의존하는 경우 너무 자주 실행될 수 있음. 예를 들어, 아래 예제의 Effect는 렌더링할 때마다 option 객체가 달라지기 때문에 렌더링할 때마다 다시 연결됨:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  const options = { // 🚩 This object is created from scratch on every re-render
    serverUrl: serverUrl,
    roomId: roomId
  };

  useEffect(() => {
    const connection = createConnection(options); // It's used inside the Effect
    connection.connect();
    return () => connection.disconnect();
  }, [options]); // 🚩 As a result, these dependencies are always different on a re-render
  
  // ...

렌더링 중에 생성된 객체를 dependency로 사용하지 말 것. 대신 Effect 내에서 객체를 생성할 것:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

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

이제 Effect 내부에 option 객체를 만들었으므로 Effect 자체는 roomId 문자열에만 의존함.

따라서 input에 값을 입력해도 채팅이 다시 연결되지 않음. 다시 만들어지는 객체와 달리, roomId와 같은 문자열은 다른 값으로 설정하지 않는 한 변경되지 않음.

참고: Dependencies 제거

Removing unnecessary function dependencies

Effect가 렌더링 중에 생성된 객체 또는 함수에 의존하는 경우 너무 자주 실행될 수 있음. 예를 들어, 아래 예제의 Effect는 렌더링할 때마다 createOptions 함수가 달라지기 때문에 렌더링할 때마다 다시 연결됨:

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  function createOptions() { // 🚩 This function is created from scratch on every re-render
    return {
      serverUrl: serverUrl,
      roomId: roomId
    };
  }

  useEffect(() => {
    const options = createOptions(); // It's used inside the Effect
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // 🚩 As a result, these dependencies are always different on a re-render
  
  // ...

렌더링할 때마다 함수를 처음부터 새로 만드는 것은 그 자체로는 문제가 되지 않고, 이를 최적화할 필요가 없음. 하지만 이 함수를 Effect의 dependency로 사용하면 렌더링할 때마다 Effect가 다시 실행됨.

렌더링 중에 생성된 함수를 dependency로 사용하지 말 것. 대신 Effect 내부에서 선언할 것:

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() {
      return {
        serverUrl: serverUrl,
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);
  
  // ...

이제 Effect 내부에서 createOptions 함수를 정의했으므로 Effect 자체는 roomId 문자열에만 의존함.

따라서 input에 값을 입력해도 채팅이 다시 연결되지 않음. 다시 만들어지는 함수와 달리, roomId와 같은 문자열은 다른 값으로 설정하지 않는 한 변경되지 않음.

참고: Dependencies 제거

Reading the latest props and state from an Effect

Under Construction

이 섹션에서는 아직 안정된 버전의 React로 출시되지 않은 실험적인 API에 대해 설명함.

기본적으로 Effect에서 반응형 값을 읽을 때는 dependency로 추가해야함. 이렇게 하면 Effect가 해당 값의 모든 변경에 "반응"하도록 할 수 있음. 이는 대부분의 dependencies에 기대하는 동작임.

그러나 때로는 '반응'하지 않고 Effect에서 최신 props와 state를 읽고 싶을 때가 있음. 예를 들어 페이지 방문 시마다 장바구니에 있는 아이템의 수를 로그에 기록한다면:

function Page({ url, shoppingCart }) {
  useEffect(() => {
    logVisit(url, shoppingCart.length);
  }, [url, shoppingCart]); // ✅ All dependencies declared
  // ...
}

url이 변경될 때마다 새 페이지 방문을 기록하되, shoppintCart만 변경되는 경우에는 기록하지 않고 싶다면? 반응성 규칙을 위반하지 않고는 shoppintCart를 dependencies에서 제외할 수 없음. 그러나 코드가 Effect 내부에서 호출되더라도 변경 사항에 '반응'하지 않도록 표현할 수 있음. useEffectEvent Hook을 사용하여 Effect Event를 선언하고 그 안으로 shoppintCart를 읽는 코드를 이동하면 됨:

function Page({ url, shoppingCart }) {
  const onVisit = useEffectEvent(visitedUrl => {
    logVisit(visitedUrl, shoppingCart.length)
  });

  useEffect(() => {
    onVisit(url);
  }, [url]); // ✅ All dependencies declared
  // ...
}

Effect Event는 반응형이 아니므로 Effect의 dependencies에서 항상 생략해야함. 이를 통해 비반응형 코드(일부 props 및 state의 최신 값을 읽을 수 있는 코드)를 그 안에 넣을 수 있음. onVisit 내부에서 shoppingCart를 읽으면 shoppingCart가 Effect를 다시 실행하지 않도록 할 수 있음.

참고: Effect Event를 통해 반응형 코드와 비반응형 코드를 분리하는 방법

Displaying different content on the server and the client

앱에서 서버 렌더링을 사용하는 경우(직접 또는 프레임워크를 통해) 컴포넌트는 두 개의 다른 환경에서 렌더링됨. 서버에서는 초기 HTML을 생성하기 위해 렌더링되고, 클라이언트에서는 React가 렌더링 코드를 다시 실행하여 이벤트 핸들러를 해당 HTML에 첨부할 수 있음. 따라서 hydration이 작동하려면 초기 렌더링 결과가 클라이언트와 서버에서 동일해야함.

드물지만 클라이언트에 다른 콘텐츠를 표시해야 하는 경우가 있을 수 있음. 예를 들어, 앱이 localStorage에서 일부 데이터를 읽는 경우 서버에서는 이를 수행할 수 없음. 이를 구현하는 방법은 다음과 같음:

function MyComponent() {
  const [didMount, setDidMount] = useState(false);

  useEffect(() => {
    setDidMount(true);
  }, []);

  if (didMount) {
    // ... return client-only JSX ...
  }  else {
    // ... return initial JSX ...
  }
}

앱이 로드되는 동안 사용자는 초기 렌더링 결과를 볼 수 있음. 그런 다음, 앱이 로드되고 hydrate되면 Effect가 실행되고 didMounttrue로 설정하여 재렌더링을 트리거함. 그러면 클라이언트 전용 렌더링 결과로 전환됨. Effect는 서버에서 실행되지 않으므로 초기 서버 렌더링 중에는 didMountfalse임.

이 패턴은 신중히 사용해야함. 연결 속도가 느린 사용자는 초기 콘텐츠를 꽤 오랜 시간(몇 초) 동안 보게 되므로 컴포넌트의 모양을 갑작스럽게 변경하는 것은 좋지 않음. 대부분의 경우 CSS를 사용하여 조건부로 다른 내용을 표시함으로써 이러한 코드를 대체할 수 있음.

0개의 댓글