<flux복습 - index.html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flux Architecture</title>
<script src="app.js" type="module"></script>
</head>
<body>
<div id="count">count:</div>
<div id="msg">msg:</div>
</body>
</html>
<flux복습 - app.js>
import { createStore } from "./redux.js"
import { reducer } from "./reducer.js" // worker함수
import { decrease, increase, setToastFalse, setToastMsg } from "./actions.js"
// 사용 - 함수 호출 -> store 생성하기 - 리액트에서는 index.js에서 할 일!
// -> index.js에서 모든 전역 state를 관리하기위해
// app.js에 있는 코드가 리액트 컴포넌트에 써야하는 코드임(store부분은 index.js)
// 문제제기 - app.js 하나에 모두 있을때는 파라미터에 reducer(구:worker)를 파라미터로 넘겨야 함
const store = createStore(reducer) // index.js
store.subscribe(function () { // 구독발행모델 - 함수호출
// getState는 리액트에서 useSelector(state => state.userAUth) - 상태값을 store에서 읽어올 때 사용
console.log(store.getState()) // 변경된 상태값 찍기 - 리액트 컴포넌트가 마운트될 때 찍기
const state = store.getState()
document.querySelector("#count").append(state.count)
document.querySelector("#msg").append(state.msg)
})
// dispatch가 리액트에선 const dispatch = useDispatch() -> dispatch(type, payload)
// -> 액션을 스토어에 전달
// 해당 컴포넌트에서 state값을 가져오기 - useSelector훅
store.dispatch(increase())
store.dispatch(increase())
store.dispatch(decrease())
store.dispatch(setToastMsg("관리자에게 문의하세요"))
store.dispatch(setToastFalse())
<flux복습 - redux.js>
// type을 정의하는 규칙 - 커링함수
// 매개변수 분할 처리
// 첫번째 파라미터 - 타입
// 두번째 파라미터 - 데이터를 받아오는 인자
// payload - 수하물
// 개발자가 정의한 data나 에러처리에 필요한 메시지값
// 요청에 대한 응답 메시지로 사용 가능 - Toast
// 실제 서비스에서는 필요없음 - react-redux
// 순서대로 처리할 필요가 있음 - 커링함수 패턴
export const actionCreator = (type) => (payload) => ({
type,
payload,
})
export const createStore = (reducer) => {
let state;
let handlers = []
const dispatch = (action) => {
console.log('dispatch 호출')
state = reducer(state, action)
handlers.forEach(handler => handler())
}
const subscribe = ((handler) => {
handlers.push(handler)
})
const getState = () => {
return state;
}
return {
dispatch,
getState,
subscribe
}
}
<flux복습 - reducer.js>
import * as ActionType from './action-type.js'
import { initializeState } from './state.js'
// store에서 관리해야하는 상태값의 종류가 점점 늘어남 - 객체 리터럴 - 열거형 연산자 - n개 관리, 초기화 처리
// 상태를 관리하는 변수 선언
// 선언된 변수들이 payload에 담김
//상태를 변형하는 것 - 리듀서
// 첫번째 파라미터 - 상태값
// 두번째 파라미터 - 액션 - dispatch를 통해 store에 전달
//action에 담긴 정보를 dispatch가 store에 전달하기 - flux architect
// One way방식 - 한 방향으로만 흐름
// action - dispatch - store - view
// action-type.js에 별도 정의
export const reducer = (state = initializeState, action) => { // 처음예제에선 worker
switch(action.type){
case ActionType.INCREASE:
return {...state, count: state.count + 2}
case ActionType.DECREASE:
return {...state, count: state.count - 1}
case ActionType.RESET:
return { ...state, count: 0 };
case ActionType.SET_MSG:
// 깊은 복사에서 두번째 인자가 payload에 해당됨
return {...state, status: action.bool, msg:action.msg}
case ActionType.SET_FALSE:
return {...state, status: action.bool, msg:action.msg}
default:
return {...state}
}
}
<flux복습 - state.js>
export const initializeState = {
count: 0,
status: false, // Toast메시지 컴포넌트를 보여줄지 말지
msg: "", // 사용자 요청에대한 처리 결과 메시지를 담기
} // 처음엔 let state로 처리함
<flux복습 - action.js>
import { DECREASE, INCREASE, RESET, SET_FALSE, SET_MSG } from "./action-type.js";
import { actionCreator } from "./redux.js";
// store.dispatch(increase()) - dispatch는 action을 store에 전달하는 허브
// store에 들어있는 상태값을 꺼내는 것이 getState - 리액트 useSelector
export const increase = actionCreator(INCREASE)
export const decrease = actionCreator(DECREASE)
export const reset = actionCreator(RESET)
export const setToastMsg = (msg) => {
return{
type: SET_MSG,
msg: msg,
bool: true,
}
}
export const setToastFalse = () => {
return{
type: SET_FALSE,
msg: '',
bool: false,
}
}
<flux복습 - action-type.js>
// 타입선언 - 대응되는건 함수
export const INCREASE = 'increase'
export const DECREASE = 'decrease'
export const RESET = 'reset'
export const SET_MSG = 'TOAST_STATUS/SET_MSG'
export const SET_FALSE= 'TOAST_STATUS/SET_FALSE'
<리액트 리덕스 - index.js>
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "@fortawesome/fontawesome-free/js/all.js";
import ImageUploader from "./service/imageUploader";
import { Provider } from "react-redux";
import { legacy_createStore } from "redux";
import rootReducer from "./redux/rootReducer";
import AuthLogic from "./service/authLogic";
import firebaseApp from "./service/firebase";
import { setAuth } from "./redux/userAuth/action";
// 리덕스 적용하기
const store = legacy_createStore(rootReducer)
// AuthLogic객체 생성하기
const authLogic = new AuthLogic(firebaseApp)
// store에 있는 초기 상태정보 출력하기
store.dispatch(setAuth(authLogic.getUserAuth(), authLogic.getGoogleAuthProvider()))
console.log(store.getState());
// 이미지 업로더 객체 생성
const imageUploader = new ImageUploader();
const root = ReactDOM.createRoot(document.getElementById("root"));
// 리덕스 추가 - store 생성
// createStore 호출
root.render(
<>
<Provider store={store}>
<BrowserRouter>
<App imageUploader={imageUploader} />
</BrowserRouter>
</Provider>
</>
);
<리액트 리덕스 - App.jsx>
import { Route, Routes } from 'react-router-dom';
import LoginPage from './components/auth/LoginPage';
import KakaoRedirectHandler from './components/kakao/KakaoRedirectHandler';
import Profile from './components/kakao/Profile';
import MemberPage from './components/page/MemberPage';
import HomePage from './components/page/HomePage';
import DeptPage from './components/page/DeptPage';
import DeptDetail from './components/dept/DeptDetail';
import RepleBoardPage from './components/page/RepleBoardPage';
import Signup from './components/member/Signup';
import Toast from './components/Toast';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';
import { setToastMsg } from './redux/toastStatus/action';
function App({imageUploader}) {
const dispatch = useDispatch()
const toastStatus = useSelector(state => state.toastStatus)
useEffect(() => {
dispatch(setToastMsg('회원가입을 해주세요.'))
}, [])
return (
<>
<div style={{height: '100vh'}}>
{toastStatus.status && <Toast />}
<Routes>
<Route path='/login' exact={true} element={<LoginPage />} />
<Route path='/' exact={true} element={<HomePage />} />
<Route path='/home' exact={true} element={<HomePage />} />
<Route path='/member/signup' exact={true} element={<Signup />} />
<Route path='/repleboard' element={<RepleBoardPage />} />
<Route path='/dept/:gubun' element={<DeptPage imageUploader={imageUploader} />} />
{/* 컴포넌트 함수를 호출하는 것이다 - 마운트(화면이 보여지는 것) - return이 호출되었다 */}
<Route path='/deptdetail/:deptno' element={<DeptDetail imageUploader={imageUploader} />} />
<Route path='/auth/kakao/callback' exact={true} element={<KakaoRedirectHandler />} />
<Route path="/member" exact={true} element={<MemberPage imageUploader={imageUploader} />} />
<Route path="/profile" exact={true} element={<Profile />} />
</Routes>
</div>
</>
);
}
export default App;
<리액트 리덕스 - rootReducer.js>
import { combineReducers } from "redux"
import userAuth from "./userAuth/reducer";
import toastStatus from "./toastStatus/reducer";
const rootReducer = combineReducers({
userAuth,
toastStatus,
})
export default rootReducer;
<리액트 리덕스 - reducer.js>
import { toastStatus } from "./state";
import { SET_FALSE, SET_MSG } from "./action";
// 아래 toastInfo함수이름을 직접 사용하지 않음
export default function toastInfo(state = toastStatus, action) {
switch(action.type) {
case SET_MSG:
return {
...state, status: action.bool, msg: action.msg
}
case SET_FALSE:
return {
...state, status: action.bool, msg: action.msg
}
default:
return { ...state }
}
} // end of toastInfo
<리액트 리덕스 - state.js>
// reducer에서 변경하는 data에대한 선언 및 초기화
export const toastStatus = {
status: false,
msg: "",
}
<리액트 리덕스 - action.js>
// action에서 사용되는 타입 선언
export const SET_MSG = 'TOAST_STATUS/SET_MSG'
export const SET_FALSE = 'TOAST_STATUS/SET_FALSE'
// Action을 dispatch를 통해서 store에 전달할 때 호출되는 함수
// 이것이 리듀서에 전달되면 switch문에서 변화
export const setToastMsg = (msg) => {
return {
type: SET_MSG,
msg: msg,
bool: true,
}
}
export const setToastFalse = () => {
return {
type: SET_FALSE,
msg: '',
bool: false,
}
}
<리액트 리덕스 - reducer.js>
import { SET_AUTH } from "./action"
import { userAuth } from "./state"
export default function userInfo(state = userAuth, action){
switch(action.type){
case SET_AUTH:
return{
...state,
auth: action.auth,
googleProvider: action.googleProvider,
}
default:
return { ...state }
}
}
<리액트 리덕스 - state.js>
// service > authLogic.js > AuthLogic객체선언
export const userAuth = {
auth: "", // firebase/auth
googleProvider: "", // 구글 공급자 정보
// githubProvider:"", 깃헞브 공급자 정보
}
<리액트 리덕스 - action.js>
export const SET_AUTH = "USER_AUTH/SET_AUTH"
export const setAuth = (auth, googleProvider) => {
return {
type: SET_AUTH,
auth: auth,
googleProvider: googleProvider,
}
}
<리액트 리덕스 - authLogic.js>
import { getAuth, GoogleAuthProvider, signInWithPopup } from 'firebase/auth'
class AuthLogic {
constructor() {
this.auth = getAuth()
this.googleProvider = new GoogleAuthProvider()
}
getUserAuth = () => {
return this.auth
}
getGoogleAuthProvider = () => {
return this.googleProvider
}
}
export default AuthLogic
export const onAuthChange = (auth) => {
return new Promise((resolve) => {
// 사용자가 바뀌었을 때 콜백함수를 받아서
auth.onAuthStateChanged((user) => {
resolve(user)
});
}) // end of Promise
} // end of onAuthChange
// 로그아웃 버튼 클릭시 호출하기
export const logout = (auth) => {
return new Promise((resolve, reject) => {
auth.signOut().catch(e => reject(e + '로그아웃 오류입니다.'))
// 로그인 성공시 세션 스토리지에 담아둔 정보를 모두 지운다
sessionStorage.clear();
// 서비스를 더 이상 사용하지 않는 경우이므로 돌려줄 값은 없다
resolve();
})
} // end of logout
// 로그인 시도시 구글인증인지 아니면 깃허브 인증인지 문자열로 넘겨받음
// 구글 인증인 경우 - Google
// 깃허브 인증인 경우 - Github
export const loginGoogle = (auth, googleProvider) => {
return new Promise((resolve, reject) => {
signInWithPopup(auth, googleProvider).then(
(result) => {
const user = result.user; // 구글에 등록되어있는 profile정보가 담겨있음
console.log(user);
resolve(user)
}
).catch(e => reject(e))
})
}
<리액트 리덕스 - Toast.jsx>
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { setToastFalse } from '../redux/toastStatus/action';
import './toast.css';
const ToastDiv = styled.div`
position: fixed;
top: 50%;
left: 50%;
padding: 11px;
min-width: 350px;
transform: translate(-50%, -50%);
justify-content: center;
text-align: center;
//font-weight: bold;
font-size: 18px;
z-index: 99;
background: rgba(0, 0, 0, 0.7);
color: #ffffff;
border-radius: 4px;
border: 1px solid #000000;
`
const Toast = () => {
const toastStatus = useSelector(state => state.toastStatus);
const dispatch = useDispatch();
useEffect(() => {
if (toastStatus.status) {
setTimeout(() => {
dispatch(setToastFalse());
}, 1500)
}
}, [toastStatus.status, dispatch]);
return (
<ToastDiv>{JSON.stringify(toastStatus.msg)}</ToastDiv>
);
};
export default Toast;
<리액트 리덕스 - LoginPage.jsx>
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Link, useNavigate } from 'react-router-dom'
import { setToastMsg } from '../../redux/toastStatus/action'
import { loginGoogle } from '../../service/authLogic'
import './loginpage.css'
const LoginPage = () => {
const navigate = useNavigate()
// One-Way 바인딩에서 액션을 스토어에 전달할 때 디스패치 사용함
const dispatch = useDispatch()
// store에 초기화된 state 안에 담긴 data 꺼내올 때 셀렉터 사용
const userAuth = useSelector((store) => store.userAuth)
const auth = useSelector((store) => store.userAuth.auth)
console.log(auth);
const googleProvider = useSelector((store) => store.userAuth.googleProvider)
console.log(googleProvider);
const handleGoogle = async(event) => {
event.preventDefault()
try {
const result = await loginGoogle(auth, googleProvider)
console.log(result.uid);
window.sessionStorage.setItem('userId', result.uid)
navigate('/')
} catch (error) {
dispatch(setToastMsg(error + ': 로그인 오류입니다.')) // [object, object] -> JSON.stringify()
}
} //end of handleGoogle
return (
<>
<div className="bg"></div>
<form className="login__form" action="">
<h1>로그인</h1>
<p>신규 사용자이신가요? <Link to="#">계정 만들기</Link></p>
<label htmlFor="useremail">이메일 주소
<input type="email" id="useremail" placeholder="이메일 주소를 입력해주세요."/>
</label>
<input type="submit" className="btn__submit" value="계속" />
<div className="divider">
<hr />
<span>또는</span>
</div>
<button className="btn__google" onClick={handleGoogle}>
<i className="icon icon-google"></i>Google으로 계속
</button>
<button className="btn__facebook">
<i className="icon icon-facebook"></i>Facebook으로 계속
</button>
<p className="captcha__text">
reCAPTCHA로 보호되며 Google <Link to="#">개인정보보호 정책</Link> 및
<Link to="#">서비스 약관</Link>의 적용을 받습니다.
</p>
</form>
</>
)
}
export default LoginPage
<리액트 리덕스 - HomePage.jsx>
import React from 'react'
import { Button } from 'react-bootstrap'
import { useNavigate } from 'react-router-dom'
import BlogHeader from '../include/BlogHeader'
import KakaoMap from '../kakao/KakaoMap'
import {ContainerDiv, FormDiv, HeaderDiv} from '../styles/FormStyle'
const HomePage = () => {
const navigate = useNavigate()
const handleLogin = () => {
console.log('로그인 요청');
navigate('/login')
}
return (
<>
<ContainerDiv>
<BlogHeader />
<HeaderDiv>
<h1 style={{marginLeft:"10px"}}>터짐블로그</h1>
<Button onClick={handleLogin}>로그인</Button>
</HeaderDiv>
<FormDiv>
<div>이벤트존</div>
<hr style={{height:"2px"}} />
<div>추천 수업존</div>
<hr style={{height:"2px"}} />
<div><KakaoMap /></div>
<div>카카오맵존</div>
<hr style={{height:"2px"}} />
</FormDiv>
</ContainerDiv>
</>
)
}
export default HomePage
<props로 넘겨받기 - index.js>
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import ReduxApp from "../ReduxApp";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<>
<App />
</>
);
<props로 넘겨받기 - App.jsx>
import "./App.css";
import Header from "./components/include/Header";
import Footer from "./components/include/Footer";
import MainPage from "./components/page/MainPage";
import { useState } from "react";
function App() {
const num = 0; // 리렌더링 없음
const [number, setNumber] = useState(0) // 리렌더링O
const addNumber = () => {
console.log('addNumber');
setNumber(number + 1)
}
return (
<>
<div className="container">
<Header number={number} />
<MainPage number={number} addNumber={addNumber} />
<Footer addNumber={addNumber} />
<button onClick={addNumber}>추가-{number}</button>
</div>
</>
);
}
export default App;
/*
리렌더링 3가지 - 부모컴포넌트, props, state
*/
<props로 넘겨받기 - Header.jsx>
import React from 'react'
const Header = ({number}) => { // props로 넘어온 값을 바로 구조분해할당
return (
<>
<div className='header_container'>
헤더영역
<h4>{number}</h4>
</div>
</>
)
}
export default Header
<props로 넘겨받기 - MainPage.jsx>
import React from 'react'
const MainPage = (props) => {
const{number, addNumber} = props
return (
<>
<div className='main_container'>
컨텐츠 영역
<h4>{number}</h4>
<button onClick={addNumber}>메인버튼</button>
</div>
</>
)
}
export default MainPage
<props로 넘겨받기 - Footer.jsx>
import React from 'react'
const Footer = (props) => {
const {addNumber} = props // 구조분해할당
return (
<>
<div className='footer_container'>
푸터영역<br />
<button onClick={addNumber}>푸터증가버튼</button>
</div>
</>
)
}
export default Footer
<Redux로 바꾸기 - index.js>
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import ReduxApp from "./ReduxApp";
import { Provider } from "react-redux";
import { legacy_createStore } from "redux";
import reducer from "./redux/store";
const store = legacy_createStore(reducer)
console.log(store.getState());
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<>
{/* <App /> */}
<Provider store={store}>
<ReduxApp />
</Provider>
</>
);
<Redux로 바꾸기 - ReduxApp.jsx>
import React from 'react'
import ReduxFooter from './components/include/ReduxFooter'
import ReduxHeader from './components/include/ReduxHeader'
import ReduxMainPage from './components/page/ReduxMainPage'
const ReduxApp = () => {
return (
<>
<h3>리덕스 적용 예제</h3>
<div className='container'>
<ReduxHeader />
<ReduxMainPage />
<ReduxFooter />
</div>
</>
)
}
export default ReduxApp
<Redux로 바꾸기 - ReduxHeader.jsx>
import React from 'react'
const ReduxHeader = () => {
return (
<>
<div className='header_container'>
리덕스 헤더영역
</div>
</>
)
}
export default ReduxHeader
<Redux로 바꾸기 - ReduxMainPage.jsx>
import React from 'react'
import { useSelector } from 'react-redux'
const ReduxMainPage = () => {
const number = useSelector(store => store.number)
return (
<>
<div className='main_container'>
컨텐츠 영역
<h4>{number}</h4>
</div>
</>
)
}
export default ReduxMainPage
<Redux로 바꾸기 - ReduxFooter.jsx>
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { increase } from '../../redux/store'
const ReduxFooter = () => {
const dispatch = useDispatch()
const handleAdd = (e) => {
e.preventDefault() // 이벤트 버블링 차단
dispatch(increase('가나다'))
}
return (
<>
<div className='footer_container'>
리덕스 푸터영역<br />
<button onClick={handleAdd}>증가버튼</button>
</div>
</>
)
}
export default ReduxFooter
<Redux로 바꾸기 - store.js>
// 액션
export const increase = (mem_name) => ({
type: 'INCREASE',
payload: mem_name,
})
// 초기상태 만들기
const initstate = {
number: 0,
mem_name: '이순신',
empVO: {empno: 7566, ename: '나신입'},
depts: [
{ DEPTNO: 10, DNAME: '총무부', LOC: '인천'},
{ DEPTNO: 20, DNAME: '영업부', LOC: '서울'},
{ DEPTNO: 30, DNAME: '개발부', LOC: '괌'},
],
auth: '',
googleProvider: '',
}
// worker가 reducer함수로 전환
//액션의 결과를 필터링 - 리듀서
const reducer = (state = initstate, action) => {
switch(action.type) {
case "INCREASE":
// return이되면 그걸 호출한 쪽에서 받는게 아니고, return되는 순간 바로 화면이 변경됨
return { ...state, number: state.number + 1, mem_name: action.payload }
default:
return { ...state } // 깊은 복사, 새로운 객체 생성됨
}
}
export default reducer
'국비학원 > 수업기록' 카테고리의 다른 글
국비 지원 개발자 과정_Day84 (0) | 2023.03.29 |
---|---|
국비 지원 개발자 과정_Day83 (1) | 2023.03.28 |
국비 지원 개발자 과정_Day81 (0) | 2023.03.25 |
국비 지원 개발자 과정_Day80 (0) | 2023.03.23 |
국비 지원 개발자 과정_Day79 (0) | 2023.03.22 |
댓글