1st project-review

WeSpace Project

1. 프로젝트 소개

spacecloud 클론 (2주)

2. 사용한 기술

HTML/SCSS/JavaSciript/React

3. 나의 역할

  • 로그인 페이지

  • 회원가입 페이지

  • 공간정보 상세페이지

4. 프로젝트를 진행하며 잘했다고 느껴지는 부분

  • 작은 기능들도 놓치고 싶지 않아서 끝까지 파고 들었다. (물론 놓친 기능들이 많다)

  • 백엔드와 데이터를 주고받는 첫 프로젝트 였지만 미리 틀을 만들어 준비를 하고 데이터가 준비 되었을떄 원활히 진행될 수 있도록 하였다.

  • 외관은 원본과 차이없게 만들고 싶어서 SCSS작업을 할때 1px단위도 신경쓰면서 만들었다.

5. 첫 프로젝트를 진행하며 나온 수많은 문제들

5.1 첫 로그인 페이지

아주 간단할것만 같았던 로그인 페이지가 시간을 많이 잡아먹었다. 간단한 레이아웃과 인풋창 2개에 로그인버튼 하나만 보여서 만만하게 생각했지만 옳은 이메일 형식을 잡아야 하는데 골치아팠다. 이것 저것 검색을 해보다가 정규표현식 중 이메일 형식을 잡아주는 식이 있길래 사용해서 문제를 해결했다.

const checkMail = mail => {
  const mailtest = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i;
  return mailtest.test(mail);
};

아직 정확한 원리는 이해를 하지 못하여 시간을 내어 정규표현식을 커스텀하여 쓸 수 있도록 공부를 해야겠다. checkMail이라는 메일형식을 확인하고 결과를 리턴 해주는 함수를 src 밑에 utils라는 폴더를 새로 만들어서 넣어두고 필요한 파일에서 import해와서 사용하였다.

5.2 회원가입 페이지

로그인 페이지에서 시간을 많이 쏟고 회원가입 페이지를 보면서 처음느낀건 이미 로그인 페이지에서 작업한 데이터가 있으니 금방 할 수 있겠다 싶었다. 하지만 인풋 창이 늘어나면서 그에대해 또 문제가 생기더라. 회원가입 페이지에는 닉네임,이메일,비밀번호,비밀번호 확인 4개의 인풋창이 있다. 4개의 인풋창을 핸들링을 효과적으로 하지 못해 하나하나 함수를 만들고 코드가 지저분했다.

handleChange = e => {
    const { value } = e.target;
    this.setState({
      [e.target.name]: value
    });
  };

인풋태그의 onChange이벤트의 인자로 들어오는 e객체를 활용하여 해당 인풋의 name을 state명과 같게 지정해주고 []대괄호 안의 값을 키값으로 설정하여 value값을 값으로 setState 처리한다.

모달창 구현하는 부분이 있었는데 이것저것 검색을 통해 알아보다가 css의 타겟속성을 활용해서 만들었다. dim처리 하는부분은 +라는 가상선택자를 통해서 구현을 했는데. 이 방법은 너무 예전 방식이고 state를 통해 관리하는것이 좋다고 하였다. 다음 모달창 구현시에는 state관리를 통해서 구현해야겠다.

5.3 상세페이지

상세페이지는 육체적으로나 정신적으로나 너무 어려운 부분이었다. 기능하나하나를 할때마다 모두 모르는것 투성이였고 고민을해도 답을 찾기가 너무 힘들었다 달력과 시간선택부분 슬라이드는 라이브러리를 사용해서 틀을 만들었고 나머지들은 하나하나 직접 구현을 했다.

handleToggle = number => {
  const copy = Object.assign({}, this.state.timeState)
  const Arr = []
  copy[number] = !copy[number]
  this.setState(
    {
      timeState: copy,
    },
    () => {
      for (let click in this.state.timeState) {
        if (this.state.timeState[click] === false) {
          Arr.push(click)
        }
      }
      this.setState(
        {
          click: Arr,
        },
        () => {
          this.props.toRender(this.state.click)
        }
      )
    }
  )
}

예약기능에서 시간을 선택하는 부분중 선택한 시간을 상위 컴포넌트로 올려주고 상위 컴포넌트에서 렌더링을 해야하는 부분이었다. 애를 참 많이 먹은 부분이었는데 setState가 비동기처리를 하기 떄문에 자꾸 한박자씩 밀렸다. 하나의 setState안에 콜백으로 클릭한것을 배열에 넣고 다시 setState하여 그안에 또 콜백으로 state값을 전달하였다. 뭔가 더 효율적인 방법이 있을것같지만. 시간상의 문제도 있고 이런식으로 비동기의 문제를 해결했다.

