๐Ÿ”ฃ Text Editor Library-Quill & ReactQuill

๊น€์ฒ ์ค€ยท2022๋…„ 3์›” 23์ผ
2

REACT

๋ชฉ๋ก ๋ณด๊ธฐ
18/33

Quill & ReactQuill

๋ธ”๋กœ๊ทธ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค๋ณด๋‹ˆ Text-editor library๊ฐ€ ํ•„์š”ํ•˜์—ฌ Quill ๊ณผ ReactQuill Library๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์•˜๋‹ค.

Quill๊ณผ ReactQuill์€ ๋‘˜ ๋‹ค text๋ฅผ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” library์ด๋‹ค.

๋ธ”๋กœ๊ทธ ํฌ์ŠคํŒ…์„ ์ž‘์„ฑํ•  ๋•Œ ์œ„์ฒ˜๋Ÿผ ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ text๋ฅผ ์œ„์™€ ๊ฐ™์ด UI๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ text ๋ณ€ํ™˜์„ ํ•˜๋Š” ๊ฒƒ์ด ํ•„์š”ํ•œ๋ฐ ์ด์™€ ๊ฐ™์€ UI๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” Library๊ฐ€ Quill์ธ ๊ฒƒ์ด๋‹ค.

๋‚˜๋Š” React๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๊ณ  ์žˆ๋Š”๋ฐ ๋‘˜ ์ค‘์— ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ณ ๋ฅด์ž๋ฉด ๊ฒฐ๋ก ์ ์œผ๋กœ๋Š” Quill๊ณผ ReactQuill ์ค‘์— ReactQuill ์‚ฌ์šฉํ•˜๊ธฐ์— ํ›จ์”ฌ ํŽธํ–ˆ๋‹ค.

์ด์— ๋Œ€ํ•œ ์ด์œ ๋Š” ์‚ฌ์šฉ๋ฒ•์„ ๋ณด๋ฉด ๊ทธ ์ด์œ ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

Quill

npm i quill

quill์€ ์œ„์™€ ๊ฐ™์ด npm ์„ค์น˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•˜๋ฉด ๋œ๋‹ค.

  • Quill์ด๋ผ๋Š” ์ƒ์„ฑ์žํ•จ์ˆ˜์˜ ์ฒซ๋ฒˆ์งธ ์ธ์ž์— quill์„ ์‚ฌ์šฉํ•  UI๋ฅผ ํ• ๋‹นํ•ด์ค€๋‹ค.
    ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ๋Š” quill์˜ต์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • react์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด quill์„ ์‚ฌ์šฉํ•  react ์š”์†Œ๋ฅผ ref๋กœ ์ง€์ •ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ ์ฃผ์„์„ ์ฐธ๊ณ ํ•˜๋ผ.

Vanilla.js with quill

<!-- Include Quill stylesheet -->
<link href="https://cdn.quilljs.com/1.0.0/quill.snow.css" rel="stylesheet" />

<!-- Create the toolbar container -->
<div id="toolbar">
  <button class="ql-bold">Bold</button>
  <button class="ql-italic">Italic</button>
</div>

<!-- Create the editor container -->
<div id="editor"> <!-- quill์„ ์‚ฌ์šฉํ•  ์š”์†Œ-->
  <p>Hello World!</p>
</div>

<!-- Include the Quill library -->
<script src="https://cdn.quilljs.com/1.0.0/quill.js"></script>

<!-- Initialize Quill editor -->
<script>
  // editor๋Š” quill contents์˜ instance์ด๋‹ค.
  // ์ฒซ๋ฒˆ์งธ ์ธ์ž๋Š” ์œ„์˜ DOM์š”์†Œ์ด๊ณ  ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” text-editor์˜ ์˜ต์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
  var editor = new Quill("#editor", { 
    modules: { toolbar: "#toolbar" },
    theme: "snow",
  });
</script>

