2nd Project 회고

jivyyyy·2020년 6월 28일
2

ESSAY

목록 보기
2/2

Epilogue

1차 프로젝트가 끝나자마자 바로 2차 팀과 주제가 발표되었다. 이번에 맡게 된 프로젝트는 어려워보이지만 재밌어보인다고 생각했던 Pinterest 클론이었다.
지금 생각해보면 2차 주제로 정말 좋은 사이트였다는 생각이 든다.
UI는 단순한 편이어서 상대적으로 CSS 보다는 기능을 구현하는 데 주력할 수 있었다.
우리 팀은 백엔드 1명, 프론트엔드 3명으로 구성되었다.

팀의 목표

핀터레스트 웹사이트는 요즘 트렌드라는 벽돌형태의 레이아웃에 여러가지 기능들을 가지고 있었다. 단순하게 생각하면 인스타와 비슷할 것 같은데 내가 맘에 드는 사진들을 내 보드에 저장, 좋아요를 누를 수 있고 댓글을 달거나 삭제하는 기능도 있다. 또한 내가 좋아하는 사진을 올리는 사람들을 팔로워 할 수도 있으며 내 사진을 올릴 수도 있다.
우리는 최대한 실제 웹사이트와 똑같이 구현하되, 중요도는 UI 보다 기능을 완성도 있게 구현하는 것에 두기로 하였고 이번 프로젝트를 통해서 연습해보고자 했던 기능들은 아래와 같다.

  • 소셜로그인/소셜회원가입
  • 백엔드와 api 통신(get, post, delete)
  • 함수형 컴포넌트(useEffect,useState..)
  • style component를 통한 스타일링
  • router 를 이용한 자유로운 페이지 이동
  • form data post

프론트엔드가 3명이어서 윤지님이 메인과 소셜로그인, 정엽님이 메인디테일페이지, 내가 마이페이지를 담당하게 되었다.

개인적인 목표

지난 1차 프로젝트에서 시간이 부족하여 마무리를 제대로 하지 못한 것 같아서 아쉬움이 많았다.
이번에는 제대로 시간 분배를 해서 시간에 쫓기지 않게 작업하고 싶었고, 마찬가지로 지난 프로젝트때 느꼈던 아쉬운 점 중에 하나였던 백엔드와 충분한 소통을 하려고 노력했다. 저번 프로젝트 때에는 뭔가 백엔드에게 부탁하거나 물어보기 미안해서 그냥 건너뛰거나 당연히 내 쪽 문제라고 생각해서 혼자 끙끙 앓기도 했었는데 이번에는 그러지 말자고 생각했다. 문제는 같이 해결해야 하는 거니까!

My Roles

내가 구현해야 했던 페이지는 mypage 였다.
여기서 사용자는 보드를 만들거나/삭제하거나/보드별로 핀들을 확인할 수 있고,
핀 텝에서는 보드에 상관없이 모든 핀을 볼 수 있고, 주제에서는 관심있는 토픽을 팔로우/언팔로우 할 수 있으며, 핀을 올릴 수도 있다.

1. 보드

이번 프로젝트에서는 함수형을 이용했기 때문에 componentDidMount가 아닌 useEffect를 사용해서 페이지가 렌더링될 때, 헤더에 사용자의 access Token을 담아 해당 사용자의 pin과 보드의 정보들을 get요청했다.

