Web Study/Slack 클론 코딩

슬랙 클론 코딩 - 1

쿠리의일상 2023. 4. 21. 16:25

백엔드 세팅해주기 

MySQL : database(==Schema) -> table -> row 계층 구조

 

 

더보기

질문 잘하기!

 

- 에러 메세지를 정확히 

- 코드를 보여준다

- 줄넘김, 들여쓰기를 제대로한 코드를 첨부

- 어떤 작업을 하는 코드인지

- OS나 버전 기재

- 이미 시도해본 것들은 미리 기재

 

프론트 기본 세팅하기

.eslint

.prettierrc 등 기본 파일

 

컴파일?

tsc -> js

tsc -> babel -> js ✔️

 

babel 과 webpack 설정

..

 

아니 무슨 세팅이 이렇게 어렵나......그냥 따라치기만 했음

아직은 너무 어려운 개념 같다..

 

코드 스플리팅

보통 웹팩에 의해 자바스크립트 파일은 하나의 파일로 합쳐지게 되는데 모든 자바스크립트를 묶어서 빌드하면 파일의 크기가 커질 것이고, 1줄의 코드를 수정하더라도 다른 모든 js파일들을 새로 빌드해야하기에 좋지않다.

 

더욱이 라이브러리 같은 대부분의 코드는 거의 바뀔 일이 없기에 빌드한 파일을 그대로 사용해도 괜찮은 경우가 많다.

이렇듯 자주 바뀌지 않는 빌드에 대해 브라우저가 파일을 항상 새로 받을 필요가 없으므로 캐싱을 해주는 것이 좋다.

 

즉, 코드 스플리팅은 파일을 분리하는 작업이라고 보면 된다.

코드를 비동기적으로 로딩하는 대표적인 예시인 것

 

- 페이지 단위

- SSR이 필요없는 사이트 -> 서버의 용량과 트래픽을 줄일 수 있음

@loadable/component 를 사용하여 코드 스플리팅을 실행

 

 

Emotion

서버사이드 렌더링에 유리

styled-components와 유사

npm i @emotion/react @emotion/styled

 

import styled from '@emotion/styled';

 

import styled from '@emotion/styled';

export const Header = styled.header`
  text-align: center;
  font-family: Slack-Larsseit, Helvetica Neue, Helvetica, Segoe UI, Tahoma, Arial, sans-serif;
  font-weight: 700;
  font-size: 48px;
  line-height: 46px;
  letter-spacing: -0.75px;
  margin-top: 50px;
  margin-bottom: 50px;
`;

emotion은 타입스크립트 export 해서 첨부해준다.

 

import {Form, Header, Input, Label, LinkContainer, Button, Error} from './styles';

이렇게 넣어서 사용해줌

 

 

회원가입 페이지

기본적으로 입력되는 회원가입 인풋은 모두 state 와 onchange 로 관리해준다 특히 onchange 이벤트의 경우 useCallback 처리를 해줘야 리렌더링을 막아서 최적화가 가능하다고 한다.

 

const [email, setEmail] = useState('');
  const [nickname, setNickname] = useState('');
  const [password, setPassword] = useState('');
  const [passwordCheck, setPasswordCheck] = useState('');
  const [mismatchError, setMismatchError] = useState(false);

각각의 인풋 state

 

const onChangeEmail = useCallback((e) => {
    setEmail(e.target.value);
  }, []);

  const onChangeNickname = useCallback((e) => {
    setNickname(e.target.value);
  }, []);

  const onChangePassword = useCallback((e)=>{
    setPassword(e.target.value);
    setMismatchError(e.target.value !== passwordCheck);
  },[passwordCheck]);

  const onChangePasswordCheck = useCallback((e) => {
    setPasswordCheck(e.target.value);
    setMismatchError(e.target.value !== password);
  }, [password]);

  const onSubmit = useCallback((e) => {
    e.preventDefault(); //a 태그나 submit 태그는 누르면 href 를 통해 이동하거나 새로고침되는데 이를 막아주기 위함
    console.log(email, nickname, password, passwordCheck);

    if(!mismatchError) {
      console.log('서버로 회원가입처리');
    }
  }, [email, password, nickname, passwordCheck]);

