땡독 프로젝트

히치키치·2022년 1월 19일
0

✔ 개요

1. 페이지 컴포넌트 - 라우팅

회원가입 : RegisterPage : "/register"
로그인 : LoginPage : "/login"
포스트 작성 : WriterPage : "/write"
포스트 읽기 : PostPage : "/@username/:postId"
포스트 목록 : PostListPage : ["/@:username","/"]

2. styled-components 사용

컴포넌트 단위로 일정한 스타일을 적용하기 위해!
컴포넌트가 아닌 것에 스타일 적용시 CSS selector 활용

3. redux 사용

리덕스 스토어 생성 후 Provider 컴포넌트를 통해 리덕스 적용
Ducks 패턴 사용해 리덕스 모듈 작성

4. UI 개발

components 디렉토리 -> 프레젠테이셔널 컴포넌트
ㄴ common 디렉토리 -> 재사용되는 컴포넌트 (Button)
ㄴ auth 디렉토리 -> 회원 인증 관련 컴포넌트 (AuthForm, AuthTemplate)
ㄴ write 디렉토리 -> 글쓰기 관련 컴포넌트
ㄴ post 디렉토리 -> 포스트 읽기 관련 컴포넌트
ㄴ base 디렉토리 -> 프로젝트 기반 관련 컴포넌트 (Header)

5. Snippet 사용해 컴포넌트 작성 중 반복되는 부분 자동 작성

1) Snippet 코드 작성
https://snippet-generator.app/ 사용
확장자를 제외한 파일 이름을 의미하는 ${TM_FILENAME_BASE} 쿼리를 통해 Snippet 코드 작성
Snippet 설명 (Description) : Styled React Functional Component
Snippet 줄임 단어 (Tab trigger) : srfc

2) VS Code 설정
file -> preferences -> user snippets
언어는 javascriptreact 사용, JSON 파일에 {}로 감싸서 Snippet 코드 복붙하기!

3) 컴포넌트 생성시 사용
js 파일 생성 후 언어를 javascriptreact로 바꾸기
Snippet 줄임 단어로 설정한 srfc 치면 자동 완성됨!

✔ 과정

1. 기본 컴포넌트 / 라우팅 / UI

AuthTemplate 컴포넌트 생성
child 랜더링 되도록 작성

 //AuthTemplate.js
 import React from "react";
import styled from "styled-components";

// 회원가입/로그인 페이지의 레이아웃 담당하는 컴포넌트

const AuthTemplateBlock = styled.div``;

const AuthTemplate = ({children}) => {
  //children 랜더링
  return <AuthTemplateBlock>{children}</AuthTemplateBlock>;
};

export default AuthTemplate;

AuthForm 컴포넌트 생성

//AuthForm.js
import React from "react";
import styled from "styled-components";

// 회원가입 또는 로그인 폼

const AuthFormBlock = styled.div``; //최상위 컴포넌트로 Block 붙임

const AuthForm = () => {
 return <AuthFormBlock>AuthForm</AuthFormBlock>;
};

export default AuthForm;

LoginPage와 RegisterPage 랜더링 확인
AuthForm과 AuthTemplate 사용하는 컴포넌트의 랜더링 확인
http://localhost:3000/register

http://localhost:3000/login

AuthTemplate 부모 영역 UI
배경은 회색, 중앙에 흰색 박스 띄우고, 홈 경로 /로 돌아가기

AuthTemplate에는 input from 으로 회원가입이랑 로그인이 들어갈거임

AuthForm 스타일링

  1. input form 스타일링
  2. 로그인하는 창인 경우 회원가입으로 이동, 회원가입하는 창인 경우 로그인으로 이동하는 글자? Footer? 스타일링
//AuthForm.js

(...)
//필요한 정보 입력하는 input 스타일링
const StyledInput = styled.input`
  font-size: 1rem;
  border: none;
  border-bottom: 1px solid ${palette.gray[5]};
  padding-bottom: 0.5rem;
  outline: none;
  width: 100%;
  &:focus {
    color: $0c-teal-7;
    border-bottom: 1px solid ${palette.gray[7]};
  }
  & + & {
    margin-top: 1rem;
  }`;

//로그인에서 회원가입 이동, 회원가입에서 로그인 이동하는 Footer 스타일링
const Footer = styled.div`
  margin-top: 2rem;
  text-align: right;
  a {
    color: ${palette.gray[6]};
    text-decoration: underline;
    & :hover {
      color: ${palette.gray[9]};
    }
  }
`;

  1. 버튼 컴포넌트에 폭과 색상을 props로 스타일링
//components/common/Button.js


