Cooka프로젝트/게시판

react-quill 라이브러리 이미지처리

winhwi 2023. 9. 24. 21:19

react-quill라이브러리를 사용하여 게시글글 작성페이지를 구현했습니다.

react-quill의 사용방법에대해는 자세히 다루지않겠습니다.

 

 

글내용인 안녕하세요가 html형식으로 출력되는걸 볼수있습니다.

 

 

하지만 이렇게 이미지를 넣게되면

이렇게 img파일이 아주긴 문자열로 저장되는걸 볼 수 있습니다.

그이유는 react-quill라이브러리에서 이미지를 삽입하면 이미지가 Base64인코딩된 문자열로 저장되기때문입니다.

이렇게되면 이미지파일을 직접 사용하는 것보다 더많은 데이터를 전송해야하고, 이로인해 웹페이지의 로딩속도가 느려질수 있습니다.

따라서, 이미지첨부를 하게되면 서버에api요청을 보내 response로 URL을 돌려받고, 이미지태그의 src에 인코딩된 문자열대신 URL을 넣어야합니다.

 

 

이작업을위해 react-quill 모듈에

const iamgeUploadHanler =()=>{
//아직 아무것도 입력x
}

const modules = {
  toolbar: {
    container: [
      ["link", "image", "video"],
      [{ header: [1, 2, 3, false] }],
      ["bold", "italic", "underline", "strike"],
      ["blockquote"],
      [{ list: "ordered" }, { list: "bullet" }],
      [{ color: [] }, { background: [] }],
      [{ align: [] }],
    ],
    handlers: {
      image: imageUploadHanler,
    },
  },
};

핸들러함수를 추가했습니다.

이렇게 작성하고 이미지첨부를 클릭하면 파일선택창이 열리지않습니다.
이유는 imageUploadHanler함수내에서 파일선택창을 열도록 구현되지않았기 때문입니다.
imageUploadHanler함수내 파일선택창을 열도록 코드를 작성해야합니다.
이를위해 input type="file" 요소를 사용하면됩니다.

 

 

const imageUploadHanler = (e: React.ChangeEvent<HTMLInputElement>) => {
  const input = document.createElement("input");
  input.setAttribute("type", "file");
  input.setAttribute("accept", "image/*");
  input.click();
};

 

const input = document.createElement("input") input요소를 동적으로 생성하여 input변수에 저장하는것입니다.
input.setAttribute("type", "file") 생성된 input요소에 type을 file로 설정하고
input.setAttribute("accept", "image/* ") 생성된 input요소에 accept로 모든이미지파일형식을 허용합니다.
input.click() input요소를 클릭한것처럼 동작하게 됩니다.

 

 

input요소의 값이 변경되었을때 선택한파일정보를 저장하기위해

const imageUploadHanler = () => {
  const input = document.createElement("input");
  input.setAttribute("type", "file");
  input.setAttribute("accept", "image/*");
  input.addEventListener("change", (e) => {
    const selectImg = e.target.files ? e.target.files[0] : null;
    if (selectImg) {
      const formData = new FormData();
      formData.append("image", selectImg);
      axios.post(
        "url"/image, formData,
        {
          headers: {
            "Content-Type": "multipart/form-data",
          },
        }
      );
    } else {
      console.log("no file err");
    }
  });
  input.click();
};

이미지파일을 서버에 효과적이게 전송하기위해 FormData를 사용했습니다.
const formData = new FormData() FormData객체를 생성합니다.
formData.append("image", selectImg) 이미지파일을 formdata에 추가합니다.
formData.append() 메서드는 formData.append("name","value")로 이루어집니다.

일단 여기까지 작성후 서버코드로 이동하겠습니다.

 

 

import { Express } from "express";
import multer from "multer";
import path from "path";

const app = express();
app.use(express.static(path.join(__dirname, "uploads")));

