본문 바로가기

archive/Project

[MainProject] day12 (5/16)

1. 메세지 기능 미리보기(feat.socket.io)

2. 아직도 안한 태그 선택 완성 ... ing

3. 멘토링!!

4. 숙제 ...ing

5. 다시 내 개발작업~!!

 

#1 메세지 기능 미리보기(feat.socket.io)

오후 회의를 준비하기 위해 메세지 기능을 실시간으로 만드는 편이 좋을 것 같아서, 바드에게 물어보았다. 백엔드에게 메세지 보내고 받을 데이터 형태를 말해주어야 하는데 어떻게 만들면 좋을지 알려주기 위해서이다.

 

너무 인공지능에 의존하는 사람이 되는 것이 아닌가 걱정되지만, 아침에 바드에게 질문을 통해 실시간채팅을 생각보다 쉽게 구현할 수 있을 것 같다는 생각이 들어서 자신감이 생겼다.

답변 내용은 아래와 같다.


Sure, I can make an asynchronous app with the same mechanism. Here are the steps involved:

  1. Create a new React project.
  2. Install socket.io.
  3. Create a new file called index.js and add the following code:
 
import React from 'react';
import ReactDOM from 'react-dom';
import SocketIO from 'socket.io-client';

const App = () => {
  const [messages, setMessages] = useState([]);
  const [isTyping, setIsTyping] = useState(false);

  const handleMessage = (message) => {
    setMessages([...messages, message]);
    if (message.type === 'typing') {
      setIsTyping(true);
    } else {
      setIsTyping(false);
    }
  };

  return (
    <div>
      <h1>Asynchronous React Messenger</h1>
      <ul>
        {messages.map((message) => (
          <li key={message.id}>
            {message.text}
            {message.type === 'typing' && <span>Typing...</span>}
          </li>
        ))}
      </ul>
      <input
        type="text"
        placeholder="Enter message"
        onChange={(event) => {
          const message = event.target.value;
          socket.emit('message', message);
          setMessages([...messages, {text: message, type: 'sent'}]);
        }}
      />
    </div>
  );
};

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

const socket = new SocketIO('http://localhost:3000');
socket.on('message', (message) => {
  handleMessage(message);
});

socket.on('typing', () => {
  setIsTyping(true);
});

socket.on('stopTyping', () => {
  setIsTyping(false);
});

// This is the asynchronous part
const handleTyping = () => {
  socket.emit('typing');
};

const handleStopTyping = () => {
  socket.emit('stopTyping');
};

// This is how we call the asynchronous functions
const input = document.querySelector('input');
input.addEventListener('keyup', handleTyping);
input.addEventListener('blur', handleStopTyping);

Create a new file called style.css and add the following code:

body {
  font-family: sans-serif;
  margin: 0;
  padding: 0;
}

h1 {
  text-align: center;
}

ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

li {
  margin-bottom: 10px;
}

input {
  width: 50%;
  margin-bottom: 10px;
}
  1. Start the development server with the following command:
 
npm start
  1. Open a web browser and go to http://localhost:3000. You should see an asynchronous messenger app.

To send a message, type a message in the input field and press Enter. The message will be sent to all other connected users.

To receive messages, the app will listen for messages from the socket.io server. When a message is received, it will be added to the list of messages.

The app will also show a "Typing..." indicator when the other user is typing.

// 위 코드대로 하면.. 정말 타이핑 중에 Typing이라고 나오는거 맞는거지..??

Here is a diagram of the architecture of the app:

[Diagram of the architecture of the asynchronous React messenger app using socket.io]

//다이어그램 어딨어.. 나도 보여줘

The app has two main components: the frontend and the backend. The frontend is the part of the app that the user sees and interacts with. The backend is the part of the app that handles the communication with the socket.io server.

The frontend is written in React. React is a JavaScript library that makes it easy to create user interfaces. The frontend uses socket.io to communicate with the backend.

The backend is a simple Node.js server. The server listens for messages from the socket.io server and then sends those messages to all other connected users


 