const StyledButton = styled.button`

//기본 버튼 스타일링
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  font-weight: bold;
  padding: 0.25rem 1rem;
  color: white;
  outline: none;
  cursor: pointer;

  background: ${palette.gray[8]};
  &:hover {
    background: ${palette.gray[6]};
  }


//button컴포넌트에 props로 full width 설정 시 적용 스타일 (너비)
  ${(props) =>
    props.fullWidth &&
    css`
      padding-top: 0.75rem;
      padding-bottom: 0.75rem;
      width: 100%;
      font-size: 1.125rem;
    `}


//button 컴포넌트에 props로 cran 설정 시 적용되는 스타일 (색상)
  ${(props) =>
    props.cran &&
    css`
      background: ${palette.cyan[5]};
      &:hover {
        background: ${palette.cyan[4]};
      }
    `}

버튼 자체에 필요한 스타일링 (너비/색상) Button 컴포넌트에 작성
실제 정보를 입력하는 input과 button 사이의 필요한 마진은 AuthForm에 작성


//component/auth/AuthForm.js

//버튼 위로 마진 넣어 input과 공백 생성
const ButtonWithMarginTop = styled(Button)`
  margin-top: 1rem;
`;

(...)
 
 //버튼의 props로 색상(cran)과 너비(fullWidth) 설정
 //Styled-css로 버튼 위 마진 부여
 <ButtonWithMarginTop fullWidth cran >
 로그인
 </ButtonWithMarginTop>
 
(...)
  1. input 스타일링은 유사하지만 로그인과 회원가입에 따라 필요한 form 필드는 다르니 페이지 타입(로그인/회원가입)에 따라 같은 스타일에 다른 구조의 폼 나오도록 작성
//components/auth/AuthForm.js

(...)
 
 //페이지 컴포넌트에서 props를 전달하는 방식으로 페이지 타입 명시해 전달
 const textMap = {
  register: "회원가입",
  login: "로그인",
};


const AuthForm = ({ type }) => {
  const text = textMap[type];
  return (
    <AuthFormBlock>
      <h3>{text}</h3>
      <form>
        <StyledInput
          autoComplete="username"
          name="username"
          placeholder="아이디"
        />
        <StyledInput
          autoComplete="new-password"
          name="password"
          placeholder="비밀번호"
          type="password"
        />

        {/*회원가입 부분에는 비밀번호 확인하는 칸 필요*/}
        {type === "register" && (
          <StyledInput
            autoComplete="new-password"
            name="passwordConfirm"
            placeholder="비밀번호 확인"
            type="password"
          />
        )}
        <ButtonWithMarginTop
          /*cran로 props로 설정해 버튼 색깔 파란색으로 스타일링 가능 */ fullWidth
        >
          로그인
        </ButtonWithMarginTop>
      </form>
      <Footer>
        {/*회원가입이면 클릭해 로그인으로 감, 로그인이면 클릭해 회원가입으로 감 */}
        {type === "login" ? (
          <Link to="/register">회원가입</Link>
        ) : (
          <Link to="/login"> 로그인</Link>
        )}
      </Footer>
    </AuthFormBlock>
  );
};

export default AuthForm;

로그인/회원가입 페이지에서 props를 통해 페이지 타입 전달

로그인 페이지

import React from "react";
import AuthForm from "../components/auth/AuthForm";
import AuthTemplate from "../components/auth/AuthTemplate";

const LoginPage = () => {
  return (
    <AuthTemplate>
      <AuthForm type="login" />
    </AuthTemplate>
  );
};

export default LoginPage;

회원 가입 페이지

import React from "react";
import AuthForm from "../components/auth/AuthForm";
import AuthTemplate from "../components/auth/AuthTemplate";

const RegisterPage = () => {
  return (
    <AuthTemplate>
      <AuthForm type="register" />
    </AuthTemplate>
  );
};

export default RegisterPage;

2. 리덕스로 폼 상태관리

modules/auth.js

import { createAction, handleActions } from "redux-actions";
import produce from "immer";

const CHANGE_FIELD = "auth/CHANGE_FIELD";
const INITIALIZE_FORM = "auth/INITIALLIZE_FORM";

export const changeField = createAction(
  CHANGE_FIELD,
  ({ form, key, value }) => ({
    form, //register,loign
    key, //username, password, passwordConfirm
    value, //실제 바꾸려는 값
  }),
);

export const initializeForm = createAction(INITIALIZE_FORM, (form) => form);
//register, login

const initialState = {
  register: {
    username: "",
    password: "",
    passwordConfirm: "",
  },

  login: {
    username: "",
    password: "",
  },
};

const auth = handleActions(
  {
    [CHANGE_FIELD]: (state, { payload: { form, key, value } }) =>
      produce(state, (draft) => {
        draft[form][key] = value;
        //state.register.username 바꾸기
      }),
    [INITIALIZE_FORM]: (state, { payload: form }) => ({
      ...state,
      [form]: initialState[form],
    }),
  },
  initialState,
);

export default auth;

컨테이너 컴포넌트 : LoginForm 컴포넌트

container/auth/LoginForm.js

import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { changeField, initializeForm } from "../../modules/auth";
import AuthForm from "../../components/auth/AuthForm";

const LoginForm = () => {

  //컴포넌트를 리덕스와 연동
  const dispatch = useDispatch();
  const { form } = useSelector(({ auth }) => ({
    form: auth.login,
  }));
  //input 변경 이벤트 핸들러
  const onChange = (e) => {
    const { value, name } = e.target;
    dispatch(
      changeField({
        form: "login",
        key: name,
        value,
      }),
    );
  };

  //폼 등록 이벤트 핸들러
  const onSubmit = (e) => {
    e.preventDefault();
    //구현 예정
  };

  //컴포넌트가 처음 랜더링될 때 form 초기화함
  //useEffect 사용해 렌더링 후 initializeForm 액션 생성 함수 호출
  useEffect(() => {
    dispatch(initializeForm("login"));
  }, [dispatch]);

  return (
    <AuthForm
      type="login"
      form={form}

      //액션을 디스패치함
      onChange={onChange}
      onSubmit={onSubmit}
    />
  );
};

export default LoginForm;
  • useDispatch와 useSelector 사용해 컴포넌트와 리덕스 연동
const LoginForm = () => {

  //컴포넌트를 리덕스와 연동
  const dispatch = useDispatch();
  const { form } = useSelector(({ auth }) => ({
    form: auth.login,
  }));
  (...)
  • 필요한 액션을 디스패치 할 함수를 컴포넌트에 props 작성 : onChange, onSubmit
  //input 변경 이벤트 핸들러 함수 작성
  const onChange = (e) => {
    const { value, name } = e.target;
    dispatch(
      changeField({
        form: "login",
        key: name,
        value,
      }),
    );
  };

  //폼 등록 이벤트 핸들러 함수 작성
  const onSubmit = (e) => {
    e.preventDefault();
    //구현 예정
  };

액션 디스패치

  return (
    <AuthForm
      type="login"
      form={form}

      //액션을 디스패치함
      onChange={onChange}
      onSubmit={onSubmit}
    />
  );

⭐ useEffect 사용해 처음 랜더링 후 initializeForm 액션 생성 함수 호출 ⭐
로그인 페이지에서 값 입력 후 다른 페이지로 갔다가 다시 로그인 페이지로 돌아왔을 때 입력했던 값이 남아 있지 않도록 함!!

  //컴포넌트가 처음 랜더링될 때 form 초기화함
  //useEffect 사용해 렌더링 후 initializeForm 액션 생성 함수 호출
  useEffect(() => {
    dispatch(initializeForm("login"));
  }, [dispatch]);
  • LoginPage에 작성한 LoginForm 넣기

pages/LoginPage.js

import React from "react";
import AuthForm from "../components/auth/AuthForm";
import AuthTemplate from "../components/auth/AuthTemplate";
import LoginForm from "../containers/auth/LoginForm";

const LoginPage = () => {
  return (
    <AuthTemplate>
      <LoginForm />
    </AuthTemplate>
  );
};

export default LoginPage;
  • LoginForm 컨터이너에 props로 onChange, onSubmit, form 사용

component/auth/AuthForm.js

onChange, onSubmit, form의 Props로 받아오기

const AuthForm = ({ type, form, onChange, onSubmit }) => {
  const text = textMap[type];
  return (...
  • form 제출로 onSubmit
  • input 입력 값에 대해 onChange
  • 입력 값은 입력 칸에 따라
    유저 이름 - form.username, 비밀번호 - form.password, 비밀번호 확인 - form.passwordConfirm
 <form onSubmit={onSubmit}> //form 제출
        <StyledInput
          autoComplete="username"
          name="username"
          placeholder="아이디"
          onChange={onChange} //input 입력
          value={form.username} //유저 이름 값
        />
        <StyledInput
          autoComplete="new-password"
          name="password"
          placeholder="비밀번호"
          type="password"
          onChange={onChange} //input 입력
          value={form.password} //비밀번호 값
        />

        {/*회원가입 부분에는 비밀번호 확인하는 칸 필요*/}
        {type === "register" && (
          <StyledInput
            autoComplete="new-password"
            name="passwordConfirm"
            placeholder="비밀번호 확인"
            type="password"
            onChange={onChange} //input 입력
            value={form.passwordConfirm} //비밀번호 확인 값
          />
        )}
        <ButtonWithMarginTop fullWidth
        >
          로그인
        </ButtonWithMarginTop>
      </form>

로그인 페이지 리덕스 연결 확인

컨테이너 컴포넌트 : RegisterForm 컴포넌트

0개의 댓글