Framework Integration

React, Vue, Svelte 및 기타 프레임워크와 모델 기반 contenteditable 에디터를 통합하는 방법입니다.

개요

모델 기반 에디터를 프레임워크와 통합하려면 라이프사이클, 상태 관리 및 DOM 업데이트를 신중하게 처리해야 합니다. 핵심은 프레임워크 상태와 통합하면서 에디터가 자체 DOM을 관리하도록 하는 것입니다.

핵심 원칙:

  • 에디터가 자체 DOM 관리 - 프레임워크가 재렌더링하지 않도록
  • ref를 사용하여 에디터 인스턴스 접근
  • 필요 시 에디터 상태를 프레임워크 상태와 동기화
  • 라이프사이클 적절히 처리 - 초기화 및 정리
  • 프레임워크가 에디터 DOM을 diff하지 않도록 방지

React 통합

React 통합은 ref 사용과 React가 에디터 DOM을 재렌더링하지 않도록 방지하는 것이 필요합니다:

기본 통합

import { useEffect, useRef, useState } from 'react';
import { Editor } from './editor';

function EditorComponent() {
  const editorRef = useRef<HTMLDivElement>(null);
  const editorInstanceRef = useRef<Editor | null>(null);
  const [content, setContent] = useState('');

  useEffect(() => {
    if (!editorRef.current) return;

    // 에디터 초기화
    const editor = new Editor({
      element: editorRef.current,
      initialContent: content,
    });

    editorInstanceRef.current = editor;

    // 변경 사항 듣기
    editor.on('change', (newContent) => {
      setContent(newContent);
    });

    // 정리
    return () => {
      editor.destroy();
    };
  }, []); // 한 번만 실행

  return (
    <div
      ref={editorRef}
      contentEditable={false} // 에디터가 contenteditable 관리
      suppressContentEditableWarning // React 경고 억제
    />
  );
}

Custom Hook

import { useEffect, useRef, useState, useCallback } from 'react';
import { Editor } from './editor';

function useEditor(initialContent: string = '') {
  const editorRef = useRef<HTMLDivElement>(null);
  const editorInstanceRef = useRef<Editor | null>(null);
  const [content, setContent] = useState(initialContent);
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    if (!editorRef.current) return;

    const editor = new Editor({
      element: editorRef.current,
      initialContent,
    });

    editorInstanceRef.current = editor;
    setIsReady(true);

    editor.on('change', (newContent) => {
      setContent(newContent);
    });

    return () => {
      editor.destroy();
      setIsReady(false);
    };
  }, []);

  return {
    editorRef,
    content,
    isReady,
    editor: editorInstanceRef.current,
  };
}

Vue 통합

Vue 통합은 템플릿 ref와 라이프사이클 훅을 사용합니다:

Composition API

<template>
  <div ref="editorRef" />
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { Editor } from './editor';

const editorRef = ref<HTMLElement | null>(null);
const editorInstance = ref<Editor | null>(null);
const content = ref('');

onMounted(() => {
  if (!editorRef.value) return;

  const editor = new Editor({
    element: editorRef.value,
    initialContent: content.value,
  });

  editorInstance.value = editor;

  editor.on('change', (newContent) => {
    content.value = newContent;
  });
});

onUnmounted(() => {
  editorInstance.value?.destroy();
});
</script>

Svelte 통합

Svelte 통합은 bind:this와 라이프사이클 함수를 사용합니다:

<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import { Editor } from './editor';

  let editorElement: HTMLDivElement;
  let editorInstance: Editor | null = null;
  let content = '';

  onMount(() => {
    if (!editorElement) return;

    editorInstance = new Editor({
      element: editorElement,
      initialContent: content,
    });

    editorInstance.on('change', (newContent) => {
      content = newContent;
    });
  });

  onDestroy(() => {
    editorInstance?.destroy();
  });
</script>

<div bind:this={editorElement} />

공통 패턴

프레임워크 간 공통 패턴:

프레임워크 DOM Diff 방지

// React: key 사용하여 재렌더링 방지
<div key="editor-root" ref={editorRef} suppressContentEditableWarning />

// Vue: key 사용
<div :key="'editor'" ref="editorRef" />

// Vue: v-once 사용 (한 번만 렌더링)
<div v-once ref="editorRef" />

// Svelte: key 사용
<div key="editor" bind:this={editorElement} />

상태 관리

상태 관리 라이브러리와 통합:

Redux 통합

import { useEffect, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Editor } from './editor';
import { setEditorContent, selectEditorContent } from './editorSlice';

function ReduxEditor() {
  const editorRef = useRef<HTMLDivElement>(null);
  const editorInstanceRef = useRef<Editor | null>(null);
  const dispatch = useDispatch();
  const content = useSelector(selectEditorContent);

  useEffect(() => {
    if (!editorRef.current) return;

    const editor = new Editor({
      element: editorRef.current,
      initialContent: content,
    });

    editorInstanceRef.current = editor;

    editor.on('change', (newContent) => {
      dispatch(setEditorContent(newContent));
    });

    return () => editor.destroy();
  }, []);

  return <div ref={editorRef} suppressContentEditableWarning />;
}