[1차 프로젝트] Wash Korea 회고

수경, Sugyeong·2021년 11월 21일
0

1st 프로젝트

목록 보기
2/2
post-thumbnail

1. 프로젝트 개요

약 2주 동안 달려오던 1차 프로젝트가 끝이 났다. 프로젝트 사이트의 Footer 를 구현하면서 작성한 글에서 언급한 바와 같이 감사하게도 내가 제안했던 사이트인 러쉬 코리아(https://lush.co.kr/) 가 선정이 되어 나를 주축으로 팀이 꾸려지게 되었다. 프로젝트 첫 날 Planning Meeting 을 통하여 프론트 단에서 필수로 구현하면 좋을 기능을 추린 후 파트를 분배하였는데 나는 Footer, 회원가입 그리고 로그인 페이지 구현을 맡았다.

러쉬 코리아의 경우 기본적인 커머스 사이트에 필요한 핵심 기능들인 로그인 및 회원가입, 제품 리스트 페이지 그리고 제품 상세 페이지 등을 구현해볼 수 있도록 짜여진 좋은 사이트라고 생각되어 선정하게 되었다. 그 외에도 장바구니, 상품 찜 기능, 검색 기능 등의 다양한 기능도 있었지만 주어진 프로젝트 시간 상 전부 구현할 수는 없어서 추가 기능 구현 사항으로 분류해두었지만 구현하지 못하여 조금 아쉽기도 하였다. 또한 메인 페이지를 만들기 보다는 기능 구현에 충실하고자 메인 페이지 대신 상품 리스트 페이지를 메인으로 삼았다.

Github Link


1.1 개발 기간

2021/11/1 ~ 2021/11/12


1.2 개발 인원

나를 포함한 프론트엔드 3분, 백엔드 2분으로 구성되었다. (개인 정보를 위해 얼굴이 보이는 사진은 블러 처리 하였다.)


1.3 기술 스택

Front-end

  • HTML/CSS
  • JavaScript
  • React
  • SASS

Back-end

  • Python
  • Django
  • MySQL
  • JWT
  • Bcrypt
  • AWS(EC2, RDS)

1.4 협업 툴

  • Git
  • Github
  • Trello
  • Slack

1.5 필수 구현 사항 (Front-end)

  • Navigation, Footer
  • 회원가입, 로그인 페이지
  • 제품 리스트 페이지
  • 제품 상세 페이지

2. 프로젝트 구현 사항

위에서 소개한 필수 구현 사항 중 나는 Footer, 회원가입 그리고 로그인 페이지를 맡아서 진행하게 되었다. 프론트 분들과 어떤 페이지를 맡으면 좋을지 의논하다가 회원가입과 로그인 페이지에 신경 써보는 것이 좋겠다는 생각이 들었다. 몇 주 전 개인적인 클론 코딩 프로젝트로 진행 하였던 Westagram 프로젝트 당시에도 로그인과 메인 페이지를 구현해보았는데 React에 익숙치 않았던 상태였기에 다시 한 번 그 과정을 복기하면서 추가적으로 새로운 기능을 구현하는 코드를 배워보고자 하였다.


  • Footer 레이아웃 구현
  • mockup 데이터 파일 생성 및 map() 메소드를 사용하여 Footer Navbar 구현

구독하기 기능은 아쉽지만 구현해보지 못하고 레이아웃만 작성하게 되었다.


2.2 회원가입(Register)

  • 회원가입 페이지 레이아웃 구현
  • mockup 데이터 파일 생성 및 map() 메소드, input 컴포넌트를 생성하여 input 구현
  • 비밀번호 유효성 검사 (정규식 표현 사용) (영문자 + 숫자 + 특수문자 포함)
  • 이메일 유효성 검사 (@. 포함)
  • 이메일 및 비밀번호 입력창 값의 조건 미충족 시 해당 input 하단에 에러 메세지 발생
  • 필수 입력 값 미기입 후 회원가입 버튼 클릭 시 alert 을 통하여 입력 요청 알림
  • 이미 등록된 아이디나 이메일은 alert 으로 알림
  • 서버와 API 통신을 통한 회원가입 기능

아이디나 이메일이 이미 가입 되어 있다면 존재하는 아이디나 이메일이라는 alert을 띄워주어 새로운 값을 넣도록 유도한다. 닉네임이나 주소 등의 필수 입력 사항이 아닌 것은 입력 시에 빨간 테두리가 뜨지 않도록 하였으며 입력을 하지 않아도 가입이 되도록 구현하였다.


2.3 로그인(Login)

  • 로그인 페이지 레이아웃 구현
  • JWT와 로컬스토리지를 사용한 로그인 기능 구현
  • 로그인 유효성 검사
  • Link 컴포넌트를 통하여 회원가입 창 이동 기능 구현
  • 아이디 혹은 비밀번호 미기입 후 로그인 버튼 클릭 시 alert 을 통하여 입력 요청 알림
  • 서버와 API 통신을 통한 로그인 기능

등록된 정보를 기입 후 로그인 버튼을 누르면 상품 리스트 페이지로 이동하게 된다.


3. 코드(리팩토링)

Footer 레이아웃을 만들면서 map() 메소드를 사용하여 반복되는 li 요소를 단 몇 줄로 줄일 수 있었는데 정말 놀라웠다. 이제까지 하드코딩만 하던 나였기에 map()과 같은 적절한 메소드나 코드를 사용하면 좀 더 깔끔한 코드에 다가갈 수 있겠구나 하는 깨달음을 얻었다.

Array.map() 메소드 사용 시 유의 사항
1. map을 사용할 때는 return 되는 JSX 요소마다 유니크한 key 값이 존재 해야한다.
2. key 속성은 제일 바깥에 있는 태그에 부여 해야한다.
3. 변수 선언 시 render 와 return 사이가 아닌 컴포넌트 밖에서 선언 해야한다. (보통 .js 혹은 .json 파일로 분리해서 데이터를 관리한다.)


// 리팩토링 이전
<ul className="footerNav">
  <li className="footerNavList">스카우트</li>
  <li className="footerNavList">회사소개</li>
  <li className="footerNavList">개인정보처리방침</li>
  <li className="footerNavList">영상정보관리지침</li>
  <li className="footerNavList">이용약관</li>
  <li>고객센터</li>
</ul>

// 리팩토링 이후
<ul className="footerNav">
  {Info.map(footernav => {
    return (
      <li key={footernav.id} className="footerNavList">
        {footernav.content}
      </li>
    );
  })}
</ul>
// data 파일
export const Info = [
  { id: 1, content: '스카우트' },
  { id: 2, content: '회사소개' },
  { id: 3, content: '개인정보처리방침' },
  { id: 4, content: '영상정보관리지침' },
  { id: 5, content: '이용약관' },
];

3.2 회원가입(Register)

내가 이번 프로젝트 동안 가장 시간을 할애했던 코드 부분은 바로 회원가입 페이지에서 map() 메소드를 사용한 input 요소를 컴포넌트로 만들어서 관리하는 것이었다. 앞서 Footer를 작업하면서 map() 메소드를 사용해보았기에 그래도 할만하지 않을까 생각하였지만 오산이었다. 컴포넌트는 그렇게 쉬운 개념이 아니었다. 아래 이미지의 표시된 부분이다.

우선 어려웠던 점을 꼽자면

  • 앞에 필수 입력 사항을 나타내는 ◾ 기호는 각각 어떻게 주어야 할지
  • input 앞에 적는 타이틀 (가령, 아이디, 비밀번호, 이메일 등의 텍스트)은 어떻게 처리해야하는지
  • input 각각의 type 은 어떻게 주어야할지
  • Error Message를 주는 것과 border 색이 빨간색으로 변하는 것은 어떻게 해야할지
  • 컴포넌트로 관리하게 된다면 각각의 value 값은 어떻게 받을 것인지
  • 각 value 값의 변화는 어떻게 감지할 것인지
  • 각 value 값에 유효성 검사는 어떻게 진행해야 할지

등 등 수많은 고민들이 나를 감쌌다. 😱

구글링 해보기도 하고 동기 분들이나 멘토님께 여쭤본 결과

  • map() 메소드 사용
  • 컴포넌트 사용
  • && 연산자를 활용한 조건부 렌더링
  • 삼항 연산자
  • className의 동적 사용
  • 계산된 속성명 (Computed Property Name)

등을 사용하여 해결할 수 있었다.

우선적으로 해야할 것은 아래 이미지와 같이 inputList mockdata 파일을 생성해주는 것이었다.
필수 입력 사항이 아닌 입력창 앞에는 ◾ 기호가 붙지 않도록 하기 위해서 데이터 파일에서 isNecessary 라는 key 값에 true나 false 값을 주고 부모 컴포넌트에서 자식 컴포넌트에 props 값으로 전달해주어야 했다.
props 받은 값은 자식 컴포넌트에서 && 연산자를 활용한 조건부 렌더링을 통하여 값이 true 일 때 ◾ 기호를 붙이도록 하였다.

위와 같은 방식으로 input 앞에 적는 타이틀, type 값, Error Message 값, value 값 등을 줄 수 있다.

map() 메소드로 바게트 빵을 썰듯이 inputList 데이터 파일의 key 값들을 input 이라는 인자로 받아서 name={input.name}, isNecessary={input.isNecessary} 처럼 각각의 속성 값에 props 로 전달해준다. 부모 컴포넌트에서 props 로 전달해주는 속성들을 자식 컴포넌트에서 name={name}, {isNecessary && <span className="smallSquare">▪</span>} 코드와 같이 받아준다.

// 부모 컴포넌트 파일
export class Register extends Component {
  constructor() {
    super();
    this.state = {
      inputList: [],
      userID: '',
      password: '',
      rePassword: '',
      name: '',
      nickname: '',
      email: '',
      contact: '',
      address: '',
    };
  }
  ...
  distributeValueToKey = e => {
    const { name, value } = e.target;
    this.setState({
      [name] : value,	// 계산된 속성명 (Computed Property Name)
    });
  };
...
render() {
    const { inputList } = this.state;
    return (
      ...
	<form className="formList">
            {inputList.map(input => {
              return (
                <Inputs
                  key={input.id}
                  name={input.name}
                  content={input.content}
                  inputType={input.inputType}
                  isNecessary={input.isNecessary}
                  placeholder={input.placeholder}
                  distributeValueToKey={this.distributeValueToKey}
                  validator={validator[input.name]}
                  value={this.state[input.name]}
                  errorMessage={input.errorMessage}
                />
              );
            })}
          </form>
// 자식 컴포넌트 <Inputs /> 파일
export class Inputs extends React.Component {
  transferInputEvent = e => {
    const { distributeValueToKey } = this.props;
    distributeValueToKey(e);
  };

  render() {
    const {
      isNecessary,
      content,
      name,
      value,
      inputType,
      placeholder,
      errorMessage,
      validator,
    } = this.props;
    const isValid = validator(value);
    return (
      <div className="subTitle">
        <div className="inputTitle">
          {isNecessary && <span className="smallSquare"></span>} // 조건부 랜더링
          {content}
        </div>
        <div className="input">
          <input
            // 조건부 렌더링, className의 동적 사용, 삼항 연산자
            className={value && !isValid ? 'borderRed' : 'borderNormal'}
            name={name}
            value={value}
            type={inputType}
            placeholder={placeholder}
            errormessage={errorMessage}
            onChange={this.transferInputEvent}
          />
          {value && !isValid && (
            <span className="errorMessage">{errorMessage}</span>
          )}
        </div>
      </div>
    );
  }
}

부모 컴포넌트에서 자식 컴포넌트의 input 값의 이벤트를 감지하는 함수 distributeValueToKey 를 만들어주고 자식 컴포넌트에 props로 전달을 해준다. 자식 컴포넌트에서는 그 값을 onChange={this.transferInputEvent} 속성으로 받고 입력창에서 변화가 감지되면 해당 함수를 실행하게 된다. 자식 컴포넌트 선언문과 render 메소드 사이에서 transferInputEvent 라는 이름의 함수를 만들어 준다. 함수의 실행문으로는 input 에서 일어나는 모든 이벤트 값이 부모 컴포넌트의 distributeValueToKey 함수에서 관리가 된다.
이 때 계산된 속성명을 사용하여 setState에 담기는 각각의 값들을 하나의 함수 안에서 관리할 수 있다. 특히 input 태그에는 name 이라는 속성명이 존재하는데 이 속성명 덕분에 여러 개의 함수로 관리해주어야 할 것을 한 번에 관리할 수 있게 된다. 아래는 계산된 속성명을 이용한 리팩토링 예시이다.

// Before 예시
handleEmailInput = e => {
  const { value } = e.target;
  this.setState({
    email: value,
  });
};

handlePwInput = e => {
  const { value } = e.target;
  this.setState({
    password: value,
  });
};
// After
distributeValueToKey = e => {
  const { name, value } = e.target;
  this.setState({
    [name]: value,
  });
};

유효성 검사를 진행하는 코드는 부모 컴포넌트의 render 메소드 영역 외부에서 validator 라는 변수를 선언하고 해당 변수에 유효성을 검사하는 함수들이 담긴 객체를 담아 역시나 props 로 자식 컴포넌트에 보내주었다.

// 객체 validator
const validator = {
  userID: value => value.length >= 4,
  password: value => {
    const pwRegExp =
      /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,20}$/;
    const result = pwRegExp.test(value);
    if (result === true) {
      return true;
    }
  },
  rePassword: value => value.length >= 2,
  name: value => value.length >= 2,
  nickname: value => value.length >= 0,
  email: value => value.includes('@') && value.includes('.'),
  contact: value => value.length >= 10,
  address: value => value.length >= 0,
};

자식 컴포넌트에서는 validator 를 props 로 받아서 const isValid = validator(value); 를 선언하고 isValid 변수와 value 값을 활용하여 각 입력창 마다 설정한 조건에 맞기 전까지 border 색을 빨간색으로 표시하고 해당 입력창 하단에 에러메세지를 띄울 수 있게 된다.


3.3 로그인(Login)

로그인 페이지를 구현할 때는 회원가입 페이지를 구현할 때보다는 훨씬 빠르게 구현할 수 있었다. Westagram 프로젝트 때에도 로그인 기능을 구현해본 경험이 있었고 회원가입 페이지 구현 시 필요한 기능이 로그인 페이지를 구현할 때도 적용할 수 있어서 빠르게 구현할 수 있었던 것 같다.

차이점이 있다면 회원가입 정보를 서버에 POST 로 넘겨줄 때와는 다르게 로그인 정보는 토큰 값을 이용하여 로컬 스토리지에 저장한다는 것이었다.
가령 로그인한 회원만 볼 수 있는 회원 전용 페이지 등 사용자의 토큰 값으로 인가해줄 수 있다.

  submitLoginForm = () => {
    const { idValue, pwValue } = this.state;
    fetch(`${API.login}`, {
      method: 'POST',
      body: JSON.stringify({
        user_name: idValue,
        password: pwValue,
      }),
    })
      .then(res => res.json())
      .then(result => {
        if (result.message === 'USER_DOES_NOT_EXISTS') {
          alert('아이디 혹은 비밀번호를 다시 확인해주세요.');
        } else {
          localStorage.setItem('token', result.token);
          this.goToMain();
        }
      });
  };

4. 소감

위코드에서의 첫 프로젝트였다보니 두려움 반 설레임 반의 마음으로 프로젝트에 임한 것 같다.

팀원들에게 혹여나 피해가 가지 않을까 걱정했지만 다행히 우리 워시팀원분들 한 분 한 분 모두 열정과 배려가 넘치시는 분들이셨기에 편안한 마음으로 함께할 수 있었다.

열정과 더불어 우리 팀의 자리 위치 때문인지 지나가는 동기분들마다 우리팀은 스타트업 회사 같다고 표현해주시곤 하셨다.
항상 늦게까지 팀원들이 많이 남아있거나 밤샘으로 프로젝트를 진행한 적도 있고, 주말에도 대부분 팀원들이 출석하시는 등 다들 열정 넘치는 모습들을 보여주셔서 더욱 그런 말을 들은 것 같다.
또한 우리 팀의 자리가 다른 팀들과는 다르게 안쪽 구석에 마련되어 있었는데 2명이 마주 보고 1명은 다른 팀원들을 바라볼 수 있는 자리로 구성 되어있었다. 내가 한 발 늦어서 바깥쪽의 1인 자리에 앉았는데 몇 몇 분들은 지나가실 때마다 부장님 자리에 앉아계신 것이냐며 농담을 던지시곤 하셨다. :) 그럴 때마다 저는 바지 부장이라며 너스레를 놓기도 하였다.

스타트업 회사 같다는 말이 듣기에는 좋았지만서도 한편으로는 그만큼 팀원들분들의 체력 관리가 잘 되지 못한 것 같아 아쉬었다. 각자 열심히 Sprint 를 달리고 심지어 마지막 주에는 나를 포함한 프론트 팀원들과 밤샘을 하게 되어 체력 관리가 제대로 되지 못한 것 같았다. 데드라인을 지키는 것도 무척 중요하지만 그와 함께 중요한 것은 체력 관리 문제였다고 생각한다. 이를 경험 삼아 2차 프로젝트 때에는 공통 규칙으로 체력 관리와 관련된 규칙을 세우는 것이 좋겠다.

저작권에 위배되지 않는 이미지를 어떻게 가져올까 생각하다가 팀원들과 직접 그림을 그리기로 했다. 각자 아이패드, 수채화 등의 도구를 가지고 멋진 이미지들을 만들어주셨다. 그 중 나는 아래 샴푸바 이미지 6장을 그렸다. 바쁜 와중에 그림까지 그리려니 시간이 촉박하였지만 멋지게 활용하게 되어 뿌듯하다.
Westagram 프로젝트 때부터 시작하여 1차 프로젝트까지 리액트를 사용하며 많은 깨달음과 절감 또한 느끼기도 하였다. 자바스크립트의 확장 문법인 jsx와 nesting 을 할 수 있는 sass, state로 값을 관리하고 반복되는 코드들을 효율적으로 관리하는 컴포넌트 등 다양한 개념들을 익히고 적용해볼 수 있었다. 익숙하지 않은 것을 만나는 것은 항상 어렵다. 하지만 1차 프로젝트가 끝나고 나서 다시 돌이켜 생각해보니 확실히 많은 부분에서 성장 하였다고 느꼈다. 이상할 것만 같았던 리액트 문법이 차차 익숙해지고 map 메소드와 컴포넌트를 분리하고 값을 props 로 전달하는 과정도 익숙해지기 시작했다.

2차 프로젝트는 이를 양분 삼아 복습한다는 생각으로 다시 나아가야겠다.

이번 프로젝트에 많은 도움을 주신 동기분들과 멘토님들께 다시 한 번 감사드린다는 말씀을 드리며 끝내고 싶다.

0개의 댓글