회원가입을 위한 비밀번호가 서로 다르면 일치하지 않다고 만들기 위하여 mismatchError 상태를 조작해준다.

다만 위같은 경우에는 비밀번호가 아예 입력이 되지 않았을 때 회원가입이 가능하게 되므로 다른 처리 추가해줘야 함

 

 

Custom Hooks

이메일과 닉네임 부분의 쓰임새가 비슷한 것을 볼 수 있다.

이 경우 커스텀 훅을 만들어줄 수 있는데,

최대한 틀을 그대로 가져와준다.

 

커스텀 훅은 여러 기본 훅들을 묶어서 한번에 표기하거나 원하는 훅을 만들 수 있는 것임

import { useCallback, useState } from "react"

const useInput = (initialData) => {
  const [value, setValue] = useState(initialData);
  const handler = useCallback((e)=> {
    setValue(e.target.value);
  }, []);

  return [value, handler, setValue];
}

export default useInput;

 

  const [email, setEmail] = useInput('');
  const [nickname, setNickname] = useInput('');

const onChangeEmail = useCallback((e) => {
    setEmail(e.target.value);
  }, []);

  const onChangeNickname = useCallback((e) => {
    setNickname(e.target.value);
  }, []);

--------- 변경

import useInput from "@hooks/useInput";

  const [email, onChangeEmail] = useInput('');
  const [nickname, onChangeNickname] = useInput('');
  
  // 위의 onChange 이벤트용 함수들을 생략해줄 수 있게 되었다.

 

 

비밀번호 부분도 결국 입력을 받는 부분이므로 만들어준 커스텀 훅을 사용한다.

  const [password, , setPassword] = useInput('');
  const [passwordCheck, , setPasswordCheck] = useInput('');
  
  const onChangePassword = useCallback((e)=>{
    setPassword(e.target.value);
    setMismatchError(e.target.value !== passwordCheck);
  },[passwordCheck]);

  const onChangePasswordCheck = useCallback((e) => {
    setPasswordCheck(e.target.value);
    setMismatchError(e.target.value !== password);
  }, [password]);
  
  // 닉네임, 이메일과 달리 비밀번호쪽은 원래의 onChange 이벤트가 상이하므로 받아오지 않아줌을 알 수 있다.

 

그리고 타입스크립트를 사용하여 훅을 업뎃하기

보통 변수의 타입은 타입 추론에 의해,

함수의 리턴타입 또한 타입 추론에 의해 굳이 지정해주지 않아도 되지만

 

매개변수의 타입은 타입 추론이 잘 되지 않아서 작성해줄 필요가 있다.

이때 어떤 값이 매개변수로 들어올지 확인이 어려우므로 간단하게 any를 써주거나

any보단 좀더 상세하게 타입을 정해주고 싶다면 제네릭 타입을 <T> 사용해준다.

<T = 타입> (파라미터 :T) 형태임

추가로 해당 함수의 리턴타입은 state, 핸들러, setState 순이므로 리턴타입 또한 [T, (e :any) => void, Dispatch<SetStateAction<T>>] 로 나타내줄 수 있다.

이제 해당 훅은 3가지 요소를 리턴해주므로 필요할 때마다 지정해준다.

import { Dispatch, SetStateAction, useCallback, useState } from "react"

const useInput = <T = any>(initialData : T) :[T, (e:any) =>void, Dispatch<SetStateAction<T>>] => {
  const [value, setValue] = useState(initialData);
  const handler = useCallback((e)=> {
    setValue(e.target.value);
  }, []);

  return [value, handler, setValue];
}