const Boards = ({ history }) => {
  const [boards, setBoards] = useState([]);
  const [onMouse, setOnMouse] = useState(false);
  const [hoveredBoard, setHoveredBoard] = useState("");

  useEffect(() => {
    const accessToken = localStorage.getItem("Authorization");

    fetch(`${url}/boards`, {
      headers: {
        Authorization: accessToken,
      },
    })
      .then((res) => res.json())
      .then((res) => setBoards(res.boards));
  }, []);

컴포넌트를 만들고 map을 돌려서 백엔드에서 받은 data들을 화면에 렌더링한 후, 각각의 컴포넌트에 효과를 주고 싶은데 문제가 있었다. 호버효과를 주면 한 사진에 호버를 해도 모든 사진에 호버가 되는 현상이 일어나는 것이다. 처음에는 황당했는데 지금 생각해 보니 당연한 일이었다.
이를 해결하기 위해서는 해당 데이터의 id를 함께 전달해서 그 id 에 해당하는 요소에만 효과를 주어야 했다.

 //마우스호버&아웃시 삭제 버튼 생기는 함수
  const onMouseHandler = (boardId) => {
    setHoveredBoard(boardId);
  };

        {boards &&
          boards.map((data) => (
            <BoardBoxes
              onMouseEnter={() => onMouseHandler(data.board.id)}
              onMouseLeave={() => onMouseHandler("")}>

그리고 여기서 또 한가지.
원래는 onMouseOver 와 onMouseOut을 썼었는데 호버하면 생기는 버튼이 클릭이 도저히 안되서 보니 마우스오버는 워낙 마우스의 움직임에 예민해서 사용하기가 어렵다고 한다. 마우스엔터로 바꾸니 바로 해결이 되었다.

2. Style Component

인스타그램 클론과 1차 프로젝트때 sass로 신나게 작업하다가 갑자기 2차때 스타일컴포넌트로 스타일링을 하라니 사실 막막해서 이틀 정도는 헤맸던 것 같다. 하지만 역시 적응의 동물인지라 익숙해졌고 오히려 스타일 컴포넌트가 props 전달과 삼항연산자를 활용해서 조건에 따라 스타일링 하기엔 또 편한 점도 많았던 것 같다.

예를 들면 보드/핀/주제 선택에서 active tab 을 사용해서 선택한 탭은 검은색버튼으로, 나머지는 하얀색 버튼으로 스타일링을 해주어야 했는데 아래와 같이 활용할 수 있었다.

    <SubjectTab
    onClick={() => setTab(0)}
    color={tab === 0 ? true : false}
    >
      보드
    </SubjectTab>
    <SubjectTab
    onClick={() => setTab(1)}
    color={tab === 1 ? true : false}
    ></SubjectTab>
    <SubjectTab
    onClick={() => setTab(2)}
    color={tab === 2 ? true : false}
    >
      주제
    </SubjectTab>



const SubjectTab = styled.button`
  color: ${(props) => (props.color ? "white" : "black")};
  background: ${(props) => (props.color ? "black" : "transparent")}`;

3. 핀 올리기/Form Data Post

그동안 다른 형식의 데이터들은 쉽게 post로 백엔드 api에 전달 하였으나 이번에는 이미지를 전달해야 했다. 열심히 구글링을 해보니 생각보다 어렵지는 않아서 코드를 작성하고 백엔드와 늦은 밤 귀가도 하지 못한 채 열심히 맞춰 보았는데 도저히 우리끼리는 답을 찾을 수가 없었다.
몇시간 내내 고민하다가 결국 백엔드 분의 제안으로 stack overflow의 도움을 받아보기로 한다.
미국 실리콘밸리 사람들이 퇴근하고 쉴 시간이라고 한다. 답이 오기를 기대하며 질문을 올렸고 두 시간도 안되어 답변이 달리기 시작했다. 그리고 결국 정말 쉽게 해결!!

문제는 헤더에 content-type의 문제가 아니었고, 내가 올릴 파일 객체에서 [0]을 해주는 것으로 바로 해결 되었다... 허무했지만 문제가 해결되어 정말 기뻤고 신났었던 기억이 난다.
기념으로 우리가 올렸던 stackoverflow 의 링크를 남긴다.

우리의 해결사 스텍오버플로우

//input 에서 선택한 파일 정보를 담기
  const fileChangeHandler = (e) => {
    const files = e.target.files;
    console.log(files);
    setUploadedfile(files[0]);
  };

  // 핀만들기(사진올리기)
  const savePins = () => {
    const fd = new FormData();
    fd.append("filename", uploadedfile);
    fd.append("title", title);
    fd.append("text", imgDes);

    const accessToken = localStorage.getItem("Authorization");

    fetch(`${url}/pin-builder`, {
      method: "POST",
      headers: {
        // "content-type": "multipart/form-data",
        Authorization: accessToken,
      },
      body: fd,
    }).then(function (res) {
      if (res.ok) {
        alert("핀이 등록되었습니다.");
        //디테일페이지로 가야함
        history.push("/mypage");
      } else {
        alert("Oops");
      }
    });
  };

이건 그냥 내가 기억하고 싶어서 남기는 코드
textarea 에서 입력창이 content 길이에 맞게 조정되도록 코드를 작성해보았다.

  //TextArea 글자 수에 따라 입력창이 늘어나는 함수
  const Resizing = (e) => {
    e.style.height = "auto";
    e.style.height = e.scrollHeight + "px";
  };

<MakeTitle
    type="text"
    placeholder="제목 추가"
    maxlength="100"
    rows="1"
    onKeyDown={(e) => Resizing(e.target)}
    onChange={titleChangeHandler}
                />

4. 주제선택

여기서도 역시 마찬가지로 map을 돌린 후 각 요소의 id를 사용해서 식별하여 배열에 이미 있으면 삭제하고, 없으면 넣어주는 방식으로 팔로우/팔로우 취소 기능을 구현했다.

  const handleClick = (id) => {
    if (followingList.some((el) => el === id)) {
      let index = followingList.indexOf(id);
      followingList.splice(index, 1);
      setFollowingList(followingList);
    } else {
      setFollowingList(followingList.concat(id));
    }
    setKey(id);
    console.log(key);
  };

 <Follow
    onClick={() => handleClick(data.id)}
    followingList={followingList}
    key={data.id}
    color={followingList.includes(data.id) ? true : false}
      >
        <div>
        {followingList.includes(data.id) ? "팔로우" : "팔로잉"}
          </div>

프로젝트 결과물

프로젝트 github
유투브 영상

프로젝트를 마치며

이번 프로젝트는 저번 프로젝트의 뼈아픈 교훈을 통해서 시간분배와 충분한 회의를 통해서 만족스러운 결과를 얻어낼 수 있었다. 어려운 문제에 부딪힐 때가 정말 많았지만 단결력 최고인 우리 팀 멤버들과 늘 머리를 맡대고 어떻게든 같이 해결하려고 노력했던 감동적인 순간들도 많았다.
분위기도 열정도 결과도 최고였던 우리 멋진 핀테러리스트팀.
마지막 2주차에는 학원 근처에 숙소를 얻어서 다들 새벽까지 열심히 달리다보니 힘들기도 했지만 마지막의 결과를 보니 보람을 느꼈다. 처음엔 막막하기만 했는데 멘땅에 헤딩하고 동료들과 같이 고뇌에 빠지다보면(?) 어려운 프로젝트로 해낼 수 있다는 자신감과 성취감 또한 귀한 결실이었다.
앞으로 어려운 프로젝트를 만날 때마다 결국 또 두렵고 막막함을 느끼겠지만 그때마다 이번 프로젝트를 하면서 겪었던 과정들을 떠올릴 수 있었으면 좋겠다.

profile
나만의 속도로

0개의 댓글