#2 태그 선택 기능 완성

 

사실 어제 코드는 거의 짜놨고 화면에 보여지도록 추가를 안했다. 아니 추가를 했는데 확인을 안해본건가. 일단 npm start를 해서 확인해보도록 하겠다. 

 

flex도 하나도 안주고 div로만 태그들이 감싸져 있어서 엉망으로 생긴 것을 약간만 틀을 잡으면 벌써 그럴싸한 느낌이다.

 

이제 On/Off 스타일만 임의로 설정해주고, 클릭했을 때 배열에 상태가 true/false 잘 바뀌는지만 확인하면 된다. 현재는 버튼을 누르면 의도치않게 axios 요청이 간다.. 분명 등록에만 연결해놓은 것 같은데 왜 그런것일까..?

 

일단, 태그초기화 버튼만 axios 요청이 가고 다른 버튼은 다음과 같이 정상 동작되는 것을 확인하였다. 어떤 버튼을 눌러

이제 작은 과제들을 나열해보면,

1) 5개까지만 선택되도록 하기

2) 태그초기화 버튼 아이콘으로 바꾸기(되돌리기 아이콘)

3) 버튼들 색상 변경(팀원이 글로벌에 지정해놓은 색상 활용)

4) 태그초기화버튼 클릭 시 초기화만 작동하고 서브밋 동작하지 않도록 하기

5) 가장 중요!! multipart/formData file과 json 제대로 포함해서 보내기

 

4) 초기화만 작동되게 해보자!

e.preventdefault를 하면 되는걸까? 아니면 form안에 버튼을 넣어놓으면 다 감지해서 작동되도록 해놨으려나? 일단 e.preventdefault를 해보자.

 

5) multipart/formData file과 json 제대로 포함해서 보내기

업로드 되는 순간, e 객체를 살펴보니 file 이란 키와 filelist라는 키가 있다. 그런데 file 형식이 아니고 프로토타입이 전부 객체로 나온다.

이 객체를 파일로 바꾸는 방법이 있는 것인지... antd Upload 태그에서 파일을 멋대로 바꾸는거라 불가능 한 것인지, stackoverflow와 구글 바드(chatGPT 잠시 쉬도록 하렴...)를 활용해서 해보고자 한다.

 

팀원과 화면공유하면서 검색해본 stackoverflow 글을 먼저 살펴보자. 

https://stackoverflow.com/questions/71870446/antd-file-upload-validation-in-reactjs

 

Antd file upload validation in Reactjs

I am having a file upload using Antd design and it makes the file upload fine. <Upload {...props}> <Button icon={<UploadOutlined />}>Upload png only</Button> </Upload&g...

stackoverflow.com

validation이 주 주제인 글이다. 이 부분은 일단 보내지게 동작되면 추가하면 좋다고 멘토님도 짚어주셨다. multipart 보내는 것을 제대로 하는 것을 여기서 잠깐중단하고, 오늘 멘토링 내용도 복기해보고 숙제도 기록하겠다. 

 

#3 멘토링 요약

이번 멘토링 시간은 유독 도움이 된다고 느꼈는데, 일단은 주요 주제들을 정리해두고 필요한 부분을 몇 마디만 첨가하도록 하겠다. 블로그 글이 무한정 길어지는 느낌이다.

