프로젝트를 만드러 가면서 지금까지 배운 다양한 기술을 활용해 봅니다. 이 과정을 통해 실무에서 프로젝트를 개발할 떄 어떤 방식으로 작업하는지 알 수 있습니다.
라우터 적용
총 다섯 개의 페이지를 만듭니다.
- 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 |