JS/React

React - ckEditor 사용법

shin96bc 2024. 3. 9. 17:48

설치하기

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