1. 사이드 프로젝트에서 template같은 것들을 어떻게 처리하는지 보고 참고해보자. - hint 글로벌스타일과 비슷하다
2. socket.io를 바로 적용할지 > 시간이 되면... 담당자의 능력에 달려있음. 시도해보고 안되면 차선책으로 마무리.
3. 라이브러리 사용과 관련하여 - 전부 손수 만드는 것은 현실성 없다. 라이브러리를 고르는 것도 능력 중 하나가 될 수 있기 때문에 그것도 의미가 있다.
추천하는 방법은!! 어떻게 동작하는지 파악하는 것이 제일 중요하다
라이브러리를 까보는 것이 어려워서 비슷하게 만든 코드를 보고 이해하는 것도 좋은 방법. >
https://youtu.be/1Lhfcz9MnmQ
4. ??님 캐러셀 자랑타임 - 퀄리티 대박
5. 디자인 시스템에 대해서 더 알아볼 것. 챠크라 같이 스타일드 컴포넌트와 잘 어울리는 것들을 활용할 것.
6. 푸터를 가리고 싶은 화면 > 레퍼런스 참고
7. CRUD : formValidation이나 글자수제한, 삭제나 수정의 권한문제 등 보안문제
8. 서버가 불안정한 상황같은 개발경험이 안좋아지는 경우에 대한 대처는 어떻게 해야하는지
9. 오류가 날 때 해당 부분의 원리파악 - 면접에서 말할 수 있는 정도까지 이번기회에 정리할 것
10. 텍스트 사이에 이미지를 넣는 것과 관련된 문제 - 레퍼런스 참고
여러가지 방법 중 가능한 것을 고민해보자.
백엔드 멘토를 활용하는 것도 좋겠다.
11. GPT같은 친구들을 잘 쓰는 방법을 고민하자.
12. https://wrtn.ai/ - drop down 옵션을 지능향상으로 해야 GPT 4 를 쓸 수 있다
13. 인강은 책처럼 필요한 부분만 골라듣자.
14. 부담없이 코드리뷰를 신청하자 ㅎㅎ
15. 머지하면 브랜치 자동삭제되는 설정도 있다고함.
16. 다들 화이팅!!

 

#4 숙제

숙제의 주제는 useInput을 아래와 같은 비효율적인 구조에서 Input Container에서 바로 사용할 수 없는것인가였다.

signup, signup Fields Container(SFC), signup Fields Input Container(SFIC)에서는 useInput 을 없애고 Input Container에서 Input으로 내려주는 정도만으로 사용할 수 있도록 만들어보겠다.

 

1. 일단 SignUp에서 SFC로 내려줄 때 이름만 바꿔서 그대로 내려주므로 큰 이슈가 없다. 그리고 SFC에서 SFIC로 내려줄 때는 갑자기 useInput안에 validity를 가지고 Input Container와 같은 위상의 자식인 validation message가 보여질지말지를 판단하기 때문에 여기서 막힌다. 틀렸다. Input Container로는 input이 어떤 타입인지 inputtype과 valid message만 내려주면 되고 validity는 InputContainer에서 판단하여 

 

라고 판단하였으나, SignUp에서 SFC로 내려주는 것을 없애려고하는데 문제가 발생하였다. 바로, SignUp 버튼을 누를 때 데이터 패칭을 할 username과 email 그리고 password value값을 prop으로 내려주는 객체에서 가져와서 보내도록 해놓았기 때문인데.. props drilling을 줄이려면 redux를 사용하거나 버튼 눌렷을 때 작동하는 함수들을 다른 파일로 빼서 해당 파일에서는 InputContainer에 있는 객체로부터 값을 가져와서 데이터를 패칭하도록 해야한다.

일단 이부분을 코드를 수정하고 SFIC에서 내려주는 것까지는 만들어보고 질문을 하든지 해결방법을 더 고민해보든지 하겠다.

 

SFIC말고 인풋컨테이너에서 useInput을 바로 적용하려고하니 데이터 패칭 시 username에 입력된 값과  email에 입력된 값, password에 입력된 값을 각각 가져오는 것에 문제가 있어서 redux를 사용하고 있으므로 스토어에 객체를 만들어서 해당 객체를 입력될 때마다 업데이트하고 패칭할 때 해당 객체를 가져오도록 수정하고자 한다. 그러면 Inputtype 별로 스토어에있는 다른 값을 업데이트 할 수 있고, 사용법이 완전 달라질 것이다. 재사용성이 높아지고 코드도 간결해질 것으로 예상된다.

 

현재 목표를 요약하면 다음과 같다.

 

변경 전

 

변경 후

