React-Quill 이미지 처리하기 (2)

스탁벅스·2023년 5월 31일
4

react-quilll

목록 보기
2/2
post-thumbnail

이전글에서 img 태그의 문자열 길이가 어마어마하게 커지는 문제점, aws를 이용해 해결해보았다.

1. s3에 이미지 업로드 하기

1. s3 생성, aws-sdk 설치

우선 이미지를 업로드할 s3버킷을 생성하자. 아래 블로그를 참고했다.
https://jforj.tistory.com/260

추가로, 만든 s3에 업로드하기 위해 aws-sdk 라이브러리를 설치했다.
npm install aws-sdk

2. imageHandler

이미지를 업로드할때 사용할 함수인 imageHandler를 만들었다.

  const imageHandler = async () => {
    const input = document.createElement("input");
    input.setAttribute("type", "file");
    input.setAttribute("accept", "image/*");
    input.click();
    input.addEventListener("change", async () => {
      //이미지를 담아 전송할 file을 만든다
      const file = input.files?.[0];
      try {
        //업로드할 파일의 이름으로 Date 사용
        const name = Date.now();
        //생성한 s3 관련 설정들
        AWS.config.update({
          region: REGION,
          accessKeyId: ACCESS_KEY,
          secretAccessKey: SECRET_ACCESS_KEY,
        });
        //앞서 생성한 file을 담아 s3에 업로드하는 객체를 만든다
        const upload = new AWS.S3.ManagedUpload({
          params: {
            ACL: "public-read",
            Bucket: "itsmovietime", //버킷 이름
            Key: `upload/${name}`, 
            Body: file,
          },
        });
        //이미지 업로드 후
        //곧바로 업로드 된 이미지 url을 가져오기
        const IMG_URL = await upload.promise().then((res) => res.Location);
        //useRef를 사용해 에디터에 접근한 후
        //에디터의 현재 커서 위치에 이미지 삽입
        const editor = quillRef.current.getEditor();
        const range = editor.getSelection();
        // 가져온 위치에 이미지를 삽입한다
        editor.insertEmbed(range.index, "image", IMG_URL);
      } catch (error) {
        console.log(error);
      }
    });
  };

3.modules 객체 수정

  const modules = useMemo(() => {
    return {
      toolbar: {
        container: [
          ["image"],
          [{ header: [1, 2, 3, 4, 5, false] }],
          ["bold", "underline"],
        ],
        handlers: {
          image: imageHandler,
        },
      },
    };
  }, []);

우선 toolbar의 handlers에 저렇게 만든 imageHandler 함수를 추가해준다.
또 달라진 점이 useMemo를 사용했다는 점이다. useMemo를 사용하지 않으면, 이미지 삽입할 때마다 modules가 리렌더링 되고, 리렌더링 된 뒤에 커서 위치를 찾지 못해서 에러가 발생한다. 따라서 useMemo를 사용해서 반드시 리렌더링을 막아줘야한다.

2. CloudFront에 접근하여 이미지 가져오기

0. CloudFront 사용 이유

CloudFront는 aws에서 제공하는 CDN 서비스이다. 엣지 서버를 사용해서 콘텐츠를 캐싱하기 때문에 성능이 향상되고 보안적으로 우수하다. 또한 결정적으로 s3만 사용할 때에 비해 확연한 비용절감 효과도 얻을 수 있다.

1. CloudFront 배포 후 S3 연결

CloudFront 배포 후 s3연결 하는 과정은 아래 블로그를 참고하였다.
https://earth-95.tistory.com/128

2. imageHandler 수정

  const imageHandler = async () => {
	...
        const url_key = await upload.promise().then((res) => res.Key);
        const editor = quillRef.current.getEditor();
        const range = editor.getSelection();
        editor.insertEmbed(range.index, "image", CLOUD_FRONT_URL + url_key);
        
	...
    
  };

