실무용 폴더구조(container / presenter)
프로젝트를 만들때 폴더 구조는 굉장히 중요합니다.
실무에서 사용하는 폴더 구조는 여러가지 방법이 존재하는데, 이러한 방법들을 패턴이라고 부릅니다.
리액트에서 사용하는 유명한 패턴으로는 container / presentational 패턴과 atomic 패턴이 있습니다.
container / presentational 패턴을 사용 하였습니다.
container
import { useState } from "react";
import { useMutation } from '@apollo/client'
import { useRouter } from 'next/router'
import BoardWriteUI from './BoardWrite.presenter'
import { CREATE_BOARD } from './BoardWrite.queries'
export default function BoardWrite(){
const router = useRouter()
const [writer, setWriter] = useState("");
const [password, setPassword] = useState("");
const [title, setTitle] = useState("");
const [contents, setContents] = useState("");
const [writerError, setWriterError] = useState("");
const [passwordError, setPasswordError] = useState("");
const [titleError, setTitleError] = useState("");
const [contentsError, setContentsError] = useState("");
const [createBoard] = useMutation(CREATE_BOARD)
const onChangeWriter = (event) => {
setWriter(event.target.value);
if(event.target.value !== ""){
setWriterError("")
}
};
const onChangePassword = (event) => {
setPassword(event.target.value);
if(event.target.value !== ""){
setPasswordError("")
}
};
const onChangeTitle = (event) => {
setTitle(event.target.value);
if(event.target.value !== ""){
setTitleError("")
}
};
const onChangeContents = (event) => {
setContents(event.target.value);
if(event.target.value !== ""){
setContentsError("")
}
};
const onClickSubmit = async () => {
if (!writer) {
setWriterError("작성자를 입력해주세요.");
}
if (!password) {
setPasswordError("비밀번호를 입력해주세요.");
}
if (!title) {
setTitleError("제목을 입력해주세요.");
}
if (!contents) {
setContentsError("내용을 입력해주세요.");
}
if (writer && password && title && contents) {
try {
const result = await createBoard({
variables: {
createBoardInput: {
writer: writer,
password: password,
title: title,
contents: contents
}
}
})
console.log(result.data.createBoard._id)
router.push(`/boards/${result.data.createBoard._id}`)
} catch(error) {
alert(error.message)
}
}
};
return (
<BoardWriteUI
writerError={writerError}
passwordError={passwordError}
titleError={titleError}
contentsError={contentsError}
onChangeWriter={onChangeWriter}
onChangePassword={onChangePassword}
onChangeTitle={onChangeTitle}
onChangeContents={onChangeContents}
onClickSubmit={onClickSubmit}
/>
)
}
presenter
import * as S from "./BoardWrite.styles";
export default function BoardWriteUI(props){
return (
<S.Wrapper>
<S.Title>게시글 등록</S.Title>
<S.WriterWrapper>
<S.InputWrapper>
<S.Label>작성자</S.Label>
<S.Writer type="text" placeholder="이름을 적어주세요." onChange={props.onChangeWriter} />
<S.Error>{props.writerError}</S.Error>
</S.InputWrapper>
<S.InputWrapper>
<S.Label>비밀번호</S.Label>
<S.Password type="password" placeholder="비밀번호를 작성해주세요." onChange={props.onChangePassword} />
<S.Error>{props.passwordError}</S.Error>
</S.InputWrapper>
</S.WriterWrapper>
<S.InputWrapper>
<S.Label>제목</S.Label>
<S.Subject type="text" placeholder="제목을 작성해주세요." onChange={props.onChangeTitle} />
<S.Error>{props.titleError}</S.Error>
</S.InputWrapper>
<S.InputWrapper>
<S.Label>내용</S.Label>
<S.Contents placeholder="내용을 작성해주세요." onChange={props.onChangeContents} />
<S.Error>{props.contentsError}</S.Error>
</S.InputWrapper>
<S.InputWrapper>
<S.Label>주소</S.Label>
<S.ZipcodeWrapper>
<S.Zipcode placeholder="07250" />
<S.SearchButton>우편번호 검색</S.SearchButton>
</S.ZipcodeWrapper>
<S.Address />
<S.Address />
</S.InputWrapper>
<S.InputWrapper>
<S.Label>유튜브</S.Label>
<S.Youtube placeholder="링크를 복사해주세요." />
</S.InputWrapper>
<S.ImageWrapper>
<S.Label>사진첨부</S.Label>
<S.UploadButton>+</S.UploadButton>
<S.UploadButton>+</S.UploadButton>
<S.UploadButton>+</S.UploadButton>
</S.ImageWrapper>
<S.OptionWrapper>
<S.Label>메인설정</S.Label>
<S.RadioButton type="radio" id="youtube" name="radio-button" />
<S.RadioLabel htmlFor="youtube">유튜브</S.RadioLabel>
<S.RadioButton type="radio" id="image" name="radio-button" />
<S.RadioLabel htmlFor="image">사진</S.RadioLabel>
</S.OptionWrapper>
<S.ButtonWrapper>
<S.SubmitButton onClick={props.onClickSubmit}>등록하기</S.SubmitButton>
</S.ButtonWrapper>
</S.Wrapper>
)
}
queries
import { gql } from '@apollo/client'
export const CREATE_BOARD = gql`
mutation createBoard($createBoardInput: CreateBoardInput!){
createBoard(createBoardInput: $createBoardInput){
_id
}
}
`
styles(CSS)
import styled from "@emotion/styled";
export const Wrapper = styled.div`
width: 1200px;
/* height: 1847px; */
border: 1px solid black;
margin: 100px;
padding-top: 80px;
padding-bottom: 100px;
padding-left: 102px;
padding-right: 102px;
display: flex;
flex-direction: column;
align-items: center;
border: none;
box-shadow: 0px 0px 10px gray;
`;
export const Title = styled.div`
font-family: Arial, Helvetica, sans-serif;
font-size: 36px;
font-weight: bold;
`;
export const WriterWrapper = styled.div`
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-top: 40px;
`;
export const Writer = styled.input`
width: 486px;
height: 52px;
padding-left: 16px;
border: 1px solid #bdbdbd;
`;
export const Password = styled.input`
width: 486px;
height: 52px;
padding-left: 16px;
border: 1px solid #bdbdbd;
`;
export const Label = styled.div`
padding-bottom: 16px;
font-size: 16px;
font-weight: 500;
`;
export const InputWrapper = styled.div`
padding-top: 40px;
`;
export const Subject = styled.input`
width: 996px;
height: 52px;
padding-left: 16px;
border: 1px solid #bdbdbd;
`;
export const Contents = styled.textarea`
width: 996px;
height: 480px;
padding-left: 16px;
padding: 14px;
border: 1px solid #bdbdbd;
`;
export const ZipcodeWrapper = styled.div`
display: flex;
flex-direction: row;
`;
export const Zipcode = styled.input`
width: 77px;
height: 52px;
padding-left: 16px;
border: 1px solid #bdbdbd;
`;
export const SearchButton = styled.button`
width: 124px;
height: 52px;
margin-left: 16px;
background-color: black;
cursor: pointer;
color: white;
`;
export const Address = styled.input`
width: 996px;
height: 52px;
margin-top: 16px;
padding-left: 16px;
border: 1px solid #bdbdbd;
`;
export const Youtube = styled.input`
width: 996px;
height: 52px;
padding-left: 16px;
border: 1px solid #bdbdbd;
`;
export const ImageWrapper = styled.div`
width: 996px;
padding-top: 40px;
`;
export const UploadButton = styled.button`
width: 78px;
height: 78px;
background-color: #bdbdbd;
margin-right: 24px;
outline: none;
border: none;
cursor: pointer;
`;
export const OptionWrapper = styled.div`
width: 996px;
padding-top: 40px;
`;
export const RadioButton = styled.input`
cursor: pointer;
`;
export const RadioLabel = styled.label`
margin-left: 8px;
margin-right: 20px;
font-weight: 500;
cursor: pointer;
`;
export const ButtonWrapper = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
padding-top: 80px;
`;
export const CancelButton = styled.button`
width: 179px;
height: 52px;
background-color: #bdbdbd;
border: none;
font-size: 16px;
font-weight: 500;
margin-left: 12px;
margin-right: 12px;
cursor: pointer;
`;
export const SubmitButton = styled.button`
width: 179px;
height: 52px;
border: none;
font-size: 16px;
font-weight: 500;
margin-left: 12px;
margin-right: 12px;
cursor: pointer;
background-color: yellow;
`;
export const Error = styled.div`
padding-top: 10px;
font-size: 14px;
color: red;
`;
기존의 part들을 실무용 폴더구조로 나누었습니다.
part별로 보기 매우 편해졌습니다.
import BoardWrite from "../../../src/components/units/board/write/BoardWrite.container";
export default function BoardWritePage() {
return <BoardWrite />
}
구조
main - container- presenter(HTML) - styles(CSS)
- queries
'실습' 카테고리의 다른 글
게시판 만들기.03 (0) | 2023.01.31 |
---|---|
게시판 만들기.02 (0) | 2023.01.30 |
게시판 만들기.01 (0) | 2023.01.30 |