초록색 박스가 prop와 관련된 것들인데 InputEl에서 사용된다고 표시한 것을 제외하고 비교해보면 7번 변수를 적으면서 내려주는 것보다 4번이나 적게 적을 수 있고 그에 더해서 validity에 대한 판단을 SFIC에서 하던 것도 없애줄 수 있기 때문에 절반보다도 더 간결하게 작성이 된다. 물론 리덕스 스토어나 슬라이스가 추가되고 dispatch를 해주고 useSelector를 활용하는 코드가 약간 늘어나지만 기존의 코드에 비하면 가독성도 훨씬 증가하고 무엇보다도 InputContainer가 재사용이 가능하게 바뀌어서 다른 사람들도 부담없이 사용할 수 있게 된다.

 

거기에 더해서 정말 재사용이 용이하게 만들려면, input 뿐만 아니라 textarea 등 상태관리와 validation이 필요한 것들에는 다 사용할 수 있도록 username, email, password 의 유형이 아닌 custom 유형을 추가하여 커스터마이징한 유효성검사 함수도 넣을 수 있게해야한다. 그리고 validation을 넣고 싶지않은 입력창에는 none 유형을 추가하여 입력값만 관리해주고 validation은 스킵할 수 있도록 해야한다. 해당 작업까지만해도 정말 지금 수준에서는 만족할만한 커스텀 훅이 만들어지고 팀원들도 그제서야 제대로 사용해볼 수 있을 것 같다. 팀원들이 사용하는 것을 떠나서 내가 당장 재사용하기도 불편했기 때문에 팀원들이 사용하지 않게 되더라도 매우 의미가 있는 작업이다.

 

바로 수정해보자.

순서를 정해서 차근차근 해보자.

1) 데이터 패칭 함수 분리

2) 스토어, 슬라이스 생성

3) SignUp 부터 SFIC까지 prop 내려주는거 전부 제거 - 필요없어진 타입,import 전부 제거

4) SFIC에서 내려줄 prop 재설정

5) IC에서 useInput 사용하도록 하고 prop 내려주는거 재설정, 타입설정

6) InputEl 에서 받은 prop 사용하는 로직 재설 여기는 그냥 두면 된다! 5)까지하면 끝난다.

 

 

인풋 종류에 따라 store 상태의 어떤 값을 변경할지 정해주어야하기 때문에 inputType은 SFIC에서 InputContainer로 넘겨주는 것으로 설정하였다. 그에 따라 validationmessage도 넘겨주는 것으로 정했다.

 

따라서 이렇게 약간 수정되어야 한다.

 

1) 데이터 패칭 함수 분리

utils
 ┣ EmailSignUpFunction.ts
 ┗ OauthSignUpFunction.ts

SignUp에 있던 함수들을 utils로 빼고 import해서 사용하는 것으로 바꿔주었다. (lazy.suspense)적용하면 최적화에 도움이 될 것 같다 싶다.

 

2) 스토어, 슬라이스 생성

스토어에는 다음과 같이 슬라이스를 추가해주면 되고,

import { configureStore } from '@reduxjs/toolkit';
import userSlice from '../slice/userSlice';
import signupSlice from '../slice/signupSlice';

const store = configureStore({
  reducer: {
    user: userSlice,
    signup: signupSlice
  }
});
export type RootState = ReturnType<typeof store.getState>;
export default store;

슬라이스는 다음과 같이 만들어주었다.

import { createSlice } from '@reduxjs/toolkit';
import { Signup } from '../types/dataTypes';

const initialStateSignup:Signup = {
  username: '',
  email: '',
  password: ''
}

const signupSlice = createSlice({
  name: 'signup',
  initialState: { ...initialStateSignup },
  reducers: {
    setSignupInfo(state, action) {
      state = action.payload;
    },
    clearSignupInfo() {
      return initialStateSignup;
    }
  }
});

export const { setSignupInfo, clearSignupInfo } = signupSlice.actions;
export default signupSlice.reducer;

 

3) SignUp부터 SFIC까지 전부 청소

일단 계획대로만 된다면 SignUp 페이지 파일이 스타일드 컴포넌트 코드를 제외하면 30줄 정도로 간략하게 줄었다.

