프로젝트를 만드러 가면서 지금까지 배운 다양한 기술을 활용해 봅니다. 이 과정을 통해 실무에서 프로젝트를 개발할 떄 어떤 방식으로 작업하는지 알 수 있습니다.
라우터 적용
총 다섯 개의 페이지를 만듭니다.
- LoginPage.js - 로그인
- RegisterPage.js - 회원가입
- WritePage.js - 글쓰기
- PostPage.js - 포스트 읽기
- PostListpage.js - 포스트 목록
UI
( 기존에 쓰던 블로그가 날라가서 UI부분은 간략하게 적겠다.. 죄송합니다 )
// src/components/common/Button.js
import React from 'react';
import styled, { css } from 'styled-components';
import palette from '../../lib/styles/palette';
const StyledButton = styled.button`
border: none;
border-radious: 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]};
}
${(props) =>
props.cyan &&
css`
background: ${palette.cyan[5]};
&:hover {
background: ${palette.cyan[4]};
}
`}
${(props) =>
props.fullWidth &&
css`
padding-top: 0.75rem;
padding-bottom: 0.75rem;
width: 100%;
font-size: 1.125rem;
`}
`;
const Button = (props) => {
return <StyledButton {...props} />;
};
export default Button;
우선 Button 컴포넌트를 만들었는데 공통 UI라 common폴더에 넣었으며 cyan, fullWidth속성을 주어서 styled-component의 다형성을 구현하였습니다.
// src/components/auth/AuthTemplate.js
import React from 'react';
import styled from 'styled-components';
import palette from '../../lib/styles/palette';
import { Link } from 'react-router-dom';
/**
* 회원가입/로그인 페이지의 레이아웃을 담당하는 컴포넌트입니다.
*/
/* 화면 전체를 채움 */
const AuthTemplateBlock = styled.div`
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background: ${palette.gray[2]};
/* flex로 내부 중앙 정렬 */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
/* 흰색 박스 */
const WhiteBox = styled.div`
.logo-area {
display: block;
padding-bottom: 2rem;
text-align: center;
font-weight: bold;
letter-spacing: 2px;
a {
text-decoration: none;
color: black;
}
}
box-shadow: 0 0 8px rgba(0, 0, 0, 0.025);
border-radius: 2px;
background: white;
padding: 2rem;
width: 360px;
`;
const AuthTemplate = ({ children }) => {
return (
<AuthTemplateBlock>
<WhiteBox>
<div className="logo-area">
<Link to="/">REACTERS</Link>
</div>
{children}
</WhiteBox>
</AuthTemplateBlock>
);
};
export default AuthTemplate;
이는 Auth와 관련된 Templatel컴포넌트로서 대충 Template이라고만 알아두면 될 듯 싶다.
// src/components/auth/AuthForm.js
import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import palette from '../../lib/styles/palette';
import Button from '../common/Button';
import 'open-color';
/**
* 회원가입 또는 로그인 폼을 보여줍니다.
*/
const AuthFormBlock = styled.div`
h3 {
margin: 0;
color: ${palette.gray[8]};
margin-bottom: 1rem;
}
`;
const StyledInput = styled.input`
font-size: 1rem;
border: none;
width: 100%;
border-bottom: 1px solid ${palette.gray[5]};
padding-bottom: 0.5rem;
outline: none;
&:focus {
color: #0ca678;
border-bottom: 1px solid ${palette.gray[7]};
}
& + & {
margin-top: 1rem;
}
`;
/**
* 폼 하단에 로그인 혹은 회원가입 링크를 보여 줌
*/
const Footer = styled.div`
margin-top: 2rem;
text-align: right;
a {
color: ${palette.gray[6]};
text-decoration: underline;
&:hover {
color: ${palette.gray[9]};
}
}
`;
const ButtonWithMarginTop = styled(Button)`
margin-top: 1rem;
`;
const textMap = {
login: '로그인',
register: '회원가입',
};
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 cyan fullWidth>
로그인
</ButtonWithMarginTop>
</form>
<Footer>
{type === 'login' ? (
<Link to="/register">회원가입</Link>
) : (
<Link to="/login">로그인</Link>
)}
</Footer>
</AuthFormBlock>
);
};
export default AuthForm;
이는 Template의 child로서 LoginPage와 Registerpage안에 들어아가게 되는 컴포넌트로 로그인과 회원가입 페이지를 구성하는 컴포넌트이다.
// src/components/auth/Header.js
import React from 'react';
import styled from 'styled-components';
const HeaderBlock = styled.div``;
const Header = () => {
return <HeaderBlock></HeaderBlock>;
};
export default Header;
// javascriptreact.json
{
// Place your snippets for javascriptreact here. Each snippet is defined under a snippet name and has a prefix, body and
// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
// same ids are connected.
// Example:
// "Print to console": {
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"Styled React Functional Component": {
"prefix": "srfc",
"body": [
"import React from 'react';",
"import styled from 'styled-components';",
"",
"const ${TM_FILENAME_BASE}Block = styled.div``;",
"",
"const ${TM_FILENAME_BASE} = () => {",
"return (",
" <${TM_FILENAME_BASE}Block>",
" </${TM_FILENAME_BASE}Block>",
" );",
"};",
"",
"export default ${TM_FILENAME_BASE};",
""
],
"description": "Styled React Functinal Component"
}
}
이 Header.js는 헤더 구성 컴포넌트이며 반복되는 규칙을 통합해서 javascript react파일에서 적용 가능한 snippet을 만들어서 적용했다.
// src/pages/LoginPage.js
import React from 'react';
import AuthTemplate from '../components/auth/AuthTemplate';
import AuthForm from '../components/auth/AuthForm';
const LoginPage = () => {
return (
<AuthTemplate>
<AuthForm type="login" />
</AuthTemplate>
);
};
export default LoginPage;
// src/pages/RegisterPage.js
import React from 'react';
import AuthTemplate from '../components/auth/AuthTemplate';
import AuthForm from '../components/auth/AuthForm';
const RegisterPage = () => {
return (
<AuthTemplate>
<AuthForm type="register" />
</AuthTemplate>
);
};
export default RegisterPage;
이들은 아까 AuthTemplate과 AuthForm을 렌더링해주는 로그인과 회원가입 컴포넌트이다.
// src/lib/styles/palette.js
const palette = {
gray: [
'#f8f9fa',
'#f1f3f5',
'#e9ecef',
'#dee2e6',
'#ced4da',
'#adb5bd',
'#868e96',
'#495057',
'#343a40',
'#212529',
],
cyan: [
'#e3fafc',
'#c5f6fa',
'#99e9f2',
'#66d9e8',
'#3bc9db',
'#22b8cf',
'#15aabf',
'#1098ad',
'#0c8599',
'#0b7285',
],
};
export default palette;
마지막으로 이것은 open-color에 있는 이번 프로젝트에서 필요한 색만 따온 palette파일이다.
이에 대한 결과는 아래와 같다.
리덕스
이번에는 리덕스와 회원가입과 로그인 폼의 상태를 관리하는 코드를 짜 보겠습니다.
// src/modules/auth.js
import { createAction, handleActions } from 'redux-actions';
import produce from 'immer';
const CHANGE_FIELD = 'auth/CHANGE_FIELD';
const INITIALIZE_FORM = 'auth/INITIALIZE_FORM';
export const changeField = createAction(
CHANGE_FIELD,
({ form, key, value }) => ({
form, // register, login
key, // username, password, passwordConfirm
value, // 실제 바꾸려는 값
}),
);
export const initializeForm = createAction(INITIALIZE_FORM, (form) => form); // register
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;
이의 changeField action은 값이 바뀔 때마다 login과 register안의 state를 바꾸어 주는 action이고 initializeForm은 login -> register로 갔을 때 register의 이전 값을 초기화 시켜주고 그 반대의 경우도 성립하게 해 준다.
// src/modules/index.js
import { combineReducers } from 'redux';
import auth from './auth';
const rootReducer = combineReducers({
auth,
});
export default rootReducer;
루트 리듀서를 만들어 준다.
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from './modules';
const store = createStore(rootReducer, composeWithDevTools());
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('root'),
);
index.js에 Provider컴포넌트로 감싸주면서 리덕스 설정을 완료 시킨다.
또한 기존의 AuthForm에 onChange, onSubmit을 적용 시켜 주기 위해 아래와 같이 바꾸어 준다. 바로 컨트롤러 컴포넌트를 만드는 것이다.
// src/pages/LoginPage.js
import React from 'react';
import AuthTemplate from '../components/auth/AuthTemplate';
import LoginForm from '../components/auth/LoginForm';
const LoginPage = () => {
return (
<AuthTemplate>
<LoginForm />
</AuthTemplate>
);
};
export default LoginPage;
// src/pages/RegisterPage.js
import React from 'react';
import AuthTemplate from '../components/auth/AuthTemplate';
import RegisterForm from '../components/auth/RegisterForm';
const RegisterPage = () => {
return (
<AuthTemplate>
<RegisterForm />
</AuthTemplate>
);
};
export default RegisterPage;
또한 LoginForm과 RegisterForm의 코드를 보면 그냥 useDispatch와 useSelector로서 리덕스의 상태를 가지고 놀아서 프레젠테이셔널 컴포넌트로 속성을 넘겨주는 역할이다.
// src/components/auth/LoginForm.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { changeField, initializeForm } from '../../modules/auth';
import AuthForm from './AuthForm';
const LoginForm = () => {
const dispatch = useDispatch();
const { form } = useSelector(({ auth }) => ({
form: auth.login,
}));
// 인풋 변경 이벤트 핸들러
const onChange = (e) => {
const { value, name } = e.target;
dispatch(
changeField({
form: 'login',
key: name,
value,
}),
);
};
// 폼 등록 이벤트 핸들러
const onSubmit = (e) => {
e.preventDefault();
// 구현 예정
};
// 컴포넌트가 처음 렌더링될 떄 form을 초기화함
useEffect(() => {
dispatch(initializeForm('login'));
}, []);
return (
<AuthForm
type="login"
form={form}
onChange={onChange}
onSubmit={onSubmit}
/>
);
};
export default LoginForm;
// src/components/auth/RegisterForm.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { changeField, initializeForm } from '../../modules/auth';
import AuthForm from './AuthForm';
const RegisterForm = () => {
const dispatch = useDispatch();
const { form } = useSelector(({ auth }) => ({
form: auth.register,
}));
// 인풋 변경 이벤트 핸들러
const onChange = (e) => {
const { value, name } = e.target;
dispatch(
changeField({
form: 'register',
key: name,
value,
}),
);
};
// 폼 등록 이벤트 핸들러
const onSubmit = (e) => {
e.preventDefault();
// 구현 예정
};
// 컴포넌트가 처음 렌더링될 떄 form을 초기화함
useEffect(() => {
dispatch(initializeForm('register'));
}, []);
return (
<AuthForm
type="register"
form={form}
onChange={onChange}
onSubmit={onSubmit}
/>
);
};
export default RegisterForm;
onSubmit은 다음에 구현하도록 하는 걸로 하고 이번 포스팅은 이만.~
'React > ReactJs' 카테고리의 다른 글
React - FrontEnd Project - 3 (0) | 2022.03.17 |
---|---|
React - FrontEnd project - 2 (0) | 2022.03.15 |
React - JWT (0) | 2022.03.13 |
React - API의 활용을 위한 prototype (0) | 2022.03.13 |
React - mongoDB ( mongoose ) (0) | 2022.03.12 |