[21/07/15] 에디터 리팩토링 & 액션 리팩토링

에디터 모달 리팩토링

기존의 config 옵션 변수

const modules = {
  toolbar: {
    container: [
      [{ header: [1, 2, false] }],
      ['bold', 'italic', 'underline'],
      [{ list: 'ordered' }, { list: 'bullet' }],
      ['link', 'image'],
      [{ align: [] }, { color: [] }, { background: [] }],
      ['clean'],
    ],
  },
};
const formats = [
  'header',
  'font',
  'size',
  'bold',
  'italic',
  'underline',
  'list',
  'bullet',
  'align',
  'color',
  'background',
  'image',
];

변경된 config 옵션 변수

const config = {
    modules: {
      toolbar: {
        container: [
          [{ header: [1, 2, false] }],
          ['bold', 'italic', 'underline'],
          [{ list: 'ordered' }, { list: 'bullet' }],
          ['link', 'image'],
          [{ align: [] }, { color: [] }, { background: [] }],
          ['clean'],
        ],
        handlers: { image: onClickImage },
      },
    },
    formats: [
      'header',
      'font',
      'size',
      'bold',
      'italic',
      'underline',
      'list',
      'bullet',
      'align',
      'color',
      'background',
      'image',
    ],
  };

사용

<ReactQuill
        {...config}
        ref={editorRef}
        value={value}
        onChange={str => onChange(str)}
        readOnly={readOnly}
      />

굳이 두개로 나누기보단 깔끔하게 하나의 config 변수로 관리하여 ReactQuill 에게 전달해준다.

key & value

const [key, setKey] = useState<KeyEngType>('introduce');
const [value, setValue] = useState<string>(currentProduct[key]);

기존의 value 상태는 introduce guide 두개를 합쳐 관리하였음.

  const [type, setType] = useState<'introduce' | 'guide'>('introduce');
  const [introduce, setIntroduce] = useState<string>(currentProduct.introduce);
  const [guide, setGuide] = useState<string>(currentProduct.guide);

value 하나로 두 개의 키 데이터 관리하기보단, 각각 따로 나두는 것이 키값이 변경되어도 데이터를 보존하는데 좋을 것이라 판단됨.

 public async imageHandler() {  
    const input = document.createElement('input');  
  
    input.setAttribute('type', 'file');  
    input.setAttribute('accept', 'image/*');  
    input.click();  
  
    input.onchange = async () => {  
      var file: any = input.files[0];  
      var formData = new FormData();  
  
      formData.append('image', file);  
  
      var fileName = file.name;  
  
      const res = await this.uploadFiles(file, fileName, quillObj);  
    };  
  }  

위는 이곳을 참고한 ReactQuill에서의 이미지 업로드 코드이다. 살펴보면 이미지 핸들러가 실행될때마다 input을 만들어 로직이 수행되는 것을 알 수 있다.

위와 같이 하기 보단 인풋태그를 만들어 두고 ReactQuillimageHandler가 실행되면 인풋태그를 클릭하는 로직으로 변경하는게 효율적

const Editor: React.FC<IEditorProps> = ({ value, onChange, readOnly = false }) => {
  const editorRef = useRef<ReactQuill>(null);

  // 이미지 탐색기(파인더) 띄우기
  const imageRef = useRef<HTMLInputElement>(null);
  const onClickImage = useCallback(() => {
    if (!imageRef.current) return;
    imageRef.current.click();
  }, []);

  // 이미지 업로드
  const onChangeFile = async (e: ChangeEvent<HTMLInputElement>) => {
    const { files } = e.target;
    if (files) {
      const formData = new FormData();
      formData.append('img', files[0]);
      try {
        const {
          data: { url },
        } = await uploadImage(formData);
        if (editorRef.current) {
          const editor = editorRef.current.getEditor();
          const range = editor.getSelection();
          if (url && range) {
            editor.insertEmbed(range.index, 'image', BACKEND_URL + url);
          }
        }
      } catch (error) {
        console.error(error);
      }
    }
  };

...

  return (
    <Wrapper>
      <ReactQuill
        {...config}
        ref={editorRef}
        value={value}
        onChange={str => onChange(str)}
        readOnly={readOnly}
      />
      {!readOnly && (
        <input
          ref={imageRef}
          type="file"
          accept="image/png, image/jpeg"
          alt="이미지 업로드"
          onChange={onChangeFile}
          hidden
        />
      )}
    </Wrapper>
  );
};

기존 액션 수정하기

상태의 불변성을 유지하지 못하고 있는 액션을 리팩토링하자.

  const onClickSelect = (hashtag: any) => {
    const newHashtag = { hashtagId: hashtag.id, hashtagName: hashtag.name };
    const tempCurrentProduct = currentProduct;
    tempCurrentProduct[columnString] = [...tempCurrentProduct[columnString], newHashtag];
    dispatch({
      type: 'CHANGE_PRODUCT_CELL',
      payload: { newValue: tempCurrentProduct },
    });
  };

위 코드는 다음 그림과 같다. 얕은 복사가 수행되고 있음.

따라서 불변성이 유지되지 못하므로 위험한 코드임. 다음 액션으로 변경한다.

//CHANGE_PRODUCT_CELL => CHANGE_PRODUCT_INFO
case 'CHANGE_PRODUCT_INFO':
      return {
        ...state,
        table: {
          ...state.table,
          currentProducts: {
            ...state.table.currentProducts,
            [state.modal.currentProduct.id]: {
              ...state.modal.currentProduct,
              [action.payload.key]: action.payload.value,
            },
          },
          isModifed: { ...state.table.isModifed, [state.modal.currentProduct.id]: true },
        },
        modal: {
          ...state.modal,
          currentProduct: {
            ...state.modal.currentProduct,
            [action.payload.key]: action.payload.value,
          },
        },
      };

keyvalue를 받아 productkey속성 값을 value로 변경해준다.

  const onClickSelect = (hashtag: any) => {
    dispatch({
      type: 'CHANGE_PRODUCT_INFO',
      payload: {
        key: columnString,
        value: [
          ...currentProduct[columnString],
          { hashtagId: hashtag.id, hashtagName: hashtag.name },
        ],
      },
    });
  };

클립보드 이미지 복사하기(React-Quill)

ReactQuill에서 onPaste 이벤트를 지원할 줄 알았는데, 아니였나보다. 복붙을 하면 그 정보가 이미지인지 아닌지를 점검한 후, 이미지라면 서버에 올리는 작업 수행하면 될 것 같다.

아직 할게 많아서 4순위로 할 예정이다.

랜딩페이지 업체탐색 소개 기능 추가

다음 사진은 우리 디자인 사진이다.

여기서 css 궁금한 거, 다음 두개의 영역을 div로 나누어 스타일링 하는 방법말고


다음 코드 구조에서 위 디자인과 같은 스타일을 낼 수는 없을까?


이는 다음시간에..

Ref

  • codekata
  • hexo

좋은 웹페이지 즐겨찾기