함수 끝에 이미지 불러오는 방식만 바꿔주면된다. 이전에는 s3의 url을 통해 직접 이미지를 가져왔다면, 이제는 배포한 CloudFront uri에다가, s3 내에 저장된 위치와 이름(key)을 통해 사진을 가져온다.

3. 캐시 확인

1) Cache Miss
위 코드에서 설정했듯이, 특정 이미지를 최초 업로드를 하면 바로 cloudfront를 통해 이미지를 불러온다. 최초로 엣지 서버에서 이미지를 불러오면 캐싱된 내역이 없으므로 CacheMiss가 발생한다.

2) CacheHit
하지만, 그 다음에 해당 글을 제출하고, 글을 불러와서 이미지를 가져올 때의 요청을 보면, 이미 한번 불러온적이 있는 이미지를 불러오는 것이기 때문에 CacheHit가 발생한다.

3. 최종 코드

Write.jsx

import ReactQuill from "react-quill";
import { useMemo, useRef, useState } from "react";
import { createPost } from "./api/api";
import AWS from "aws-sdk";

const REGION = process.env.REACT_APP_AWS_S3_BUCKET_REGION;
const ACCESS_KEY = process.env.REACT_APP_AWS_S3_BUCKET_ACCESS_KEY_ID;
const SECRET_ACCESS_KEY = process.env.REACT_APP_AWS_S3_BUCKET_SECRET_ACCESS_KEY;
const CLOUD_FRONT_URL = (배포한 CloudFront URI);

function Write() {
  const quillRef = useRef(null);
  const [content, setContent] = useState("");
  const [title, setTitle] = useState("");

  const handleTitleChange = (e) => {
    setTitle(e.currentTarget.value);
  };
  const handleSubmit = async () => {
    const date = new Date();
    try {
      await createPost({
        title,
        content,
        date,
      }).then((res) => console.log(res));
    } catch (error) {
      console.log(error);
    }
  };
  const imageHandler = async () => {
    const input = document.createElement("input");
    input.setAttribute("type", "file");
    input.setAttribute("accept", "image/*");
    input.click();

    input.addEventListener("change", async () => {
      //이미지를 담아 전송할 formData를 만든다
      const file = input.files?.[0];

      try {
        //업로드할 파일의 이름으로 Date 사용
        const name = Date.now();
        //s3 관련 설정들
        AWS.config.update({
          region: REGION,
          accessKeyId: ACCESS_KEY,
          secretAccessKey: SECRET_ACCESS_KEY,
        });
        //앞서 생성한
        const upload = new AWS.S3.ManagedUpload({
          params: {
            ACL: "public-read",
            Bucket: "itsmovietime",
            Key: `upload/${name}`,
            Body: file,
          },
        });
        //이미지 업로드
        //업로드 된 이미지 url을 가져오기
        const url_key = await upload.promise().then((res) => res.Key);
        //useRef를 사용해 에디터에 접근한 후
        //에디터의 현재 커서 위치에 이미지 삽입
        const editor = quillRef.current.getEditor();
        const range = editor.getSelection();
        // 가져온 위치에 이미지를 삽입한다
        editor.insertEmbed(range.index, "image", CLOUD_FRONT_URL + url_key);
      } catch (error) {
        console.log(error);
      }
    });
  };
  const modules = useMemo(() => {
    return {
      toolbar: {
        container: [
          ["image"],
          [{ header: [1, 2, 3, 4, 5, false] }],
          ["bold", "underline"],
        ],
        handlers: {
          image: imageHandler,
        },
      },
    };
  }, []);
  return (
    <>
      <div>
        <label htmlFor="title">제목</label>
        <input id="title" type="text" onChange={handleTitleChange} />
        <ReactQuill
          ref={quillRef}
          style={{ width: "800px", height: "600px" }}
          modules={modules}
          onChange={setContent}
        />
      </div>
      <button style={{ marginTop: "50px" }} onClick={handleSubmit}>
        제출
      </button>
    </>
  );
}

export default Write;

참고

profile
환영합니다. 스탁벅스입니다.

0개의 댓글