handleMoveBtn = value => {
  this.setState({
    currentPage: value,
  })
}

QnA 기능 구현중 댓글이 많아지면 댓글페이지가 많아지고 그페이지를 넘기는 버튼을 만드는 중 각각의 버튼에 className으로 접근을 한 후 이벤트를 처리하도록 했다. 하지만 className으로 접근한다는 것이 좋은 방식이 아니기때문에 handleMoveBtn함수의 인자를 정해주고 각각의 onClick이벤트를 줄때 화살표 함수로 그안에 handleMoveBtn의 인자를 넣어주었다.

onClick={() => {
              if (currentPage < Math.ceil(comment.length / postPerpage)) {
                handleMoveBtn(currentPage + 1);
              }
            }}
import React from 'react'
import '../QnAList/QnAList.scss'

const PageMove = ({ postPerPage, totalPosts, movePage, currentPage }) => {
  const pageNumbers = []
  for (let i = 1; i <= Math.ceil(totalPosts / postPerPage); i++) {
    pageNumbers.push(i)
  }
  return pageNumbers.map(number => (
    <span
      className={number === { currentPage }.currentPage ? 'currentSpan' : ''}
      onClick={() => movePage(number)}
      key={number}
    >
      {number}
    </span>
  ))
}

export default PageMove

댓글QnA창 구현중 pagenation기능을 구현하기 위해 이것저것 찾아보던 중 유튜브 강의가 있길래 한번 찾아보았다 영어강의에 이해하며듣기 어려웠지만 코드를 보면서 흐름은 느꼈다. 정규표현식과 마찬가지로 pagenation부분도 다음에 더 파면서 공부해야할 부분인것같다.

const handleReply = () => {
    if (hostReply !== undefined) {
      return (
        <div className="host_reply">
          <p className="host_head">호스트의 답글</p>
          <p className="host_review">{hostReply}</p>
          <p className="host_time">{hostTime}</p>
        </div>
      );
    } else {
      return null;
    }
  };
  return (
    <div className="user_comment">
      <div className="user_context">
        <img src={userImg} alt="." />
        <p className="user_name">{userName}</p>
        <p className="user_qus">{userQus}</p>
        <div className="comment_time">
          <p>{userTime}</p>
          <div>
            <span>수정 |</span>
            <span className="delete_btn" onClick={onClick}>
              삭제
            </span>
          </div>
        </div>
      </div>
      {handleReply()}
    </div>

조건부 렌더링 활용부분 댓글 QnA창을 구현하는데 유저의 댓글과 호스트의 답글을 한 컴포넌트에 넣고 댓글은 있는데 답글이 없는경우를 고려해야 했다. 조건부 렌더링이라는 개념은 알았지만 적절히 활용을 하지 못했었는데 옆에 계시던 인호님께 조언을 구하고 해답을 얻었다. 호스트의 답글이 없을때 null을 리턴하는 함수를 정의하고 컴포넌트 리턴에 {} 함수호출을 하면 되는것. 활용도가 매우 높을것이므로 제대로 이해하고 넘어가야겠다.

기록하고 싶은 코드

const fetchData = (...req) =>
  fetch(...req)
    .then(res => res.json())
    .catch(err => {
      throw err
    })

export default fetchData
componentDidMount() {
    fetchData("http://localhost:3000/data/data.json").then(res => {
      this.setState({
        spaceData: res.spaceData,
        topTag: res.spaceData.topTag,
        reserveList: res.spaceData.spaceInfo,
        reserveWarning: res.spaceData.reserveWarning
      });
    });

이번에 프로젝트를 하면서 배운 부분중 하나인데 public폴더에 가데이터 json 파일로 만들어놓고 fetchdata함수를 utils에 만든 후 import해서 setState하여 사용했다. 아직 promise개념을 잘 모르고 공식처럼 쓰고있는 느낌이지만 저 함수 덕분에 미리 json을 쓸 수 있게 틀을잡고 백엔드 데이터가 준비되었을때 빨리 맞춰 볼 수 있었다. promise개념을 더 공부한 후 이해하고 쓸 수 있도록 해야겠다.


Written by@jaeyoung-son
배운것을 기록하는 공간입니다.

GitHub