2주차(목) - 프리 온보딩 코스 프론트엔드 - 기업 과제 회고

minbr0ther·2022년 2월 13일
0

pre-onboarding-fe

목록 보기
9/15
post-thumbnail

이번 과제는 '요구사항을 바탕으로 메신저' 을 만드는 것이였다.

이번에는 따로 주어진 디자인, 이미지가 없어서 기업의 서비스를 이용하면서 참고를 했다.

2주차(목) 수업시간에 배운 Redux의 활용을 목표로 하였다.

기능은 크게 prompt를 활용한 로그인, 답장(템플릿), 삭제 등이 있었다.

👨🏻‍💻 전체적인 개발 순서

0. 프로젝트 세팅

  • CRA (create-react-app)

  • ESLint, Prettier

  • Redux, Styled-components

  • README.md 초안 작성

  • 절대경로 세팅 src/jsconfig.json

  • 기능별 components 나누기

  • git repo 생성, 슬랙에서 repo subscribe

    • slack git subscribe pre-onboarding-course-team-6/{레포이름}

1. Redux 세팅

요구사항

  • 메시지의 데이터 모델에는 userId, userName, profileImage, content, date 등이 있습니다.

  • 대화목록은 미리 생성된 데이터로 3명이 5건의 메시지를 주고 받는 내용이 출력됩니다.

const initState = {
  user: {   // 로그인 사용자 정보
    id: 4,
    name: '',
  },
  messages: [  // 기본 메시지 5개
    {
      userId: 1,
      userName: '정민형',
      profileImage: 'https://github.com/minbr0ther.png',
      content: '안녕하세요 저는 정민형입니다.',
      date: '2022-02-10 19:19:03',
      messageId: 1,
    },

    // 생략

    {
      userId: 2,
      userName: '김선명',
      profileImage: 'https://github.com/BGM-109.png',
      content: '반가워요',
      date: '2022-02-10 19:22:03',
      messageId: 4,
    },
  ],
  text: '', // input message 상태관리
  reply: '',  // reply message 상태관리
};
export const LOG_IN = 'LOG_IN'; // 로그인
export const ADD_MESSAGE = 'ADD_MESSAGE'; // 메세지 추가
export const DELETE_MESSAGE = 'DELETE_MESSAGE'; // 메세지 삭제
export const SET_MESSAGE = 'SET_MESSAGE'; // 메시지 onChange
export const SET_REPLY = 'SET_REPLY'; // 답장 메시지 세팅

⚙️ 구현한 기능 설명

1. prompt 로그인


기능 요구 사항에는 없지만, 멘토 김예리님이 메신저를 사용하기전에 기본적으로 이름을 갖고가면 좋을 것 같다는 의견을 주셨다.

로그인 페이지를 만들어도 실질적으로 기능에서 필요한 것은 이름밖에 없을 것 같다는 생각이 들어서.

심플하게 이 기능만 수행할 수 있는 prompt를 생각했고 구현할 수 있었다.

useEffect(() => {
  let userInput;
  while (true) {
    userInput = prompt('사용자 이름을 입력해주세요.');

    // 입력받은 값이 있으면 while문 탈출
    if (userInput !== null && userInput.length > 0) break;
  }
  dispatch(logIn(userInput)); // redux store에 이름 저장
  
  alert(`반갑습니다 ${userInput}님 😀`);
}, []); // Component가 mount 됐을 때 (처음 나타났을 때)

2. 입력창에서 shift + enter 하면 개행, enter는 전송

키 이벤트를 사용하는 기능 구현이였는데, 조건이 두개밖에 안되지만 삽질을 오래했던 것 같다.

우선순위를 잘 따져서 if조건문으로 걸러주는게 key point 였다.

그냥 enter를 입력받았을때는 1. 개행이 되지 않아야 한다, 2. 입력한 메시지가 있을때 전송처리를 해줘야 한다.

shift+enter를 입력받았을때는 1. 전송이 되지 않아야 한다, 2. 개행이 되어야 한다.

