(React) TOAST UI Editor로 이미지가 포함된 게시글 작성하기

개발차 DevCHA·2023년 7월 21일
2

지난 프로젝트에서는 게시글 작성에 react-quill 에디터를 사용했었는데, 최근 TOAST UI Editor의 존재를 알게 되어 새로운 프로젝트에 적용해 보았다. 이번 포스팅에는 TOAST UI Editor(이하 Tui Editor)의 간단한 사용 방법과 이미지 핸들링 방식, react-quill과 비교했을 때 어떤 차이점이 있는지 다뤄보려고 한다.


설치

리액트 환경에서 작업을 진행 중이기 때문에 Tui Editor에 react 래퍼가 씌워진 @toast-ui/react-editor를 설치한다. 패키지 매니저로 yarn을 사용하고 있으므로, yarn을 기준으로 작성했다.

yarn add @toast-ui/react-editor

모듈 작성

import { Editor } from '@toast-ui/react-editor';
import '@toast-ui/editor/toastui-editor.css';

type Props = {
  editorRef: React.RefObject<Editor> | null;
  imageHandler: (blob: File, callback: typeof Function) => void;
  content?: string;
};

const toolbar = [['heading', 'bold', 'italic', 'strike'], ['hr', 'quote', 'ul', 'ol'], ['image']];

function TuiEditor({ content, editorRef, imageHandler }: Props) {
  return (
    <Editor
      initialValue={content ?? ' '}
      initialEditType='wysiwyg'
      autofocus={false}
      ref={editorRef}
      toolbarItems={toolbar}
      hideModeSwitch
      height='500px'
      hooks={{ addImageBlobHook: imageHandler }}
    />
  );
}

export default TuiEditor;

먼저 @toast-ui/react-editor 에서 제공하는 Editor와 css 파일을 임포트해서 사용한다.
Editor는 다양한 옵션을 받는데, 전체 목록은 여기서 볼 수 있다. 이번 프로젝트에 적용한 옵션들을 간략히 소개하자면 아래와 같다.


옵션명타입설명
initialValuestring에디터의 초기값. 수정 기능 구현시 유용
initialEditTypestring에디터 초기 타입 설정. markdown, wysiwyg 중 선택 가능
autofocusboolean = truetrue일 경우 자동으로 에디터에 포커스 설정
toolbarItemsarray툴바 아이템
hideModeSwitchboolean = falsemarkdown / wysiwyg 스위치 탭 숨김
heightstring = '300px'에디터의 높이 값. ex) '300px', '100%', 'auto'
hooksObjectaddImageBlobHook - 이미지 업로드에 사용되는 훅 설정

툴바 아이템

Tui Editor의 툴바 설정 방법은 react-quill 에디터와 비슷했다. 사용 가능한 옵션은 아래와 같다.

  [
    ['heading', 'bold', 'italic', 'strike'],
    ['hr', 'quote'],
    ['ul', 'ol', 'task', 'indent', 'outdent'],
    ['table', 'image', 'link'],
    ['code', 'codeblock'],
    ['scrollSync'],
  ]

옵션은 배열 안의 배열로 구성되며, 쉼표로 구분된 배열들은 하나의 묶음이 되어 툴바에서 | 를 사이에 두고 분리된다.


이미지 핸들러

에디터에 전달 가능한 옵션 중 hooks는 객체를 전달받는데, 객체의 addImageBlobHook 프로퍼티의 값으로 함수를 넘기면 이미지 첨부시 해당 함수가 실행된다. (파일 첨부 후 OK 버튼을 누른 시점에 addImageBlobHook 함수가 실행됨)

addImageBlobHook 함수는 첫 번째 인자로 첨부된 파일을 받고, 두 번째 인자로 콜백 함수를 받는다.
보통 프로젝트에서 이미지를 관리할 때, 이미지를 서버로 보낸 뒤 url을 받아 사용하기 때문에 이미지 핸들러 함수에서는 첨부된 File을 서버로 보내 url을 받고, 두 번째 인자인 콜백 함수에 url을 전달하면 된다.


  const handleImage = async (file: File, callback: typeof Function) => {
    const imageUrl = await getImageUrl(file);
    callback(imageUrl);
  };

return (
  ...
  	<>
		<TuiEditor editorRef={editorRef} imageHandler={handleImage} /> // 이미지 핸들러 전달
		<button onClick={getContents}>에디터 내용 출력하기</button>
	<>
  ...
)