import styled from 'styled-components';

import SignUpFieldsContainer from './SignUpFieldsContainer';
import SignUpTopWrapper from './SignUpTopWrapper';
import SignUpOauthContainer from './SignUpOauthContainer';
import SignUpButtonsContainer from './SignUpButtonsContainer';

import oauthSignUp from '../../utils/OauthSignUpFunction';
import emailSignUp from '../../utils/EmailSignUpFunction';

const SignUp = () => {
  return (
    <StyledSignUpContainer>
      <StyledSignUpFormWrapper>
        {/* top - component */}
        <SignUpTopWrapper />
        {/* Form - component */}
        <StyledSignUpFormContainer>
          {/* Oauth - component */}
          <SignUpOauthContainer onClick={oauthSignUp} />
          {/* Input - components */}
          <SignUpFieldsContainer />
          {/* Button - components */}
          <SignUpButtonsContainer onClick={emailSignUp} />
        </StyledSignUpFormContainer>
      </StyledSignUpFormWrapper>
    </StyledSignUpContainer>
  );
};

export default SignUp;

너무 깔끔해서 기분이 좋아진다.. 심지어 영역을 구분하는 주석이 없으면 25줄정도로 줄어든다.

 

그 다음은 SignUpFieldsContainer 이다. 여기도  싹 밀어준다.

지금은 이렇게 되었는데 여기서도 이제 placeholder부터 남아있는 속성들도 Inputcontainer에서 설정해주어서 제어가 가능한지 생각해보고 가능하다면 여기도 최대한 클린하게 비우는게 좋기 때문에 그렇게 해주었다.

import styled from 'styled-components';

import SignUpFieldsInputContainer from './SignUpFieldsInputContainer';

const SignUpFieldsContainer = () => {
  return (
    <StyledSignUpFieldsContainer>
      <SignUpFieldsInputContainer
        placeholder='ex)인디벗'
        title='닉네임'
        validmessage='한글/영문 2-10자로 작성해주세요.'
      ></SignUpFieldsInputContainer>
      <SignUpFieldsInputContainer
        placeholder='ex)inddy@gmail.com'
        title='아이디'
        validmessage='유효한 이메일 형식이 아닙니다.'
      ></SignUpFieldsInputContainer>
      <SignUpFieldsInputContainer
        placeholder='8자리 이상'
        title='비밀번호'
        validmessage='비밀번호는 8-16자 영문,숫자,특수문자의 조합이어야 합니다.'
      ></SignUpFieldsInputContainer>
    </StyledSignUpFieldsContainer>
  );
};

export default SignUpFieldsContainer;

const StyledSignUpFieldsContainer = styled.div`
  margin-bottom: 24px;
`;

지금은 이 상황이며,

SignUpFieldsInputContainer를 가보면 더욱 심각한게 InputContainer로 받은 것들을 그대로 내려준다.

이 상황에서 SFIC와 SFC를 하나로 합칠 필요성이 느껴진다. 그리고 SignUpFieldsInputContainer에다가 3개의 StyledInputContainer를 넣고 placeholder 등등을 여기서 넘겨주는 것이 불필요한 props drilling을 막을 수 있다.

 

다음과 같이 변경되었다.

하고 보여주려고 했는데 심각한 문제가 발생하였다.

현재시각 3시 31분 처절한 사투끝에.... 말도 안되는 곳에서 문제를 발견하였다...

심지어 빨간줄이 떠있다... InputContainer와 UseInput만 용의선상에 두고 뤼튼으로 chatGPT4.0을 처음 사용해보고... 대답을 열심히 해 준 구글 바드만 계속 갈구면서 정작... 내가 한 짓을 돌아보지 못했다.

첨부한 사진들은 정말 빙산의 일각이고 useValidation 문제인지 의심하며 useValidation > Validation > validation 까지 바꿨는데 당연히 될리가 없다...

 

오늘은 일단 여기까지... 그리고 이어지는 내용은 여기서 확인할 수 있다.