항해99 파이널 프로젝트 SFLASH 어려웠던 점과 극복한 점

김형민·2021년 5월 24일
0

파이널 프로젝트때 구현했던 주요기능들

  • 커뮤니티 페이지 구현
  • 게시물 상세보기 모달창 구현
  • 게시물 작성, 삭제, 수정, 좋아요, 댓글 작성, 삭제 CRUD구현
  • 이미지 캐러셀, 이미지 다중업로드, 다중 수정 구현
  • 반응형 사이드바 구현
  • throttle을 이용한 자동검색 구현(필터링) & 서버와 통신으로 구현한 검색기능구현
  • 카테고리별로 게시물 필터링 구현
  • 무한스크롤 페이징 처리구현
  • 사이트 소개 팝업창 구현

카테고리 모듈작성과 이미지 다수 업로드 & 이미지 수정 & 큰 사진들을 올릴때 내용작성 버퍼링에 조금 어려움을 겪었다!
프로젝트의 카테고리 선택모듈을 구현했는데
아무래도 카테고리 중복선택이 가능하다 보니 조금 여러가지 경우의 수를 생각해야 되서 그러지 않았나 싶다

카테고리 다수의 카테고리를구현한 방법은

리덕스 state에 카테고리 버튼을 누를때 마다 해당 카테고리를 넣어주는 형식으로 구현했으며

is_category의 length가 0일땐 전체 게시물을 보여주도록 구현했다

const initialState = {
  is_category: [], 
}

is_category의 길이가 0이라는 것은 아무 카테고리가 선택되지 않았음을 의미하며 모든 게시물을 보여준다

 {searchPost.map((p) => {
 if (is_category.length === 0) {
 return <Post2 key={p.id} {...p}></Post2>;}
 })}                

is_catagory안에 "카페"라는 값이 있나 확인한다

const resultCafe = is_category.find((item) => item === "카페"); 

카페란 값이 있다면 즉, resultCafe가 true라는 뜻이므로
카테로기 정보에 "카페"라는 값이 들어있는 게시물만 필터링 되서 보여진다

    {searchPost.map((p) => {
            if (resultCafe) {
              if (p.category === "카페") {
                return <Post2 key={p.id} {...p}></Post2>;
              }
            }
          })}

리덕스 모듈을 살펴보면 버튼을 누를 때는 리덕스 상태값에 해당 카테고리가 push되며 같은 값이 들어오면 해당 상태값이 제거 되도록 설계했다