export default useInput;

 

 

리덕스 없이 데이터 보내기

리덕스는 전체적인 상태를 관리할 때 사용하는데

비동기 코드 부분은 컴포넌트에 남기지 않기 위해 사가? 를 사용한다고 한다.

 

하지만 슬랙 클론에선 컴포넌트와 비동기 코드 부분을 나누지 않을 것이며 리덕스를 사용하지도 않을 예정이라 

회원가입 부분에서만 사용해주는 비동기 부분이라면 컴포넌트와 비동기 코드 부분을 혼용하여 써주기

 

리덕스의 경우 코드가 너무 길어지므로 깔끔하게 사용하기 위함이라고도 함

 

 

axios 사용 

백엔드에서 정보를 받아오기 위한 REST API (백엔드 API 설명서)

POST /users
- 회원가입
- body: {email: string, nickname: string, password: string}
- return 'ok'

성공하면 ok 라고 요청이 올 것이며

POST 형태로 /users 라는 주소로

body에 3가지를 담아서 보내라는 의미

 

if (!mismatchError) {
      console.log('서버로 회원가입처리');
      axios.post('http://localhost:3095/api/users', {
        email, nickname, password
      })
        .then((res) => {
          console.log(res);
          // return res;
        })
        .catch((err) => {
          console.log(err.response);
        })
        .finally(() => { });
    }

REST API 를 확인하고 axios 를 사용하여 요청을 보낸다.

axios.요청메서드('주소', {body에 보내질 객체}) 형태로 제대로 요청이되면 then(), 에러가 발생하면 catch(), 무조건 실행하는 부분은 finally() 이다.

 

그리고 보내지는 결과는 개발자 도구의 Network 탭에서 확인할 수 있으며

POST 요청 말고 OPTION 이라는 요청이 또 보내졌다. 이는 cors 문제로, 백엔드와 프론트의 주소가 다르므로 발생하는 에러이며

cors 설정을 해줘야 에러가 발생하지 않는다.

 

만약 백쪽에서 cors 설정을 해주지 않아서 아래와 같은 오류가 발생하면

 

백엔드쪽에서

app.use(
    cors({
      origin: true,
      credentials: true,
    })
  );

위처럼 cors 설정을 해주거나

 

 

또는 프론트쪽에서 webpack.config.ts 파일의 devServer 부분에 proxy 설정을 추가해주는 방법이 있다.

  devServer: {
    historyApiFallback: true, // react router
    port: 3090,
    devMiddleware: { publicPath: '/dist/' },
    static: { directory: path.resolve(__dirname) },
 //////////////
    proxy: {
      '/api/': {
        target: 'http://localhost:3095',
        changeOrigin: true,
      },
    },
 //////////////
  },

해당 프록시의 설정의 의미는 프론트에서 api 로 전달하는 요청들은 주소를 3095(서버쪽 포트번호)로 바꿔서 보낸다는 의미임

 

 

 

화면에서 보여주는 서버의 메시지 보이기

화면에 보여주기 위해서 useState 를 사용

 

    if (!mismatchError) {      
      setSignUpError('');
      setSignUpSuccess(false);
      axios.post('http://localhost:3095/api/users', {
        email, nickname, password
      })
        .then((res) => {
          console.log(res);
          setSignUpSuccess(true);
        })
        .catch((err) => {
          console.log(err.response);
          setSignUpError(err.res.data);
        })
        .finally(() => { });
    }
  }, [email, password, nickname, passwordCheck]);

 

          {mismatchError && <Error>비밀번호가 일치하지 않습니다.</Error>}
          {!nickname && <Error>닉네임을 입력해주세요.</Error>}
          {signUpError && <Error>{signUpError}</Error>}
          {signUpSuccess && <Success>회원가입되었습니다! 로그인해주세요.</Success>}

 

회원가입까지 완료!

 

어렵다..ㅠㅠ