파일업로드를 쉽게 처리하기위해 multer를 사용하였습니다.
app.use(express.static(path.join(__dirname, "uploads"))) 코드는 풀어서해석하자면
express.static() express에서 제공하는 정적파일 미들웨어입니다.
특정디렉토리에 저장된 파일을 웹사용자에게 제공할 수 있습니다.
(path.join(__dirname, "uploads") 파일경로를 위해 사용됩니다.

 

 

import randomstring from "randomstring";
const storage = multer.diskStorage({
  destination: (req, file, callback) => {
    callback(null, "uploads/");
  },
  filename: (req, file, callback) => {
    const uniqueKey = Date.now() + "-" + randomstring.generate(15);
    callback(null, file.fieldname + "-" + uniqueKey);
  },
});

multer.diskStorage 는 말그대로 파일을 저장할 저장소입니다.
dstination은 저장할 파일위치,
filename은 업로드될 파일명입니다.
파일명은 같으면 안되므로
randomstring라이브러리를 사용해 15자리 랜덤문자를 생성합니다.
uniqueKey변수에 " "현재시간-15자리랜덤문자열" " 를 넣어주고 파일이름뒤에 넣어서 리턴해줍니다.

 

 

uploads폴더를 만들어주고

 

 

const upload = multer({ storage: storage });
app.post("/image",upload.single("image"),
    async (req, res) => {
      const imgUrl = `/uploads/${req.file.filename}`;
      res.json({ imgSrc: imgUrl });
    }
  );

upload.single("image")는 image필드에 담긴 파일을 서버에 저장하도록합니다.

 

 

다시 글작성페이지에서 이미지를 넣고 , uploads폴더에 저장되는지 확인해보겠습니다.

확장자명이없어 이미지파일로 저장이 안되고있습니다.

 

 

업로드될 파일명에 확장자를 넣어주기위해 코드를 수정합니다.

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "uploads/");
  },
  filename: (req, file, cb) => {
    console.log("originalname:", file.originalname);
    const extension = path.extname(file.originalname);
    console.log("extension:", extension);
    const uniqueKey = Date.now() + "-" + randomstring.generate(15);
    cb(null, file.fieldname + "-" + uniqueKey + extension);
  },
});

file.originalname은 파일업로드시 이미지명입니다.

path.extname()메서드는 파일경로에서 마지막 "."이후의 문자열을 추출하여 파일의 확장자를 반환합니다.

반환값에는 "."이 포함되는걸 볼수 있습니다.

 

 

이제 이미지파일로 저장됬습니다.

 

 

하지만 에디터에서 해당url에 접근할때 에러가 발생했습니다.

에러해결과정입니다.

하지만 에디터에서 해당 url을 불러올시 에러가 발생했습니다.

저는 백엔드와 프론트엔드 서버가 서로다른 포트에서 실행되고 있습니다.

프론트는 3000포트 백엔드는 8000포트입니다.

 

 

imgUrl을 변경해줍니다

 

 

app.use(express.static(path.join(__dirname, "uploads")));
console.log("path.join:",path.join(__dirname, "uploads"))

express.static미들웨어를 사용하여 uploads폴더를 정적파일로 서빙하도록 설정했었습니다.

콘솔출력값은

저는 uploads폴더가 src하위에 있는게아니라

backend하위폴더에 존재합니다.

그렇기때문에 path.join:backend/uploads 로되어야합니다.

 

 

app.use(express.static(path.join(__dirname, "../uploads")));
console.log("path.join:",path.join(__dirname, "uploads"))

이렇게 수정했습니다.

 

 

 

-__dirname 은 현재실행중인 스크립트의 파일의 디렉토리 경로를 나타내기때문에 생긴 이슈였습니다.

 

 

 

이제 에디터에서 이미지를 첨부할시

이미지가 정상적으로 출력되고 있습니다.

'Cooka프로젝트 > 게시판' 카테고리의 다른 글

pagination을 수정해보자!  (2) 2023.10.09