๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป dev

polling mechanism์œผ๋กœ react ref ํ™•์ธํ•˜๊ธฐ

ํ”„๋กœ์ ํŠธ ๋งˆ๋ฌด๋ฆฌ๋ฅผ ํ•˜๊ณ  ์žˆ๋˜ ์ค‘, ์ œ๋Œ€๋กœ ๋ชจ๋‘ ๋Œ์•„๊ฐ€๊ณ  ์žˆ๋‚˜ ํ™•์ธํ•˜๋‹ค๊ฐ€
์ž๋™ ํšŒ์ „ํ•˜๊ณ  ์žˆ๋˜ ์ปดํฌ๋„ŒํŠธ์ธ Globe์˜ ๋™์ž‘์ด ์ด์ƒํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ–ˆ๋‹ค.
์˜ค๋Š˜์€ ๊ฐ„๋‹จํ•˜๊ฒŒ useRef์™€ useEffect์œผ๋กœ threejs ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€
reload/refresh ๋˜์–ด๋„ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋„๋ก polling์„ ํ™œ์šฉํ•œ ๊ฒƒ์„ ๋‹ค๋ฃจ๊ณ ์ž ํ•œ๋‹ค.
๋ฆฌ๋กœ๋“œํ•˜๋ฉด ๋Œ์•„๊ฐ€์•ผํ•˜๋Š” ์ง€๊ตฌ๋ณธ

์ด์ „ ์ฝ”๋“œ

'use client';

import classNames from 'classnames/bind';
import COLORS from 'constants/colors';
import dynamic from 'next/dynamic';
import { useEffect, useRef } from 'react';

import { GlobeMethods } from 'react-globe.gl';
import Countries from './countries.json';
import Style from './globe.module.scss';

const cx = classNames.bind(Style);

// https://github.com/vasturiano/react-globe.gl/issues/1#issuecomment-554459831
// https://github.com/vasturiano/react-globe.gl/issues/1#issuecomment-1710898408
const Globe = dynamic(() => import('react-globe.gl').then(mod => mod.default), {
  ssr: false,
});

const RotatingGlobe = () => {
  const globeRef = useRef<GlobeMethods>();

  useEffect(() => {
    const globe = globeRef.current;
    if (globe != null) {
      globe.controls().autoRotate = true;
      globe.controls().autoRotateSpeed = 2.5;
      globe.controls().enableZoom = false;
    }
  }, []);

  return (
    <div className={cx('globe')}>
      <Globe
        height={500}
        ref={globeRef}
        backgroundColor={COLORS['100000']}
        globeImageUrl="/earth-dark.jpeg"
        hexPolygonsData={Countries.features}
        hexPolygonColor={() => COLORS[50]}
        atmosphereColor={COLORS[50]}
      />
    </div>
  );
};

export default RotatingGlobe;

๋ฌธ์ œ

๋‹ค์Œ๊ณผ ๊ฐ™์ด ref ์ƒํƒœ๋ฅผ ํ™•์ธํ•ด ๋ณด๋‹ˆ undefined์˜€๋‹ค.

  useEffect(() => {
    console.log('Globe component mounted');
    console.log('Globe', globeRef);
    return () => {
      console.log('Globe component unmounted');
    };
  }, []);

 

useEffect๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ref๋ฅผ ์ž˜ load ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด 100ms setInterval์„ ์‚ฌ์šฉํ•ด ๋ณด์•˜๋‹ค.

์ด๋ ‡๊ฒŒ ์ปดํ“จํ„ฐ๋‚˜ ์ปจํŠธ๋กค ์žฅ์น˜๊ฐ€  ์™ธ๋ถ€์˜ ์žฅ์น˜๊ฐ€ ์ค€๋น„๋‚˜ ์ƒํƒœ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” ๋ฐฉ์‹์„ polling์ด๋ผ๊ณ  ํ•œ๋‹ค. 