{cafe ? (
          <SelectedBtn
            onClick={(e) => {
              e.preventDefault();
              e.stopPropagation();
              setCafe(false);
              dispatch(categoryActions.getCategory("카
            }}
          >
            #카페
          </SelectedBtn> 
        ) : (
          <Btn
            onClick={(e) => {
              e.preventDefault();
              e.stopPropagation();
              setCafe("cafe");
              dispatch(categoryActions.getCategory("카페"));
            }}
          >
            #카페
          </Btn>

카테고리 리듀서 부분 함수

 [GET_CATEGORY]: (state, action) =>
      produce(state, (draft) => {
        // 카테고리 상태에 카테고리 명을 넣어준다
        draft.is_category.push(action.payload.category);

        // 다른 카테고리가 들어오면 붙여주고
        draft.is_category = draft.is_category.reduce((acc, cur) => {
          if (acc.findIndex((a) => a === cur) === -1) {
            return [...acc, cur];
          } else {
         // 같은카테고리가 오면 제거한다
            for (let i = 0; i < acc.length; i++) {
              if (acc[i] === cur) {
                acc.splice(i, 1);
              }
            }
            return acc;
          }
        }, []);
      }),

전체보기 버튼을 눌러주면 is_category 배열이 0이 되면서 모든 게시물이 보여지게 처리!

[RESET_CATEGORY]: (state, action) =>
      produce(state, (draft) => {
        draft.is_category = []; // 전체보기 효과를 위해 이렇게 리셋 해준다
      }),

다중이미지 이미지업로드 수정을 구현한 방법은

이번 프로젝트는 게시물에 사진을 여러장 업로드 할 수 있게 구현을 했다
사실 업로드 부분까지는 큰 어려움이 없이 잘 구현 했지만

수정에서 문제에 부딪혔다.
생각해야 할 경우의 수가 많았기 때문인데 경우의 수를 살펴보면

  • 기존의 게시글에 업로드한 이미지가 맘에안들어 삭제하는경우
  • 기존의 게시글에 이미지를 더 추가하는 경우
  • 삭제와 추가 둘 다 하는경우
  • 이미지를 추가하려고 파일을 올리고 프리뷰를 보여줬는데 다시 맘에안들어 삭제하는 경우

아무래도 이미지 업로드 특성상 프리뷰를 보여주고 파일은 따로 저장해야하는 기능을 동시에 처리해줘야 하면서 이미지들을 어떤 값으로 비교할까에 대해 생각하며 처음부터 덜컥 겁먹고 접근했던게 조금 어려웠던 부분인것 같았다

해결했던 방법은

  • 기존의 게시글에 업로드한 이미지가 맘에안들어 삭제하는경우

일단 이미지들을 삭제하고 비교하기위해서 서버에서 response로 받아오는 이미지 값에
imgUrlId를 별도로 받았다

이렇게 함으로써 삭제버튼을 누르면 삭제한 이미지의 imgUrlId를 서버에 보내주는 것으로
이미지 수정 삭제를 구현했다


  • 기존의 게시글에 이미지를 더 추가하는 경우

수정을 할때 이미지 수정을 좀 더 자유롭게? 하기 위해서 게시물에서 이미지 부분을 때로 뺴서
리덕스의 상태값으로 넣어줬다 그리고 동시에 이미지 파일을 저장하는 곳에도 이미지를 넣어줬다

아래 코드를 보면 게시물 하나를 가져와 이미지 데이터만 따로 뺀후에
각각 다른 리덕스 state에 넣어주었다

const getModalPost = (board) => {
  return function (dispatch, getState) {
    const onlyImg = board.img_url;
    dispatch(getImage(onlyImg));
    for (let i = 0; i < onlyImg.length; i++) {
      dispatch(getImgToFile(onlyImg[i]));
    }
  };
};

/////////////

image: [], // 이미지를 따로 빼오자 // 이미지 x 클릭시 x 이미지를 제외한 배열이 들어옴 //이게 onlyImage
edit_file: [], //여기에 파일이 들어온다! //게시 버튼과 동시에 초기화

이렇게 구현한 이유는 idx를 이용해서 이미지 프리뷰와 파일을 삭제 해주기 위해서이다!

만약 게시물의 이미지를 수정이 시작된고 해당 게시물의 이미지가 두개라고 가정해보면
아래처럼 image 리덕스 state값이 바뀔 것이다

image: [img1, img2]
edit_file: [img1, img2]

여기서 이미지 파일을 1개를 추가하면

image: [img1, img2, img3]
edit_file: [img1, img2, img3(File)]

이런 형식으로 된다 이미지는 프리뷰를 보여주기 위해 추가가 되며
edit_file에넌 서버로 보내줄 img3 파일을 저장해준다!

그리고 업로드 하려고 추가로 올렸던 이미지가 맘에 안든다면 파일과 이미지 프리뷰를 동시에 삭제 해줘야한다

이미지 왼쪽 상단의 삭제 버튼을 누른다면 아래 2개의 리듀서가 동시에 발동되어
action.payload로 받은 idx의 이미지와 파일을 삭제해준다

  • 2번 이미지를 삭제한경우 다시 두개의 배열엔 이미지 두개와 파일 2개 존재
    image: [img1, img3]
    edit_file: [img1, img3(File)]
 [DELETE_IMAGE_IDX]: (state, action) =>
      produce(state, (draft) => {
        // 수정시 이미지 왼쪽상단의 삭제 버튼을 누르면 해당이미지의 idx값을 받아 이미지를 선택해 삭제
        draft.image = draft.image.filter((i, idx) => {
          if (i !== action.payload.idx) {
            return i;
          }
        });
      }),
 [DELETE_FILE_IDX]: (state, action) =>
      produce(state, (draft) => {
        //idx를 받으면 해당 패열에서 idx 받은 곳의 요소 1개를 삭제한다
        draft.edit_file.splice(action.payload.idx, 1);
 }),

이제 이렇게 수정을 마무리 해준다고 가정하면
유저입장에선 image 배열에서 삭제된 부분을 화면에서 사라지고
프론트 입장에선 img3(File)를 서버쪽으로 보내줘야한다!
image: [img1, img3]
edit_file: [img1, img3(File)]

그런데 edit_file중에 img1은 원래 서버에 있는 파일이고 img3(File)만 보내주면 되니까
불필요현 이미지url 형식인 img1을 걸러내준다!

    let _addFile = [];
    for (let i = 0; i < addFile.length; i++) {
      //가져온 배열의 길이 반복문을 돌리는데 이때 서버에 이미있는url형식의 기존 이미지는 제거
      if (!addFile[i].imgUrl) {
        _addFile.push(addFile[i]);
      }
    }
    //파일 리스트 중에 기존에 있던 imgUrl이 있는 이미지들을 제외하고 새로추가한 파일형식     의 요소만 폼데이터로 수정(추가)요청
    for (let i = 0; i < _addFile.length; i++) {
      formData.append("file", _addFile[i]);// _addFile이 최종적으로 서버에 보낼 이미지파일
    }

이미지 업로드 시 내용작성 버퍼링현상

이미지를 업로드 구현은 javascript file reader로 프리뷰를 먼저보여주고
파일은 따로 저장해서 서버로 전송해주는 방식을 사용했다

그러나 컴퓨터의 사양에 따라 다르긴하지만 고화질의 큰 사진 혹은 다수의 사진을 업로드하고나서
글작성 내용이 조금 버퍼링이 걸려서 불편했다!

물론 단순히 파일리더의 속도가 느리거나 파일을 업로드하는데 시간이 걸렸다고 생각했지만
내용을 작성할때 업로드 창에서 렌더링이 많이 발생하는 것을 발견했다

그 이유는

아래처럼 입력값이 바뀔때 마다 컴포넌트의 state값이 변경되어 렌더링이 매우 많이 발생하고 있었고 내용작성에까지 영향을 미칠것이라 판단했다

  const changeContents = (e) => {
    setContents(e.target.value);
  };

  const changeTitle = (e) => {
    setTitle(e.target.value);
  };

그렇다면 렌더링을 최소화해보자

여러가지 방법들을 찾아봤다
렌더링을 최소화 하면서 글작성에도 문제가 없는 구현방법을 떠올리다
이번 프로젝트때 자동검색 기능을 구현하면서 썼던 debounce가 생각이났다!

지금 컴포넌트를 렌더링 시키는게 setTitle(e.target.value); 요 녀석인데
이것의 실행을 키보드에서 손을 뗀뒤 0.3초 이후에 실행되게 만들어 줬다

  // 재렌더링을 최소화 하기 위해 input값을 가져오는데 디바운스 처리를 해줌
  const titleDedounce = _.debounce((e) => {
    setTitle(e.target.value);
  }, 300); //키보드 떼면 입력한게 0.3초 뒤에 나타난다.

  const contentsDedounce = _.debounce((e) => {
    setContents(e.target.value);
  }, 300);

위처럼 키보드를 누를때마다 렌더링이 발생하는 것이 아닌 키보드에서 손을 뗀뒤 0.3초 이후에 렌더링이 발생하므로 렌더링 횟수가 크게 줄어든 것을 볼 수 있다!!

물론 사용자가 글작성을 끝내고 매우 빨리 0.3초안에 게시 버튼을 누르면 문제가 발생할 것 같다.,.
사실 그럴 경우가 매우적다고 판단되지만 이보다 더 좋은 방법을 계속 찾아보려고 노력하고 있다!..


사실 막상 구현하고 나니 어려웠던 점이 당시처럼 크게 와닿지는 않았다.
코드를 짜보기도 전에 지레 겁먹지말고 안되는 이유보다 가능한 방법을 찾아내는 자세와 습관을
가져야 겠다고 느꼈다...!

profile
항해 중인 개발자

0개의 댓글