설치하기
https://ckeditor.com/docs/ckeditor5/latest/index.html 에 접속합니다.
검색창에 React Component 를 검색합니다.
또는 아래의 사진과 같이 찾아갑니다.
사이트에 나와있는 방법으로 설치를 진행합니다.
기본설정
상단에 import 를 해줍니다.
원하는곳에 CKEditor Component 를 아래와 같이 작성해줍니다.
<CKEditor
editor={ ClassicEditor }
data="<p>Hello from CKEditor 5!</p>"
onReady={ editor => {
// You can store the "editor" and use when it is needed.console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event, editor ) => {
const data = editor.getData();
console.log( { event, editor, data } );
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
/>
사용법
기본 사용법
작성한 데이터 가져오기
생략...
const [ contentState, setContentState ] = useState('');
return (
<CKEditor
editor={ ClassicEditor }
data={ contentState }
onReady={ editor => {
// # 에디터가 맨 처음에 사용할 준비가 완료되면 실행되는 event
} }
onChange={ ( event, editor ) => {
// # 에디터의 값이 바뀌었을 때 event
// 바뀐 값을 가져오는 법
const data = editor.getData();
// state 의 값을 바뀐 값으로 변경
setContentState(data);
} }
onBlur={ ( event, editor ) => {
// # 에디터를 언포커싱(다른곳을 클릭해서 에디터를 벗어났을 때) 했을 때 event
} }
onFocus={ ( event, editor ) => {
// # 에디터를 포커싱(에디터를 클릭했을 때) 했을 때 event
} }
/>
);
생략...
이미지 업로드
이미지 업로드 하는 여러가지 방법들
https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/image-upload.html
- CKBox file manager
- Easy Image
- CKFinder file manager
- Base64 upload adapter
- Simple upload adapter
- Inserting images via URL
직접 axios 등을 활용해서 커스텀 어댑터 만들기1 ( 이 방법에는 문제가 있으므로 커스텀 어댑터 만들기2를 참고하시는 걸 추천합니다. )
- 먼저 CKEditor 를 따로 컴포넌트화 시켜서 관리하는 것이 편하므로 컴포넌트화 시키고 필요한 값들은 props 로 받아옵니다.
const CustomCkEditor = (props) => {
const { contentState, setContentState } = props;
return(
<CKEditor
editor={ ClassicEditor }
data={ contentState }
onReady={ editor => {
// You can store the "editor" and use when it is needed.
console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event, editor ) => {
const data = editor.getData();
setContentState(data);
console.log( { event, editor, data } );
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
/>
);
};
export default CustomCkEditor;
- 커스텀 어댑터 함수를 만들어줍니다.
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import axios from 'axios';
const CustomCkEditor = (props) => {
const { contentState, setContentState, axiosHeader } = props;
// 파일 업로드를 해야하므로 Content-Type 을 multipart/form-data 로 바꾼 Header 를 하나 만듭니다.
const multipartAxiosHeader = {
'Content-Type': 'multipart/form-data',
Authorization: axiosHeader.Authorization
};
// customUploadAdapter 는 서버와의 통신을 처리하는 역할을 합니다.
// customUploadAdapter 가 작동하기 위해서는 upload() 와 abort() 메서드를 선언해줘야합니다.
// upload() === 파일 업로드 로직이 들어갑니다.
// abort() === 업로드가 중간에 중단될 경우 수행되는 로직이 들어갑니다.
const customUploadAdapter = (loader) => {
return {
upload() {
// upload 메소드는 Promise 객체를 반환하도록 설계합니다.
// 그리고 업로드가 성공했을 때 Promise 의 resolve() 안에 default 값에
// 업로드한 이미지의 접근 경로(주소)를 넘겨주게되면 에디터 안에 이미지가 삽입된
// <img> 태그 형식으로 반환됩니다.
// resolve() === 올바르게 수행완료 되었을 때, reject() === 문제가 발생했을 때
return new Promise((resolve, reject) => {
loader.file.then((file) => {
const data = {
file: file
}
axios.post(REST_IMAGE_UPLOAD_POST_URL, data, { headers: multipartAxiosHeader } )
.then(() => {
// axios 통신이 성공하면
resolve({
default: REST_API_URL + result.data.response
});
})
.catch((error) => {
console.log(error);
});
});
});
}
};
};
// 화살표 함수로 구현할 경우 에러가 발생합니다. 그 이유는 일반 함수와 화살표 함수의 차이점 때문에 발생합니다.
// 화살표 함수는 선언과 동시에 this 에 바인딩 할 객체가 정적으로 결정됩니다. 그래서 this 를 변경할 수 없고,
// 생성 함수로 사용할 수 없기 때문에 에러가 발생합니다.
function uploadPlugin (editor) {
editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
return customUploadAdapter(loader);
}
};
return(
<CKEditor
editor={ ClassicEditor }
// config 안에 새로 만든 uploadPlugin 을 적용시켜줍니다.
config={{
extraPlugins: [ uploadPlugin ]
}}
data={ contentState }
onReady={ editor => {
// You can store the "editor" and use when it is needed.
console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event, editor ) => {
const data = editor.getData();
setContentState(data);
console.log( { event, editor, data } );
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
/>
);
};
export default CustomCkEditor;
- 문제점 1
- 유저가 에디터에 사진을 등록한 상태로 글 작성을 취소했을 경우에도 서버에 사진이 그대로 남아버리게 됩니다.
- 문제점 2
- 이미지 업로드는 성공했으나 유저가 에디터에 사진을 등록하자마자 서버에 이미지가 저장되는 방식이다보니 유저가 에디터에 등록했던 사진을 지워도 서버에는 사진이 그대로 남아버리게 됩니다. 이 문제를 해결할 필요가 있어보입니다.
커스텀 어댑터2 ( 유저가 글 작성을 취소해도 서버에 사진이 남아버리는 문제 해결법 )
- 문제점 1에 대한 해결방법
- 유저가 글 작성을 시작할 때 임시 폴더를 만들어서 임시 폴더에 업로드 하도록하고 , 유저가 글 작성하기를 눌렀을 때 임시폴더에 남아있는 이미지들을 영구폴더로 이동시켜줍니다.
- 유저가 글 작성을 취소했을 경우 즉, 글 작성 컴포넌트가 unmount 되었을 때 임시 폴더 삭제요청을 서버에 보내줍니다.
- 이 해결방법을 사용하려면 해결해야하는 문제가 있습니다. 저는 일단 에디터에 입력된 html 태그를 통채로 DB 에 저장하기 때문에 이미지가 임시 폴더에서 영구 폴더로 이동했을 때 <img> 태그에 이미지 경로(주소)를 바꿔줘야 할 필요가 있습니다. 왜냐하면 에디터에서 이미지 미리보기 기능을 사용하기 위해서 이미지 경로를 임시 폴더로 지정했기 때문입니다.
- <img> 태그의 이미지 경로 문제는 글 작성 요청을 서버에서 받았을 때 에디터의 내용중에 임시 폴더 경로를 찾아서 영구 폴더 경로로 변경한 다음에 DB 에 저장하는 방법이 있습니다.
- 문제점 2에 대한 해결방법 1
- 임시폴더에 일단 다 저장하고 마지막에 남아있는 이미지의 이름 리스트를 서버에 보내주면 서버에서 해당하는 이름의 이미지만 영구 폴더로 이동시켜줍니다.
- 문제점 2에 대한 해결방법 2
- 에디터에 등록된 이미지의 이름들을 배열에 넣고 에디터의 값이 바뀔때마다 이미지가 지워졌는지 남아있는지 체크해서 지워진게 있다면 서버에 삭제요청을 보내줍니다. ( 서버에 부담이 너무 심해서 좋지 못함 )
- 최종 해결방법
- 여러가지를 고려한 결과 에디터의 이미지 업로드는 임시 폴더를 만들어서 업로드하고, 글 작성 요청이 들어왔을 때 삭제된 이미지를 제외한 이미지들을 영구 폴더로 이동시켜줍니다. ( 체크 방법은 에디터의 <img> 태그의 경로에서 이미지의 이름들만 추출해서 존재하는 이미지들만을 이동시킵니다. ) 그리고나서 임시 폴더를 삭제합니다.
- 또한 에디터의 <img> 태그의 이미지 경로들을 찾아서 영구 폴더의 경로로 변경한 후 DB 에 저장합니다.
- 마지막으로 사용자가 글 작성을 취소( unmount )했을 때 임시 폴더를 삭제해줍니다.
- 구현 코드 ( 서버부분 코드 https://shin96bc.tistory.com/139 )
- 마지막으로 구현하다보니 또 한가지 생각이 들었습니다. 만약에 글 수정에서 이미지를 삭제하게되면 어떻게 처리해야할지도 고민이 필요할 것 같습니다. 글 수정은 DB 에 저장된 데이터를 불러오는데 여기서 이미지들은 영구 폴더의 경로로 되어있어서 이 문제도 해결해야할 것 같습니다.
참고
https://velog.io/@ohjinseo/ReactJS-Express-CKeditor5-이미지-업로드-구현해보기
'JS > React' 카테고리의 다른 글
React - Atomic Design 이란? (0) | 2024.03.09 |
---|---|
React - setInterval & setTimeout (0) | 2024.03.09 |
React - 포트번호 변경하기 (0) | 2024.03.09 |
React - React Context (0) | 2024.03.09 |
React - 절대경로 설정하기 & Component 불러오기 (0) | 2024.03.09 |