<MessageInput
  type="text"
  placeholder="Enter message"
  name="message"
  onChange={onChange}
  onKeyDown={handleKeyPress} 
   // onKeyUp으로 하면 입력이 끝났을때를 기준으로 해서 의도치 않은 오류가 날 수 있음🚨
/>

const handleKeyPress = (e) => {
  // shift + ender 하면 return, 개행 적용
  if (e.key === 'Enter' && e.shiftKey) {
    return;
  }

  // 그냥 enter 입력하면 
  if (e.key === 'Enter') {
    e.preventDefault();

    // 메시지 입력을 하지 않았을때 enter누르면 반환
    if (text.length === 0) return;

    onSubmit(e);
  }
};

3. 메세지 답장 기능

  • 답장을 클릭하면 사용자 이름\n" + "메시지 내용\n" + "(회신)\n 문자가 입력창에 자동으로 삽입됩니다.

  • \n 개행, 입력창에 내용이 존재할때는 입력된 내용 앞에 입력됩니다.

답장 기능은 그냥 단순하게 메시지 앞에 해당 템플릿을 추가하면 되는줄 알았다.

하지만 답장을 계속 누르면 메시지 앞에 계속 추가되는 현상이 있었고, 다른 방법을 찾게 되었다.

reply(답장 템플릿), text(input에 입력한 메시지)의 상태를 분리해서 관리하고,

답장의 변경사항이 있으면 text에 reply+text로 변경해줄 수 있도록 하였다.

// message/index.jsx

// ⚙️ 1. 답장 클릭 이벤트
const reply = useSelector((state) => state.messageReducer.reply);
const text = useSelector((state) => state.messageReducer.text);

const replyClick = (e) => {
  e.preventDefault();

  // 이전에 선택한 답장메시지가 새로 답장을 원하는 메시지와 일치하지 않으면,
  if (reply !== `${userName}\n${content}\n회신\n`) {
    // input의 이전에 선택한 답장메시지를 공백('')으로 바꾼다.
    dispatch(setMessage(text.replace(reply, '')));
    
    // 새로 선택한 메시지의 정보를 기반으로 state에 기록한다
    dispatch(setReply(message));
  }
};

// messageReducer (참고용)
case SET_REPLY:
  const userInfo = action.payload;
  const msg = `${userInfo.userName}\n${userInfo.content}\n회신\n`;
  return { ...state, reply: msg };

case SET_MESSAGE:
  return {
    ...state,
    text: action.payload,
  };

// ⚙️ 2. reply가 변경되면 바로 setMessage로 답장메시지 + 기존의 text를 바꿔준다.
useEffect(() => {
  dispatch(setMessage(reply + text));
}, [reply]);

🤔 후기

  1. 오랜만에 css를 담당했는데 감각이 떨어져서 빠르게 진행하지 못했다. (자주 자주 해보는 수밖에 없는 것 같다)

  2. Redux를 사용해서 진행하는 첫 번째 프로젝트였고 역시나 삽질하는 시간이 길었다. Todo list를 만들어본 경험 밖에 없었는데 이번 과제를 진행하면서 좀 더 깊게 알 수 있었다. 아래의 a, b는 내가 했던 실수를 간략히 정리해 본 것이다.

    a. eslint & redux 충돌이 있어서 reducer에서 파라미터를 (state = initState, action) 순으로 받아야 하는데 eslint 설정에서 default 파라미터는 뒤에 받으라고 해서 (action, state = initState)으로 받았고 알 수 없는 에러들을 많이 볼 수 있었다 🤯

    b. combineReducers를 사용하면, state.{사용하는 리듀서}.{state 내부접근} 순으로 해야한다. 그런데 {사용하는 리듀서}를 빼먹고 state.{state 내부접근} 이렇게 접근해서 오류가 있었다.

  3. 이번에도 컴포넌트를 분리해서 역할분담을 하는데 어떻게 나눠가질지 정하기 어려워서 페어프로그래밍을 진행했다. 혼자 했으면 막히는 부분이 많았을텐데 서로 의견과 아이디어를 공유하면서 빠르게 과제를 끝낼 수 있었다.

profile
느리지만 꾸준하게 💪🏻

0개의 댓글