react-quill에서 이미지 첨부 기능을 구현할 때는 가상의 input 요소를 만들고 커서 위치를 찾아 이미지를 삽입하는 등 DOM 조작 과정을 직접 구현해야 했지만, Tui Editor는 콜백 함수에 이미지 url을 넘겨주기만 하면 에디터에서 직접 이미지를 삽입해 준다. (정말 편하다)


작성 내용 불러오기

Tui Editor에서 작성한 내용을 불러오는 방법은 getHTML(), getMarkdown() 으로 총 두 가지가 있다. 나는 Editor에 ref를 설정하고, 버튼 클릭시 내용을 받아오는 방식으로 구현했다. 간단하게 console에 에디터에 작성된 내용을 출력하도록 작성해 보자.


WritePage.tsx

import type { Editor } from '@toast-ui/react-editor';

const editorRef = useRef<Editor>(null);

const getContents = () => {
  const markdownContent = editorRef.current?.getInstance().getMarkdown();
  const htmlContent = editorRef.current?.getInstance().getHTML();
  
  console.log(markdownContent, htmlContent)
}

return (
  ...
  	<>
		<TuiEditor editorRef={editorRef} />
		<button onClick={getContents}>에디터 내용 출력하기</button>
	<>
  ...
)

같은 내용을 작성하고, 각각 markdown과 HTML로 출력한 모습이다.


markdown

# 토스트 UI Editor로 이미지가 포함된 게시글 작성하기!

***마크다운***으로 불러온 데이터와 ***HTML***로 불러온 데이터를 비교해 보세요.

HTML

<h1>토스트 UI Editor로 이미지가 포함된 게시글 작성하기!</h1><p><strong><em>마크다운</em></strong>으로 불러온 데이터와 <strong><em>HTML</em></strong>로 불러온 데이터를 비교해 보세요.</p><p><br></p>

뷰어

Tui Editor의 가장 큰 강점 중 하나는 뷰어를 제공한다는 것이다. react-quill을 사용할 때처럼 dangerouslyPasteHTML 이나 dangerouslySetInnerHTML 을 쓸 필요가 없다. 단지 뷰어에 markdown string 혹은 html string을 전달하기만 하면 에디터로 작성된 내용을 보여줄 수 있다!

import { Viewer } from '@toast-ui/react-editor';
import '@toast-ui/editor/dist/toastui-editor-viewer.css';

type Props = {
  content: string;
  style?: string;
};

function TuiViewer({ content, style }: Props) {
  return (
    <section className={style}>
      <Viewer initialValue={content} />
    </section>
  );
}

export default TuiViewer;

사용 방법도 아주 간단하다. @toast-ui/react-editor 에서 Viewer, css 파일을 임포트하고 initialValue에 렌더링할 내용을 넘겨주기만 하면 된다. 나는 스타일을 커스텀하기 위해 section 태그로 래핑했다.


기본 스타일 수정

뷰어나 에디터에 기본적으로 적용되어 있는 스타일이 마음에 들지 않을 수 있다. h1을 선택하면 밑줄이 두 줄 생긴다거나, 기본 폰트 크기가 14px이라 너무 작다거나...

만약 에디터에서 제공하는 기본 스타일을 수정하고 싶다면, Tui Editor/Viewer는 모두 toastui-editor-contents 클래스가 적용된 div 안에 위치하게 되므로 클래스를 이용하여 커스터마이징 하면 된다.

.toastui-editor-contents p,
.toastui-editor-contents h6 {
  font-size: 1rem !important;
}

Tui Editor 총평

Tui Editor를 사용하면서 react-quill 에디터와는 비교할 수 없을 정도의 편리함을 경험했다. 뷰어 제공, 이미지 핸들링 로직 추상화 등 이번 글에서 다룬 장점도 있지만 마크다운과 위지위그의 자유로운 전환, 툴바 아이템 커스터마이징 등 글에서 다루지 못한 좋은 기능도 많다.

그리고 무엇보다 국산 오픈소스 라이브러리이기 때문에 더욱 정이 간다! 공식 문서가 보기 힘들다는 점을 제외하면 거의 단점을 찾지 못해서, 다음 프로젝트에 에디터를 사용할 일이 있다면 다시 Tui Editor를 찾게 될 것 같다.

0개의 댓글