React-dnd: ์™ธ๋ถ€์—์„œ iframe์œผ๋กœ ๋“œ๋ž˜๊ทธํ•˜๋Š” ๋ฐฉ๋ฒ•

์— ๋งŒ๋“  2019๋…„ 08์›” 01์ผ  ยท  6์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: react-dnd/react-dnd

๊ธฐ๋Šฅ ์š”์ฒญ์ด ๋ฌธ์ œ์™€ ๊ด€๋ จ๋˜์–ด ์žˆ์Šต๋‹ˆ๊นŒ?
React๋Š” ๋‹จ์ผ HTML ๋ฌธ์„œ ๋‚ด์—์„œ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์„ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ป๊ฒŒ๋“  ์™ธ๋ถ€์—์„œ iframe ๋‚ด๋ถ€๋กœ ๋“œ๋ž˜๊ทธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์›ํ•˜๋Š” ์†”๋ฃจ์…˜ ์„ค๋ช…
dnd HTML5backend๊ฐ€ ์ฐฝ์—์„œ ๋“œ๋ž˜๊ทธ ์ฝœ๋ฐฑ์„ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•˜์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ iframe ๋‚ด๋ถ€์˜ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ํ•ด๋‹น ์ฝœ๋ฐฑ์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด React.createPortal iframe ๋‚ด๋ถ€์˜ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์™ธ๋ถ€์™€ ๋™์ผํ•œ DndProvider๋ฅผ ๊ฐ–๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์šด ์ข‹๊ฒŒ๋„ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋‚ด๊ฐ€ ๋ˆˆ์น˜์ฑ„์ง€ ๋ชปํ•œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ๊นŒ๋ด ๊ฑฑ์ •๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  HTML5Backend์—์„œ๋Š” ์ฝ”๋“œ๊ฐ€ ๊ทธ๋ ‡๊ฒŒ ํŽธ๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

import React, {
  useRef,
  useEffect,
  useContext,
  forwardRef,
} from 'react';
import classnames from 'classnames';
import { DndContext } from 'react-dnd';

// Frame base on createPortal inside iframe dom.
import Frame from 'react-frame-component';

const events = [
  ['dragstart', 'handleTopDragStart', false],
  ['dragstart', 'handleTopDragStartCapture', true],
  ['dragend', 'handleTopDragEndCapture', true],
  ['dragenter', 'handleTopDragEnter', false],
  ['dragenter', 'handleTopDragEnterCapture', true],
  ['dragleave', 'handleTopDragLeaveCapture', true],
  ['dragover', 'handleTopDragOver', false],
  ['dragover', 'handleTopDragOverCapture', true],
  ['drop', 'handleTopDrop', false],
  ['drop', 'handleTopDropCapture', true],
];

export const DndFrame = forwardRef((props = {}, ref) => {
  const container = useRef(null);
  const dndValue = useContext(DndContext) || {};

  const { dragDropManager: { backend = {} } = {} } = dndValue;
  const { children, className, containerProps = {}, ...others } = props;

  const cls = classnames({
    'dnd-frame': true,
    [className]: !!className,
  });

  useEffect(() => {
    const { current } = container;

    if (!current) {
      return;
    }

    // this make callback run in outside window
    events.forEach((eventArgs = []) => {
      const [name, callbackName, capture = false] = eventArgs;

      const callback = backend[callbackName] && backend[callbackName].bind(backend);

      current.addEventListener(name, (...args) => {
        callback && callback(...args);
      }, capture);
    });
  }, [container.current]);

  return (
    <Frame className={cls} ref={ref} {...others}>
      <div className="dnd-frame-container" ref={container} {...containerProps}>
        { children }
      </div>
    </Frame>
  );
});

๊ณ ๋ คํ•œ ๋Œ€์•ˆ์„ ๊ธฐ์ˆ ํ•˜์‹ญ์‹œ์˜ค.
HTML5Backend๋กœ ์ผ๋ถ€ ์˜ต์…˜์„ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

design decisions enhancement pinned

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

์ด ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๋Š” ๋” ์‰ฌ์šด ์†”๋ฃจ์…˜์„ ์ฐพ์•˜์ง€๋งŒ ์ด๊ฒƒ์ด ๋” ๋‚˜์€ ์†”๋ฃจ์…˜์ธ์ง€ ํ™•์‹ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

