인프런 ==> 따라하며-배우는-노드-리액트-기본
강의 자료 : https://braverokmc79.github.io/react-series/BoilerPlate%20Korean.html
소스 : https://github.com/braverokmc79/react-series
섹션 0. Node JS
강의
1.소개
2.NODE JS 와 EXPRESS JS 다운로드 하기
3.몽고 DB 연결
4.MongoDB Model & Schema
5.GIT 설치
6.SSH를 이용해 GITHUB 연결
7.BodyParser & PostMan & 회원 가입 기능
8.Nodemon 설치
9.비밀 설정 정보 관리
10.Bcrypt로 비밀번호 암호화 하기
11.로그인 기능 with Bcrypt (1)
12.토큰 생성 with jsonwebtoken
13.Auth 기능 만들기
14.로그아웃 기능
섹션 1. React JS
20.강의
15.리액트란 ?
16.Create-React-App
17.npm npx
18.구조 설명
19.CRA to Our Boilerplate
20.React Router Dom
21.데이터 Flow & Axios
22.CORS 이슈, Proxy 설정
23.Proxy Server ?
24.Concurrently
25.Antd CSS Framwork 26.Redux 기초
26.Redux 기초
27.Redux UP !!!!!
28.React Hooks
29.로그인 페이지(1)
30.로그인 페이지(2)
31.회원 가입 페이지
32.로그아웃
33.인증 체크 (1)
34.인증 체크 (2) 강의 마무리.
2. Diagram 자료
2 강의
Diagram HTML 자료
Diagram XML 자료
React JS
25.Antd CSS Framwork 26.Redux 기초
CSS Framework 종류 for React JS
1. Material UI : https://mui.com/
2. React Bootstrap : https://react-bootstrap.github.io/
3. Semantic UI : https://semantic-ui.com/
4. Ant Design : https://ant.design/
5. Materialize : https://materializecss.com/
Ant Design
$ npm install antd
$ yarn add antd
App.js
import 'antd/dist/antd.css';
26.Redux 기초
27.Redux UP !!!!!
Redux의 적용
공식 사이트 : https://redux.js.org/
설치방식 : $npm install --save redux
CDN 방식 : https://cdnjs.com/libraries/redux
===================== ======== 다음을 추천한다.============= =====================
1. redux 설치
& yarn add redux react-redux redux-promise redux-thunk
2. createStore deprecate 로 인하여 React Redux Toolkit 설치
$ yarn add @reduxjs/toolkit
또는
$ npm install @reduxjs/toolkit react-redux
크롬브라우저 확장프로그램설치 리덕스 :
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko-KR
Redux Thunk
Redux Thunk는 액션 생성자가 리턴하는 것을 객체가 아닌 함수를 사용할 수 있게 한다. 그리고 함수를 리턴하면 그 함수를 실행이 끝난 뒤에 값을 액션으로 넘겨준다.
정리하자면, 기존에 액션 생성자가 리턴하는 객체로는 처리하지 못했던 비동기 작업을 Redux Thunk를 사용하면서 일반 함수를 리턴할 수 있게 됨에 따라 일반 함수에서 가능한 모든 동작들이 가능해진다. 그중에 비동기 통신 작업을 할 수 있어 사용하는 것이다. Redux Thunk가 비동기 통신을 위해 만들어진 것이 아니라 액션 생성자가 함수를 리턴할 수 있다는 것에 좀 더 초점을 맞추어야 될 것 같다.
export const login = (dataToSubmit) => async dispatch => { const response = await axios.post(`api/users/login`, dataToSubmit) dispatch({ type: 'LOGIN', payload: response }) }
위 코드가 Redux Thunk를 사용하여 비동기 통신을 수행하는 코드이다.
login 액션 생성자가 dispatch가 되어 가지고 온 파라미터로 Post 요청을 보낸 뒤에 서버에서 응답 해준 값을 액션으로 다시 리듀서에 호출되어 전달해준다.
이때 주목해야할 점은 reponse는 Promise 객체이다. 미들웨어를 사용하지 않는 경우라면 그대로 통신을 완료하지 않은 Promise 객체를 payload로 보내겠지만
async await에 사용으로 동기적으로 사용할 수 있게 해준다 즉, Promise 객체가 통신이 완료되고 응답을 받을때까지 기다렸다가 payload에 실어서 리듀서에 전달할 수 있는 것이다.
그렇다면 비동기 통신에만 집중한 미들웨어를 사용하고 싶다면 redux-pomise가 있다. Promise 객체에 처리에 있어서는 Redux-Thunk와 거의 동일하다고 보면 되지만 Redux Thunk는 함수를 반환할 수 있다는 것에 좀 더 포커스를 맞쳐야한다.
Redux Promise
export const userInfo = (body) => { const request = axios.post('/api/users/userlist', body) .then(response => response.data); return { type: USER_INFO, payload: request } }
위 코드는 userInfo를 서버에 API 요청을 보내어 응답을 받은 뒤 리듀서에 Promise 통신 결과를 액션으로 넘기는 액션 생성자이다.
여기서 중요한 점은 리듀서에 넘겨주는 request는 Promise 객체이다. 다시 말해 store의 상태를 반영 하기에는 부적절한 타입이다.
원래라면 action을 바로 리듀서에게 넘기면 되지만 Promise 객체는 통신 결과를 가져와야지만 가능하다.
미들웨어를 사용하지 않고 Promise 객체를 리듀서에 넘겨줄 순 없다. 비동기 작업을 위한 객체를 액션 생성자가 어떻게 처리하겠는가?
방법 중 하나는 위 섹션에서 설명한 Redux Thunk를 사용해 비동기 통신을 끝낸 뒤에 dispatch 해주는 방법이 있고 지금 설명할 redux-promise-middleware를 사용하는 방법이 있다.
redux-promise를 사용하면 payload 되는 객체가 만약 Promise 객체라면 통신 결과가 나올 때까지 기다린 이후 결과 값을 리듀서에게 전달한다. 여기서 주목해야 하는 점은 미들웨어를 사용하지 않으면 그냥 Promise 객체 그 자체를 payload로 보낸다.
실제 console.log를 출력하면 Promise 객체에 대한 값이 출력된다. 하지만 redux-promise를 사용한다면 Promise를 객체가 통신이 끝난 뒤 그 값을 payload로 응답해준다. 이것이 redux-pomise-middleware에 기능이다.
redux-thunk 액션 생성자가 함수를 반환 할 수 있습니다.
function myAction(payload){ return function(dispatch){ // use dispatch as you please } }
redux-promise 약속을 반환 할 수 있습니다.
function myAction(payload){ return new Promise(function(resolve, reject){ resolve(someData); // redux-promise will dispatch someData }); }
두 라이브러리는 작업을 비동기 또는 조건부로 전달해야하는 경우 유용합니다
[ React ] 리액트 리덕스 툴킷 redux-toolkit
Redux Toolkit 는
1) middleware 미들웨어가 포함 되어 있다.
2) devTools 이 포함 되어 있다.
1)createStore 사용 시
index.js
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import { Provider } from 'react-redux'; import { applyMiddleware, legacy_createStore as createStore } from 'redux'; import promiseMiddleware from 'redux-promise'; import ReduxThunk from 'redux-thunk'; import Reducer from './_reducers'; const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore); const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(); const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={ createStoreWithMiddleware(Reducer, devTools)}> createStore 사용 < App /> </Provider > );
1)redux-toolkit configureStore 사용 시
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import { Provider } from 'react-redux'; import Reducer from './_reducers'; import { configureStore } from "@reduxjs/toolkit"; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={configureStore({ reducer: Reducer })}> configureStore 사용11 < App /> </Provider > );
logger 추가 적용시
import logger from 'redux-logger' const store = configureStore({ reducer: Reducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger), devTools: process.env.NODE_ENV !== 'production' })
28.React Hooks
29.로그인 페이지(1)
LoginPage.js
import React, { useRef, useState } from 'react' function LoginPage() { const [Email, setEmail] = useState(""); const [Password, setPassword] = useState(""); const emailRef = useRef(null); const passwordRef = useRef(null); //value={Email} 입력하지 않으면 useRef 사용 const onEmailHandler = (event) => { console.log("emailRef : ", emailRef.current.value); // console.log("passwordRef : ", passwordRef.current.value); //console.log("event ", event.target.value); setEmail(emailRef.current.value); } const onPasswordHandler = (event) => { console.log("Password : ", event.currentTarget.value); setPassword(event.currentTarget.value); } const onSubmitHandler = (event) => { } return ( <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100vh' } }> <form style={{ display: 'flex', flexDirection: 'column' }} onSubmit={onSubmitHandler} > <label>Email</label> <input type="email" onChange={onEmailHandler} ref={emailRef} /> <label>Password</label> <input type="password" value={Password} onChange={onPasswordHandler} /> <br /> <button> Login </button> </form> </div> ) } export default LoginPage
30.로그인 페이지(2) - Redux 리덕스
* 로그인 처리 : 리덕스 설정 및 처리 과정
1) index.js : 리덕스 라이브러리 연동설정(미들웨어, thunk - 동기식처리, devTool, promise 결과값 처리 )
2) src/components/views/LoginPage/LoginPage.js : dispatch 처리됨
3)src/ _actions/user_action.js : action 처리됨
4) src/ _reducers/user_reducer.js : reducer 미들웨어 처리됨
Reducer
state, action
- state는 앱을 작동 시키는 핵심.
- action은 state를 사용자의 입력을 기반으로 발생하는 이벤트 및 트리거
reducer가 action에 대한 처리를 하는지 확인 있다면 state의 복사본을 만들거나, 업데이트요청이라면, 새로운 값을
사본을 업데이트 처리한다. 만약에 없다면 기존 state 를 변경하지 않고 반환한다.
5) src/components/views/LoginPage/LoginPage.js : 결과 반환
1)index.js
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import { Provider } from 'react-redux'; import { applyMiddleware, legacy_createStore as createStore } from 'redux'; import promiseMiddleware from 'redux-promise'; import ReduxThunk from 'redux-thunk'; import Reducer from './_reducers'; const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore); const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(); const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={ createStoreWithMiddleware(Reducer, devTools)}> < App /> </Provider > );
2)LoginPage.js
~ const onSubmitHandler = async (event) => { event.preventDefault(); const body = { email: Email, password: Password } /****** 리덕스 사용 *******/ console.log("1. dispatch 로 body 값 action 에 데이터 전달"); dispatch(loginUser(body)).then(response => { if (response.payload.loginSuccess) { alert(response.payload.loginSuccess); //navigate("/"); } else { alert('Error'); } }) } ~
3)_actions/types.js
export const LOGIN_USER = "login_user";
4)_actions/user_action.js
import axios from 'axios'; import { LOGIN_USER } from './types'; export function loginUser(dataTomSubmit) { console.log("2. actions 액션작업 시작 - axios 전송"); const request = axios.post('/api/users/login', dataTomSubmit) .then((res) => { return res.data; }).catch((Error) => { console.error("에러 :", Error); }); return { type: LOGIN_USER, payload: request } }
5)_reducers/index.js
import { combineReducers } from 'redux'; import user from './user_reducer'; //import comment from './comment_reducer'; const rootReducer = combineReducers({ user, //comment }) export default rootReducer;
6)user_reducer.js
import { LOGIN_USER } from '../_actions/types'; const initialState = { value: 0 } export default function user_reducers(state = initialState, action) { console.log("3. reducer 작업 시작 타입 :", action.type); let result = null; switch (action.type) { case LOGIN_USER: result = { ...state, loginSuccess: action.payload } break; default: result = state; break; } return result; }
31.회원 가입 페이지
RegisterPage.js
import React, { useRef, useState } from 'react' import { useDispatch } from 'react-redux'; import { useNavigate } from "react-router-dom"; import { registerUser } from '../../../_actions/user_action'; function RegisterPage() { const dispatch = useDispatch(); const [Email, setEmail] = useState(""); const [Name, setName] = useState(""); const [Password, setPassword] = useState(""); const [ConfirmPassword, setConfirmPassword] = useState(""); let navigate = useNavigate(); const onEmailHandler = (event) => { setEmail(event.currentTarget.value); } const onNameHandler = (event) => { setName(event.currentTarget.value); } const onPasswordHandler = (event) => { setPassword(event.currentTarget.value); } const onConfirmPasswordHandler = (event) => { setConfirmPassword(event.currentTarget.value); } const onSubmitHandler = async (event) => { event.preventDefault(); if (Password !== ConfirmPassword) { return alert('비밀번호 비밀번호 확인은 같아야 합니다.'); } const body = { email: Email, password: Password, name: Name } dispatch(registerUser(body)).then(response => { console.log("response :", response); if (response.payload.success) { navigate("/login"); } else { alert('Error'); } }); } return ( <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100vh' }}> <form style={{ display: 'flex', flexDirection: 'column' }} onSubmit={onSubmitHandler}> <label>Email</label> <input type="email" value={Email} onChange={onEmailHandler} /> <label>Name</label> <input type="text" value={Name} onChange={onNameHandler} /> <label>Password</label> <input type="password" value={Password} onChange={onPasswordHandler} /> <label>ConfirmPassword</label> <input type="password" value={ConfirmPassword} onChange={onConfirmPasswordHandler} /> <br /> <button>회원가입</button> </form> </div> ) } export default RegisterPage
user_action.js
~ export function registerUser(dataTomSubmit) { const request = axios.post('/api/users/register', dataTomSubmit) .then(res => res.data); return { type: REGISTER_USER, payload: request } } ~
user_reducer.js
import { LOGIN_USER, REGISTER_USER } from '../_actions/types'; const initialState = { value: 0 } export default function user_reducers(state = initialState, action) { console.log("3. reducer 작업 시작 타입 :", action.type); let result = null; switch (action.type) { case LOGIN_USER: result = { ...state, loginSuccess: action.payload } break; case REGISTER_USER: result = { ...state, register: action.payload } break; default: result = state; break; } return result; }
32.로그아웃
LandingPage.js
import React, { useEffect } from 'react' import Axios from 'axios'; import { useNavigate } from 'react-router-dom'; function LandingPage() { const navigate = useNavigate(); useEffect(() => { Axios.get("api/hello") .then(res => { console.log(res.data); }); }, []); const onClikHandler = () => { Axios.get("/api/users/logout") .then(res => { if (res.data.success) { navigate("/login"); } else { alert("로그아웃 하는데 실패 했습니다."); } }) } return ( <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100vh' } }> <h2>시작 페이지</h2> <button onClick={onClikHandler}> 로그아웃 </button> </div> ) } export default LandingPage
33.인증 체크 (1)
34.인증 체크 (2)
Nodejs 서버
index.js
~ //role 1 어드민 role 2 특정 부서 어드민 //rele 0 -> 일반유저 , role 0 이 아니면 관리자. app.post('/api/users/auth', auth, (req, res) => { //여기 까지 미들웨어를 통과해 왔다는 얘기는 Authentication이 True 라는 말 res.status(200).json({ _id: req.user._id, isAdmin: req.user.role === 0 ? false : true, isAuth: true, email: req.user.email, name: req.user.name, lastname: req.user.lastname, role: req.user.role, image: req.user.image }); }); ~
App.js
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'; import LandingPage from './components/views/LandingPage/LandingPage'; import LoginPage from './components/views/LoginPage/LoginPage'; import RegisterPage from './components/views/RegisterPage/RegisterPage'; import 'antd/dist/antd.min.css'; import Auth from './hoc/auth'; function App() { const AuthLandingPage = Auth(LandingPage, null); const AuthLoginPage = Auth(LoginPage, false); //로그인한 유저는 출입불가 const AuthRegisterPage = Auth(RegisterPage, false); return ( <div className="App"> <BrowserRouter> <div> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/login">login</Link></li> <li><Link to="/register">register</Link></li> </ul> </div> <Routes> <Route path="/" element={<AuthLandingPage />} /> <Route path="/login" element={<AuthLoginPage />} /> <Route path="/register" element={<AuthRegisterPage />} /> </Routes> </BrowserRouter> </div> ); } export default App;
hoc/auth.js
import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { auth } from '../_actions/user_action'; import { useNavigate } from 'react-router-dom'; const Auth = (SpecificComponent, option, adminRoute = null) => { /** option 값 */ //null =>아무나 출입이 가능한 페이지 //true =>로그인한 유저만 출입이 가능한 페이지 //false =>로그인한 유저는 출입 불가능한 페이지 function AuthenticaitonCheck(props) { const dispatch = useDispatch(); const navigate = useNavigate(); useEffect(() => { dispatch(auth()).then(res => { console.log(res); if (!res.payload.isAuth) { //1.로그인 하지 않은 상태 console.log("1.로그인 하지 않은 상태"); if (option) { navigate("/login") } } else { //2.로그인 한 상태 console.log("2.로그인 하지 않은 상태"); if (adminRoute && !res.payload.isAdmin) { navigate("/"); } else { if (option === false) navigate("/"); } } }) }, []); return <SpecificComponent /> } return AuthenticaitonCheck; }; export default Auth;
user_action.js
export function auth() { const request = axios.post('/api/users/auth') .then(res => res.data); return { type: AUTH_USER, payload: request } }
user_reducer.js
import { LOGIN_USER, REGISTER_USER, AUTH_USER } from '../_actions/types'; const initialState = { value: 0 } export default function user_reducers(state = initialState, action) { let result = null; switch (action.type) { case LOGIN_USER: result = { ...state, loginSuccess: action.payload } break; case REGISTER_USER: result = { ...state, register: action.payload } break; case AUTH_USER: result = { ...state, userData: action.payload } break; default: result = state; break; } return result; }
댓글 ( 4)
댓글 남기기