우리 프로젝트에는 블로그 형식처럼 음식점에 대한 후기글을 남기는 기능이 있었기 때문에, text editor의 사용은 필수적이였다.
react에 적용되면서 typescript로 사용가능하고 레퍼런스가 많은 라이브러리를 찾아보려고 이틀 간 구글링을 정말 열심히 했던 것 같다! ㅠㅠ 타입스크립트로 작성된 플젝의 레퍼런스는 사막에서 바늘찾기... 난 아직 애기인걸.. 타입스크립트 신생아라서 레퍼런스 없이는 감도 안오는걸? 응애
결론적으로 내가 선택한 라이브러리는 react-quill이라는 라이브러리이다. react-quill 라이브러리는 타입스크립트가 적용될 뿐아니라 이미지 업로드 시 파일 업로더를 사용하는 방식으로 구현되어 있어, 유저가 게시글을 작성할 때 더욱 편리하게 이미지를 추가할 수 있겠다고 느껴 선택하게 되었다.
아래는 react - quill 라이브러리의 공식홈페이지 링크와
https://quilljs.com/playground/
npm 설치 링크이다.
https://www.npmjs.com/package/react-quill
이제 간단하게 적용 방법을 정리해보자.
1. 설치
npm install react-quill
2. index.html에 스타일시트 추가
<link rel="stylesheet" type="text/css" href="lib/css/normalize.css">
3. 에디터 컴포넌트 만들어주기
import React, { useRef, useState, useMemo } from "react";
import ReactQuill, { Quill } from "react-quill";
import "react-quill/dist/quill.snow.css";
import ImageResize from "@looop/quill-image-resize-module-react";
Quill.register("modules/imageResize", ImageResize);
type QuillEditorProps = {
htmlContent: string;
setHtmlContent: (htmlContent: string) => void;
};
const MatEditor = ({ htmlContent, setHtmlContent }: QuillEditorProps) => {
const QuillRef = useRef<ReactQuill>();
/**
* quill에서 사용할 모듈을 설정하는 코드
* useMemo를 사용하지 않으면, 키를 입력할 때마다, imageHandler 때문에 focus가 계속 풀림
*/
const modules = useMemo(
() => ({
toolbar: {
container: [
["bold", "italic", "underline", "strike", "blockquote"],
[{ size: ["small", false, "large", "huge"] }, { color: [] }],
[
{ list: "ordered" },
{ list: "bullet" },
{ indent: "-1" },
{ indent: "+1" },
{ align: [] },
],
["image"],
],
handlers: {
// image: imageHandler,
},
},
}),
[]
);
return (
<ReactQuill
ref={(element) => {
if (element !== null) {
QuillRef.current = element;
}
}}
value={htmlContent}
onChange={setHtmlContent}
modules={modules}
theme="snow"
placeholder="이미지를 한 개 이상 첨부하여 작성해주세요"
style={{ width: "1200px", height: "500px" }}
/>
);
};
export default MatEditor;
여기서 문제가 생겼다! ㅠㅠ 툴바에 있는 bold 기능 및 italic 기능이 제대로 동작하지 않았던 것 ㅠㅠ
결국 이 문제는 사용해주는 컴포넌트에서 css로 따로 설정을 해주었다 ㅜㅜ 왜 안되는지는 아직도 이해가 안되는 부분이다.
참고로 글을 적는 컨테이너 내 div의 heigth도 ReactQuill컴포넌트와 함께 측정되지 않고 아주 자기마음대로 따로 노는 현상이 발생해, 억지로 그 아이의 height도 같이 설정을 해주었다. 넌 또 왜그래??!!
예를들어 ReactQuill 컴포넌트의 높이를 500px로 설정해주면 글을 적는 div 컨테이너의 높이도 갑자기 500px로 설정되어 내가 의도한 것보다 컴포넌트가 길어지는 효과가 있어 의아했다... 왜 포함이 안되고 따로 노는걸까?ㅋㅋㅋㅋㅋ
// 입력 div height
.ql-container.ql-snow {
height: 450px;
}
// bold
.ql-editor p strong {
font-weight: bold;
}
//italic
.ql-editor p em {
font-style: italic;
}
버그인지 뭔지는 모르겠지만 이처럼 따로 설정해주는 방식으로 해결했다.
프리프로젝트때 마크다운 에디터에서도 간간히 툴바 기능이 적용안되는 경우가 있었는데.. 왜 그런걸까?
그냥 에디터도 힘든데 타입스크립트로 적용해보는 에디터라니... 뭐하나 쉽지가 않고만 ?!
이제 이미지 업로드 관련 처리를 해주자. 우리 프로젝트는 후기 지향 맛집 지도 컨셉을 가지고 있기에, 이미지의 존재가 무엇보다 중요했다.
하지만 react-quill 라이브러리에는 이미지 사이즈 조절 기능이 따로 없어, 관련 라이브러리를 하나 더 설치해서 추가 설정을 해주어야 한다.
이를 해결하는 데에 있어서도 무지 애를 먹었다. 구글링을 많이 해봤는데 거의 대부분의 이미지 리사이징 라이브러리가 적용되지 않았기 때문이다. 그러다가 한줄기 빛을 만나게 되었다.
npm i @looop/quill-image-resize-module-react
기도 메타는 역시 나를 실망시키지 않는다.
참고로 이 라이브러리를 쓰려면 declare를 해줘야 정상 작동한다(동적 임포팅때문이라고 하던데 이 부분은 아직 이해하지 못했다ㅠㅠ).
그래서 vscode에서 시키는 대로 type.d.ts 파일을 만들어 해당 문장을 추가해줬다.
declare module "@looop/quill-image-resize-module-react";
이처럼 declare를 해주면 아래와 같이 문제 없이 해당 라이브러리를 import를 해줄 수 있다.
import ImageResize from '@looop/quill-image-resize-module-react'
Quill.register('modules/ImageResize', ImageResize)
다시 에디터 컴포넌트로 돌아가보자.
하단의 코드를 modules 코드 내 toolbars 밑 부분에 추가해주면 어떤 이미지를 업로드하던 리사이징이 가능해진다.
imageResize : {
modules : ['Resize']
}
잘된다 잘돼...고마워 라이브러리야 ㅠㅠ
우리 팀은 이미지를 s3에 저장하여 url로 변환해주는 방식을 사용하였기 때문에 이미지 업로드 핸들링 함수가 필요했다.
/**
* 이미지 핸들러(modules 설정보다 위에 있어야 정상 적용)
*/
const imageHandler = () => {
// 파일을 업로드 하기 위한 input 태그 생성
const input = document.createElement("input");
const formData = new FormData();
let url = "";
input.setAttribute("type", "file");
input.setAttribute("accept", "image/jpg,impge/png,image/jpeg");
input.click();
// 파일이 input 태그에 담기면 실행 될 함수
input.onchange = async () => {
const file = input.files;
if (file) {
formData.append("file", file[0]);
}
try {
// file 데이터 담아서 서버에 전달하여 이미지 업로드
const res = await axios.post(
"서버 배포 url/endpoint",
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
// 이미지 url
url = res.data.data.path;
if (QuillRef.current) {
// 현재 Editor 커서 위치에 서버로부터 전달받은 이미지 url을 이용하여 이미지 태그 추가
const index = QuillRef.current?.getEditor().getSelection()?.index;
if (index !== null && index !== undefined) {
const quill = QuillRef.current?.getEditor();
quill?.setSelection(index, 1);
quill?.clipboard.dangerouslyPasteHTML(
index,
`<img src=${url} alt="이미지 태그가 삽입됩니다." />`
);
}
}
} catch (error) {
const err = error as AxiosError;
return { ...err.response, success: false };
}
};
};
간단하게 작동 방식을 살펴보자.
- 에디터 내에 이미지가 삽입될 경우 파일을 업로드하기 위한 input 태그가 생성된다.
- 파일이 input 태그에 담기면 파일에 있는 데이터를 배포 서버에 요청을 보내 해당 이미지의 url을 리턴받는다.
- 처리가 완료되면 에디터 내 이미지가 업로드 된 커서 위치에 src 값이 해당 이미지의 url인 img 태그가 삽입된다.
참고로, 이미지 핸들러 함수는 modules 코드보다 위에 있어야 정상 작동된다.
/**
* quill에서 사용할 모듈을 설정하는 코드
* useMemo를 사용하지 않으면, 키를 입력할 때마다, imageHandler 때문에 focus가 계속 풀림
*/
const modules = useMemo(
() => ({
toolbar: {
container: [
["bold", "italic", "underline", "strike", "blockquote"],
[{ size: ["small", false, "large", "huge"] }, { color: [] }],
[
{ list: "ordered" },
{ list: "bullet" },
{ indent: "-1" },
{ indent: "+1" },
{ align: [] },
],
["image"],
],
handlers: {
image: imageHandler,
},
},
imageResize: {
modules: ["Resize"],
},
}),
[]
);
이제 modules의 handlers에 작성해준 이미지 Handler를 추가해주면 의도한 대로 이미지 핸들링을 할 수 있다.
참고 링크
'코드스테이츠 SEB FE 41기 > Main-Project(MatP)' 카테고리의 다른 글
[react] useAxios 커스텀 훅 사용기1(feat. typescript) (0) | 2023.01.22 |
---|---|
[react] 이미지 hover시 이미지 짙어지며 텍스트 뜨게 만들기 (0) | 2023.01.22 |
[react] star-rating 구현하기(feat. typescript) (0) | 2023.01.16 |
[react]Modal Portal(feat. typescript) (0) | 2023.01.16 |
[react] z-index, position, map reverse, axios (feat. typescript) (1) | 2023.01.11 |