개요
모델 기반 에디터를 프레임워크와 통합하려면 라이프사이클, 상태 관리 및 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 />;
}