์œ„์™€ ๊ฐ™์ด quill์„ ์ ์šฉํ•˜์—ฌ id๊ฐ€ editor์ธ ์š”์†Œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ UI๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.(์ƒ์„ธํ•œ ์˜ต์…˜์€ ๋‹ค๋ฅด๋‹ค. ๊ทธ๋ƒฅ ๋Œ€์ฒด์ ์ธ UI๊ฐ€ ์ด๋ ‡๋‹ค๋Š” ๊ฒƒ์„ ๋งํ•˜๊ธฐ ์œ„ํ•œ ์˜ˆ์‹œ์‚ฌ์ง„์ด๋‹ค.)

React with Quill

์œ„ ์ฝ”๋“œ ์˜ˆ์‹œ๋Š” ์ผ๋ฐ˜ Vanilla์—์„œ ์‚ฌ์šฉํ•  ๋•Œ์˜ ์˜ˆ์‹œ์ด๊ณ  Quill์„ React์‚ฌ์šฉํ•  ๋•Œ์˜ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์šฐ์„  quill์„ ์‚ฌ์šฉํ•  React ์š”์†Œ๋ฅผ ์ฐธ์กฐํ•ด์ค˜์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— useRef()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฐธ์กฐํ•ด์ค€๋‹ค.

๋‹ค์Œ์œผ๋กœ useEffect์•ˆ์—์„œ quill์„ ์„ค์ •ํ•ด์ค€๋‹ค.
(useEffect์•ˆ์—์„œ ์„ค์ •ํ•ด์ฃผ๋Š” ์ด์œ ๋Š” react ์š”์†Œ๋ฅผ ์ฐธ์กฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ Œ๋”๋ง์ด ๋œ ์ดํ›„์— ์ฐธ์กฐํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.)

์œ„์™€ ๊ฐ™์ด ์„ค์ •ํ•ด์ฃผ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ UI๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์ด ๋•Œ ๋งŒ์•ฝ state๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค๋ฉด quill์˜ value๊ฐ’์„ state๋กœ ์„ค์ •ํ•˜์—ฌ onChange๋  ๋•Œ๋งˆ๋‹ค state๊ฐ€ ๋ฐ”๋€Œ๊ฒŒ ์„ค์ •ํ•ด์•ผํ•œ๋‹ค.

ํ•˜์ง€๋งŒ quill์€ ๊ทธ๋ƒฅ text๊ฐ€ ์•„๋‹Œ html์ด๋ฉฐ ์ด๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์ด ๊นŒ๋‹ค๋กญ๋‹ค.
์ด์–ด์„œ ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด์ž.

import Quill from "quill";
import React, { useRef, useEffect, useState } from "react";
import "quill/dist/quill.bubble.css";
const Example = () => {
  const quillElement = useRef();

  const [quillText, setQuillText] = useState("");
  console.log(quillText);
  useEffect(() => {
    const options = {
      theme: "bubble",
      placeholder: "๋‹น์‹ ์˜ ํ•˜๋ฃจ๋ฅผ ๋“ค๋ ค์ฃผ์„ธ์š”...,",
      modules: {
        toolbar: [
          [{ header: "1" }, { header: "2" }],
          ["bold", "italic", "underline", "strike"],
          [{ list: "ordered" }, { list: "bullet" }],
          ["blackquote", "code-block", "link", "image"],
        ],
      },
    };
    const instance = new Quill(quillElement.current, options);

    instance.on("text-change", (delta, oldDelta, source) => {
      if (source === "user") {
        setQuillText(instance.root.innerHTML);
      }
    });
  }, []);

  return (
    <div>
      <div ref={quillElement}></div>
    </div>
  );
};

export default Example;

์„ค์ •ํ•œ quill์— ๋Œ€ํ•œ instance๋ฅผ ์„ค์ •ํ•˜์—ฌ text-change๋ผ๋Š” quill ์ด๋ฒคํŠธ๋ฅผ ์„ค์ •ํ•œ๋‹ค.

์ด ๋•Œ source===user์—์„œ source text๋ฅผ ์ž…๋ ฅํ•˜๋Š” ์ฃผ์ฒด์ด๋‹ค.
๊ทธ๋Ÿฌ๋ฏ€๋กœ ์‚ฌ์šฉ์ž๊ฐ€ text๋ฅผ ๋ณ€ํ™”ํ•˜๋ฉด setQuillText๊ฐ€ ๊ณ„์† ์‹คํ–‰๋œ๋‹ค.