import React, { useContext, useEffect } from 'react';
import DndProvider, { DndContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Frame, { FrameContext } from 'react-frame-component';

const DndFrame = ({ children }) => {
  const { dragDropManager } = useContext(DndContext);
  const { window } = useContext(FrameContext);

  useEffect(() => {
    dragDropManager.getBackend().addEventListeners(window);
  });

  return children;
};

const Example = () => (
  <DndProvider backend={HTML5Backend}>
    <Frame>
       <DndFrame>
          <div>...</div>
       </DndFrame>
    </Frame>
  </DndProvider>
);

๋ชจ๋“  6 ๋Œ“๊ธ€

์ด ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๋Š” ๋” ์‰ฌ์šด ์†”๋ฃจ์…˜์„ ์ฐพ์•˜์ง€๋งŒ ์ด๊ฒƒ์ด ๋” ๋‚˜์€ ์†”๋ฃจ์…˜์ธ์ง€ ํ™•์‹ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

import React, { useContext, useEffect } from 'react';
import DndProvider, { DndContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Frame, { FrameContext } from 'react-frame-component';

const DndFrame = ({ children }) => {
  const { dragDropManager } = useContext(DndContext);
  const { window } = useContext(FrameContext);

  useEffect(() => {
    dragDropManager.getBackend().addEventListeners(window);
  });

  return children;
};

const Example = () => (
  <DndProvider backend={HTML5Backend}>
    <Frame>
       <DndFrame>
          <div>...</div>
       </DndFrame>
    </Frame>
  </DndProvider>
);

@HsuTing ๋” ์‰ฝ๊ณ  ์ข‹์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์—ฌ์ „ํžˆ HTML5Backend ์ธํ„ฐํŽ˜์ด์Šค์— ๋ฐ”์ธ๋”ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. HTML5Backend๊ฐ€ addEventListeners => addListeners๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ์›น์ด ๊ณ ์žฅ๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ •๋ง ๋ฉ‹์ง„ ์•„์ด๋””์–ด์ž…๋‹ˆ๋‹ค. ํ•˜์ดํ‚น์—์„œ ๋Œ์•„์˜ฌ ๋•Œ ๊ฐ€์ง€๊ณ  ๋†€๊ฒ ์Šต๋‹ˆ๋‹ค.

2019๋…„ 8์›” 6์ผ ํ™”์š”์ผ ์˜คํ›„ 11:43 ๆ™“็ˆฝ[email protected] ์ž‘์„ฑ:

@HsuTing https://github.com/HsuTing ๋” ์‰ฝ๊ณ  ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ
HTML5Backend ์ธํ„ฐํŽ˜์ด์Šค์— ๋ฐ”์ธ๋”ฉ๋ฉ๋‹ˆ๋‹ค. HTML5Backend๊ฐ€ addEventListeners๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒฝ์šฐ
=> addListeners, ์šฐ๋ฆฌ์˜ ์›น์€ ๊ณ ์žฅ๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค

โ€”
๋‹น์‹ ์ด ํ• ๋‹น๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ณ  GitHub์—์„œ ํ™•์ธํ•˜์„ธ์š”.
https://github.com/react-dnd/react-dnd/issues/1496?email_source=notifications&email_token=AAA3XCDZJS3V5E7YISDRUC3QDJVJLA5CNFSM4IIMWOQ2YY3PNVWWK3TUL52HS4DFVEXG43VMVB
๋˜๋Š” ์Šค๋ ˆ๋“œ๋ฅผ ์Œ์†Œ๊ฑฐ
https://github.com/notifications/unsubscribe-auth/AAA3XCGHNHO3M3CSL5VRUQTQDJVJLANCNFSM4IIMWOQQ
.

์ด ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๋Š” ๋” ์‰ฌ์šด ์†”๋ฃจ์…˜์„ ์ฐพ์•˜์ง€๋งŒ ์ด๊ฒƒ์ด ๋” ๋‚˜์€ ์†”๋ฃจ์…˜์ธ์ง€ ํ™•์‹ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

import React, { useContext, useEffect } from 'react';
import DndProvider, { DndContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Frame, { FrameContext } from 'react-frame-component';

const DndFrame = ({ children }) => {
  const { dragDropManager } = useContext(DndContext);
  const { window } = useContext(FrameContext);

  useEffect(() => {
    dragDropManager.getBackend().addEventListeners(window);
  });

  return children;
};

const Example = () => (
  <DndProvider backend={HTML5Backend}>
    <Frame>
       <DndFrame>
          <div>...</div>
       </DndFrame>
    </Frame>
  </DndProvider>
);

๊ทธ๊ฒƒ์€ ํ›Œ๋ฅญํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์ด๋ฉฐ ์ €์—๊ฒŒ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.

์ด ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๋Š” ๋” ์‰ฌ์šด ์†”๋ฃจ์…˜์„ ์ฐพ์•˜์ง€๋งŒ ์ด๊ฒƒ์ด ๋” ๋‚˜์€ ์†”๋ฃจ์…˜์ธ์ง€ ํ™•์‹ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

import React, { useContext, useEffect } from 'react';
import DndProvider, { DndContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Frame, { FrameContext } from 'react-frame-component';

const DndFrame = ({ children }) => {
  const { dragDropManager } = useContext(DndContext);
  const { window } = useContext(FrameContext);

  useEffect(() => {
    dragDropManager.getBackend().addEventListeners(window);
  });

  return children;
};

const Example = () => (
  <DndProvider backend={HTML5Backend}>
    <Frame>
       <DndFrame>
          <div>...</div>
       </DndFrame>
    </Frame>
  </DndProvider>
);

typescript๋กœ ์‹œ๋„ํ•  ๋•Œ Property 'addEventListeners' does not exist on type 'Backend' ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

TS์—์„œ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๊นŒ?

addEventListeners๋Š” ๋ฐฑ์—”๋“œ ์ธํ„ฐํŽ˜์ด์Šค์— ๋…ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. DOM/๋ธŒ๋ผ์šฐ์ €์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๋…ธ์ถœํ•˜๊ณ  ์‹ถ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ํ•ด์•ผ ํ•  ์ผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

// detect if SSR mode
const DEFAULT_GLOBAL_CONTEXT = typeof window !== 'undefined' ? window : global

const DndFrame = ({ children, globalContext = DEFAULT_GLOBAL_CONTEXT}) => {
  const { dragDropManager } = useContext(DndContext);
  const { window } = useContext(FrameContext);
  const backend = useMemo(() => dragDropManager.getBackend(), [dragDropManager])

  useEffect(() => {
     // This will required adding an initialize() method to the Backend interface.
    // Backend constructors will have to thunk over to it. It could replace constructors, but that would be semver major.
    backend.initialize(dragDropManager, globalContext)
    backend.setup()
  }, [globalContext]);

  return children;
};

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