ํ•ด๋‹น ์ฝ”๋“œ๋Š” window์˜ setInterval ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ref์˜ ์ค€๋น„ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

  // Globe ๊ฐ€ ์ถฉ๋ถ„ํžˆ ๋กœ๋“œ๋ ๋•Œ๊นŒ์ง€ ์ดˆ๊ธฐํ™” ๋Œ€๊ธฐ
  // React ref ๊ฐ€ ์•„์ง undefined์ธ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Œ. globeRef ๊ฐ€ ์ค€๋น„ ๋˜๊ธฐ ์ „ ํ™•์ธํ•˜๊ธฐ
  useEffect(() => {
    const interval = setInterval(() => {
      if (globeRef.current) {
        const globe = globeRef.current;
        const controls = globe.controls();
        controls.enableZoom = false; // zoom ๋น„ํ™œ์„ฑํ™”
        controls.autoRotate = true; // ์ž๋™ํ™”
        controls.autoRotateSpeed = 2.5;
        clearInterval(interval); // ref๊ฐ€ ์ค€๋น„๋˜๋ฉด polling mechanism ์ค‘๋‹จ
      }
    }, 100); // 100ms ๋งˆ๋‹ค ํ™•์ธ

    return () => clearInterval(interval);
  }, []);

 

100ms ์ธํ„ฐ๋ฒŒ์„ ๊ฐ–๊ณ  ํ™•์ธํ•˜๋ฉฐ globeRef.current๊ฐ€ ์ œ๋Œ€๋กœ load ๋˜๋ฉด threejs ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•ด ์ฃผ๊ณ ,

์ธํ„ฐ๋ฒŒ์„ ์ œ๊ฑฐํ•ด ์ค€๋‹ค. ํด๋ฆฐ์—… ํ•จ์ˆ˜๋กœ RotatingGlobe ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธํ•œ๋‹ค๋ฉด ์ธํ„ฐ๋ฒŒ์„ ์ œ๊ฑฐํ•ด ์ฃผ๋„๋ก ํ–ˆ๋‹ค.

 

์™„์„ฑ์ฝ”๋“œ

๋”๋ณด๊ธฐ
'use client';

import classNames from 'classnames/bind';
import COLORS from 'constants/colors';
import dynamic from 'next/dynamic';
import { Suspense, useEffect, useRef } from 'react';

import { GlobeMethods } from 'react-globe.gl';
import Countries from './countries.json';
import Style from './globe.module.scss';

const cx = classNames.bind(Style);

// https://github.com/vasturiano/react-globe.gl/issues/1#issuecomment-1710898408
const Globe = dynamic(() => import('react-globe.gl').then(mod => mod.default), {
  ssr: false,
});

const RotatingGlobe = () => {
  const globeRef = useRef<GlobeMethods>();

  // Globe ๊ฐ€ ์ถฉ๋ถ„ํžˆ ๋กœ๋“œ๋ ๋•Œ๊นŒ์ง€ ์ดˆ๊ธฐํ™” ๋Œ€๊ธฐ
  // React ref ๊ฐ€ ์•„์ง undefined์ธ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Œ. globeRef ๊ฐ€ ์ค€๋น„ ๋˜๊ธฐ ์ „ ํ™•์ธํ•˜๊ธฐ
  useEffect(() => {
    const interval = setInterval(() => {
      if (globeRef.current) {
        const globe = globeRef.current;
        const controls = globe.controls();
        controls.enableZoom = false; // zoom ๋น„ํ™œ์„ฑํ™”
        controls.autoRotate = true; // ์ž๋™ํ™”
        controls.autoRotateSpeed = 2.5;
        clearInterval(interval); // ref๊ฐ€ ์ค€๋น„๋˜๋ฉด polling mechanism ์ค‘๋‹จ
      }
    }, 100); // Check every 100ms

    return () => clearInterval(interval);
  }, []);

  return (
    <div className={cx('globe')}>
      <Suspense fallback={null}>
        <Globe
          height={500}
          ref={globeRef}
          backgroundColor={COLORS['100000']}
          globeImageUrl="/earth-dark.jpeg"
          hexPolygonsData={Countries.features}
          hexPolygonColor={() => COLORS[50]}
          atmosphereColor={COLORS[50]}
        />
      </Suspense>
    </div>
  );
};

export default RotatingGlobe;