์—ฌ๊ธฐ์„œ setQuillText()์— ํ‰์†Œ์ฒ˜๋Ÿผ e.target.value๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ๊ฐ’์ด ํ• ๋‹น๋œ๋‹ค.

์ด๋Š” ์ด๋ฒคํŠธ๊ฐ€ ์ถ”์ ํ•œ value๊ฐ’ ์ด๋ฒคํŠธ ๊ฐ์ฒด๊ฐ€ ์•„๋‹Œ instance์˜ root๋ผ๋Š” ๊ฐ’์—์„œ ์ถ”์ ๋˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋•Œ๋ฌธ์— ์ƒˆ๋กœ์šด value state๊ฐ’์„ ์„ค์ •ํ•  ๋•Œ setQuillText(instance.root.innerHTML)์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Code Too Long..

ํ•˜๋‚˜์˜ element ์š”์†Œ์— text-editor๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋งŽ์€ ์ฝ”๋“œ๋ฅผ ์ ์šฉํ•ด์•ผํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ReactQuill์„ ์‚ฌ์šฉํ•˜๋ฉด ์ด์— ๋น„ํ•ด ๋งค์šฐ ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
ReactQuill์„ ์‚ดํŽด๋ณด์ž.

ReactQuill

npm i react-quill

import React, { useState } from "react";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.bubble.css";

const ReactQuillEX = () => {
  const [quillText, setQuillText] = useState("");

  return (
    <div>
      <ReactQuill
        theme="bubble"
        value={quillText}
        onChange={(e) => setQuillText(e)}
      />
    </div>
  );
};

export default ReactQuillEX;

์œ„๋Š” react-quill์„ ์‚ฌ์šฉํ•œ ์ฝ”๋“œ์ด๋‹ค.
original quill์„ ์‚ฌ์šฉํ•  ๋•Œ๋ณด๋‹ค ํ›จ์”ฌ ์ฝ”๋“œ๊ฐ€ ์งง์•„์กŒ๋‹ค.

๋”ฐ๋กœ ref๋กœ ์š”์†Œ๋ฅผ ์ง€์ •ํ•  ํ•„์š”์—†์ด ์ปดํฌ๋„ŒํŠธ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๊ณ  ๋•Œ๋ฌธ์— useEffect์•ˆ์—์„œ ์ฒ˜๋ฆฌํ•  ํ•„์š”๋„ ์—†์–ด์กŒ๋‹ค.

๋˜ํ•œ ์ด๋Ÿฐ์ €๋Ÿฐ ์„ค์ •์ฝ”๋“œ๋ฅผ ์„ค์ •ํ•  ํ•„์š”๋„ ์—†๋‹ค.

state๊ฐ’๋„ ์ปดํฌ๋„ŒํŠธ๋‚ด์—์„œ ํ• ๋‹นํ•˜๊ณ  onChange์ด๋ฒคํŠธ๋„ ์ ์šฉํ•˜์—ฌ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•  ๊ฒƒ์€ setQuillText์—์„œ ์„ค์ •ํ•  ๊ฐ’์€ e.target.value๊ฐ€ ์•„๋‹Œ e๋กœ ์„ค์ •ํ•ด์•ผํ•œ๋‹ค. (e๋กœ value๊ฐ’์„ ๋ฐ›๊ธฐ๋•Œ๋ฌธ์ด๋‹ค.)


์ •๋ฆฌ

Quill๊ณผ ReactQuill์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ดค๋Š”๋ฐ ๋ฆฌ์•กํŠธ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค๋ฉด ReactQuill์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ํŽธํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์ด์ฒ˜๋Ÿผ ์–ด๋– ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ณ ๋ฅผ ๋•Œ ์ถฉ๋ถ„ํžˆ ๋„๊ตฌ์— ๋Œ€ํ•œ ํƒ์ƒ‰์ด ์ด๋ค„์ง€๋ฉด ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ ์šฉํ•  ์‹œ๊ฐ„์„ ์•„๋‚„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๊ณ  ์ถ”ํ›„์— ์ƒˆ๋กœ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ ์šฉํ•  ์‹œ์— ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ดํŽด๋ณด๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ ํƒํ•ด์•ผ๊ฒ ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€