Redux 데이터 중앙관리
리덕스 설치
npm i next-redux-wrapper
npm i redux
npm i react-redux
npm i redux-devtools-extension
Redux devTools 확장 프로그램 설치
어제까지 만든 react basic layout 코드에 redux 사용하는 듯 ! ? -> context 대체
REDUX
1. store/configureStore.js 만드는 이유
context.jsx 와 비슷
- 초기값, 상태값, createContext(초기값)
- const Store = craeteContext(initialState) -> 결과값 : component - 초기값 저장하고 내보내는 아이
configureStore.js 는?
- createStore === createContext 와 비슷하다. 하지만 가지는 값은 다르다
- createStore(reducer) !== createContext(초기값) createStore는 reducer를 담음
- reducer 안에 상태 초기값이 있음
redux와 context와 비슷
context는 Store 초기값을 넣고
redux는 Store에 reducer 함수를 넣음
1) 아래 packages 설치
2) store > configureStore.js 생성
Components 는 .jsx
data와 관련된 것은.js ---> 정답은 없다 (개인 취향)
import {createWrapper} from 'next-redux-wrapper'
import {createStore} from 'redux'
import reducer from '../reducers' //index 생략 가능
const configureStore=()=>{
const Store = createStore(reducer)
return Store
}
// Store 자체를 가지는게 아닌 Store를 return해 주는 함수를 담아야함
// configureStore = Store를 가지고 있는 함수 표현식에 대한 함수
const wrapper = createWrapper(configureStore)
export default wrapper
순서 :
(1) export default wrapper ----> wrapper 생성 ---> (2)
(2) createWrapper(Store) -----> configureStore 가 필요 ---> (3)
(3) createStore(reducer) -----> reducer 필요 ----> (4)
(4) import reducer ------> reducers 폴더 생성 > index.js 파일 생성
3) front > reducers 폴더 생성 > index.js 파일 생성
//context와 다른점 : 여기서 초기값을 입력해줌
const initialState = {
user:{
IsLogin:false,
},
posts:{
},
category:{
},
}
const USER_LOGIN = "USER_LOGIN" // 오타 났을 때 대비
const USER_LOGOUT = "USER_LOGOUT"
const reducer = (state = initialState,action)=>{
switch(action.type){
case USER_LOGIN:
console.log('로그인 신호왓다')
return{
...state,
user:{
...state.user,
IsLogin:true
}
}
case USER_LOGOUT:
return{
...state,
user:{
...state.user,
}
}
default:
console.log('start') // 맨 처음 default가 실행된다. (action 값 없음)
return state // defualt 가 없으면 ? 에러가 언젠가는 난다.
}
}
export default reducer
큰 테두리는 얇은 복사로 다른 메모리 참조
안에 있는 특정 값들은 참조해서 가져오는 것 (메모리 효율성을 위해)
여태 ...state, 했던 부분 얕은 복사
이번 리덕스에서 user:{ ...state.user, IsLogin:true} 포함한 이유 :
-> 안에 있는 내용까지 바꿀 것이라서 !
_app.jsx
Provider 와 같이 어떤 부분에서 redux를 시작할지 정해주기 - 최상위 compo = _app.jsx
import '../index.css'
import Head from 'next/head'
import Store, { initialState } from '../store/context'
import { useReducer, useContext } from 'react'
import { reducer } from '../store/reducer'
import wrapper from '../store/configureStore'
const App = ({ Component }) => {
const globalContext = useContext(Store) // context에 있는 값을 그대로 빼와서 담음
const [state, dispatch] = useReducer(reducer, globalContext)
return (
<>
<Head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="true" />
<link href="https://fonts.googleapis.com/css2?family=Black+Han+Sans&display=swap" rel="stylesheet" />
</Head>
<Store.Provider value={{ state, dispatch }}>
<Component /> {/* 우리가 만든 파일(index,login..)들이 여기에 위치 */}
</Store.Provider>
</>
)
}
export default wrapper.withRedux(App) //App 아래로 사용하겠다 ~
useReducer는 react-redux 에 있음 (package)
----------------- Login 할 때 써보기
login.jsx
import FormLayout from "../components/FormLayout"
import Head from 'next/head'
import Router from 'next/router'
import useInput from '../hooks/useInput'
import {useDispatch} from 'react-redux'
const Login = () => {
const dispatch = useDispatch()
const userid = userInput('') // object
const userpw = userInput('') // object
const handleSubmit=(e)=>{
e.preventDefault()
dispatch({type:"USER_LOGIN"})
userid.value==="asdf" && userpw.value==="asdf" ? Router.push('/') : alert('id 또는 pw가 다릅니다.')
}
reducer - console.log 찍고 확인
const reducer = (state = initialState,action)=>{
switch(action.type){
case USER_LOGIN:
console.log('로그인 신호왓다')
return{
사용법
context 의 경우
1) Store 가져오기 (상태값)
2) useContext 가져오기 (context사용하기 위한 매서드)
3) dispatch({type:'LOGOUT'}) 사용
const {dispatch} = useContext(Store) -> _app.js에서 <Store.provider value={{state,dispatch}} /> 요렇게 보내 준 것 중 필요한 것을 가져올 수 있었음.
Redux 의 경우
1) useDispatch 가져오기 (dispatch하면 -> reducer 이미 reducer 안에 state 가 함께 있음)
2) dispatch에 useDispatch() 함수 넣기
3) dispatch 사용
질문 : useDispatch, useSelector
dispatch를 쓰는 Header.jsx에서 useDipatch를 가져옴
IsLogin 상태값이 필요한 Login.jsx, Logout.jsx에서는 useSelector 매서드 가져와서 필요한 변수값 추출함
reducer 의 type을 변수로 빼기
오타 대비해서 코드의 생산성은 떨어지지만 디버깅에는 좋음
reducers> index.js 추가
const USER_LOGIN = "USER_LOGIN" // 오타 났을 때 대비
const USER_LOGOUT = "USER_LOGOUT"
export const UserLoginAction =()=>{
return{
type:USER_LOGIN
}
}
login.jsx
import FormLayout from "../components/FormLayout"
import Head from 'next/head'
import Router from 'next/router'
import useInput from '../hooks/useInput'
// redux
import {useDispatch} from 'react-redux'
import {UserLoginAction} from '../reducers' //가져오기
const Login = () => {
const dispatch = useDispatch()
const userid = userInput('') // object
const userpw = userInput('') // object
const handleSubmit=(e)=>{
e.preventDefault()
dispatch(UserLoginAction()) // 요렇게 수정
userid.value==="asdf" && userpw.value==="asdf" ? Router.push('/') : alert('id 또는 pw가 다릅니다.')
}
Header.jsx 의 context -> Redux로 교체
header.jsx
context 지우고 useSlector 가져오기
import Link from 'next/link'
import NavToggle from '../NavToggle'
import Styled from 'styled-components'
import { useContext } from 'react'
import Store from '../../store/context'
//redux
import {useSelector} from 'react-redux' // 1. useSelector 가져오기
const Header = () => {
// context
// const globalStore = useContext(Store)
// const { IsLogin } = globalStore.state
const IsLogin = useSelector((state)=>state.user.IsLogin) // 2. 사용
// == const {IsLogin} = useSelector((state)=>state.user)
console.log(IsLogin)
질문 : 터미널에 console.log가 안찍히고 브라우저에 찍힘 - > 교수님은 터미널에도 찎힘 !
이미 IsLogin 삼항연산자 있어서 고칠필요 X 나머지 코드만 간략히 만들기
import Link from 'next/link'
import NavToggle from '../NavToggle'
import Styled from 'styled-components'
import { useContext } from 'react'
import Store from '../../store/context'
//redux
import { useSelector } from 'react-redux'
const LoginComponent = () => {
return (
<>
<li><Link href="/login"><a>로그인</a></Link></li>
<li><Link href="/join"><a>회원가입</a></Link></li>
</>
)
}
const LogoutComponent = () => {
return (
<>
<li><Link href="/logout"><a>로그아웃</a></Link></li>
<li><Link href="/profile"><a>회원정보</a></Link></li>
</>
)
}
const Header = () => {
// context
// const globalStore = useContext(Store)
// const { IsLogin } = globalStore.state
const IsLogin = useSelector((state) => state.user.IsLogin)
// == const {IsLogin} = useSelector((state)=>state.user)
console.log(IsLogin)
return (
<>
<HeaderContainer>
{/* 로고 & 메뉴 */}
<h1>로고</h1>
<Gnb>
<li>
<Link href="/">
<a>Home</a>
</Link>
</li>
<li>
<Link href="/posts/post">
<a>글쓰기</a>
</Link>
</li>
{IsLogin === false ? <LoginComponent/> :<LogoutComponent/>}
</Gnb>
<NavToggle />
</HeaderContainer>
</>
)
}
css생략
login 하고 -> logout 오류남 !
이유 : 서로 바라보고 있는 상태가 다름
login -> useDispatch(Redux)
logout -> useContext
=> Context API로 만든 IsLogin 상태값과 Redux로 만든 IsLogin 값은 다름
Logout.jsx 도 같은 상태값(Redux 사용되는) 으로 변경하기
// import Store from '../store/context'
import { useContext, useEffect } from 'react'
import Router from 'next/router'
import { useDispatch } from 'react-redux'
const logout=()=>{
// const {dispatch} = useContext(Store)
const dispatch = useDispatch()
useEffect(()=>{
dispatch({type:'logout'})
setTimeout(()=>{
Router.back()
},1000)
},[]) // 빈배열일 경우render 완료 되었을 때 딱 한번 실행되는 코드가 됨
reducers - index.js 에 변수 추가
const USER_LOGIN = "USER_LOGIN" // 오타 났을 때 대비
const USER_LOGOUT = "USER_LOGOUT"
export const UserLoginAction =()=>{
return{
type:USER_LOGIN
}
}
export const UserLogoutAction =()=>{
return{
type:USER_LOGOUT
}
}
logout.jsx에 import 해서 변수로 넣기
// import Store from '../store/context'
import { useContext, useEffect } from 'react'
import Router from 'next/router'
//redux
import { useDispatch } from 'react-redux'
import { UserLogoutAction} from '../reducers'
const logout=()=>{
// const {dispatch} = useContext(Store)
const dispatch = useDispatch()
useEffect(()=>{
dispatch(UserLogoutAction())
setTimeout(()=>{
Router.back()
},1000)
},[]) // 빈배열일 경우render 완료 되었을 때 딱 한번 실행되는 코드가 됨
대분류 메뉴들도 상태값에 넣어서 관리하게 편하게 만들기
Accordion.jsx의 변수 menu 를 잘라서( 복사 x / 자르기 o ) -> reducers>index.js의 category에 담기
//context와 다른점 : 여기서 초기값을 입력해줌
const initialState = {
user:{
IsLogin:false,
},
posts:{
},
category:{
menu:[ // server db 에서 이렇게 데이터가 왔다고 가정
{
id: '1',
category: '대분류메뉴1',
url: '/posts/1'
},
{
id: '2',
category: '대분류메뉴2',
url: '/posts/2'
},
{
id: '3',
category: '대분류메뉴3',
url: '/posts/3'
},
{
id: '4',
category: '대분류메뉴4',
url: '/posts/4'
},
{
id: '5',
category: '대분류메뉴5',
url: '/posts/5'
}
]
},
}
상태값을 가져오는 건 - useSelector
Accordion 에 해당 상태값 가져오기 -> 뿌려주기 (map은 기존 코드엿슴)
import Styled from 'styled-components'
import Link from 'next/link'
import { useSelector } from 'react-redux'
const Accordion = ({ visible, handleToggle }) => {
// const handleClick=(e)=>{
// handleToggle()
// }
const {menu} = useSelector((state)=>state.category)
const category = menu.map((v, k)=>{
return <li key={v.id} onClick={handleToggle}>
<Link href={v.url}>
<a>{v.category}</a>
</Link>
</li>
})
질문 : 그럼 reducers> index.js data와 관련된 곳이면 db연결해서 가져오는 곳도 index.js ??
-> 단점 : index.js 의 파일이 너무너무 길어짐 & 하나의 파일에 코드가 길면 관리 힘듬
-> Redux 에서 reducer를 쪼개서 사용하라고 권장 (combine)
user , posts, category 요렇게 따로 파일 쪼개기
Redux reducer 파일 쪼개기
1) reducers > users.js, category.js, posts.js 생성
2) 각 파일은 보통 reducer 처럼 만들어 주기 (기본)
아래 기본 코드 3개 파일에 복붙
const initialState ={
}
const reducer =(state=initialState, action)=>{
switch(action.type){
default:
return state
}
}
export default reducer
3) uses.js 부터 완성 시키기
- IsLogin:false 넣기
- users 관련한 case 들 붙여 넣기
- users (login, logout)관련한 변수 설정한 것들도 모두 가져오기
const initialState = {
IsLogin: false,
}
const USER_LOGIN = "USER_LOGIN" // 오타 났을 때 대비
const USER_LOGOUT = "USER_LOGOUT"
export const UserLoginAction = () => {
return {
type: USER_LOGIN
}
}
export const UserLogoutAction = () => {
return {
type: USER_LOGOUT
}
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case USER_LOGIN:
console.log('로그인 신호왓다')
return {
...state, // state 가 달라져서 요기도 이렇게 바뀜
IsLogin: true
}
case USER_LOGOUT:
return {
...state,
IsLogin: false
}
default:
return state
}
}
export default reducer
4) reducer 합치기 (COMBINE)
combine -> switch 를 합치려고 !
index.js 에서 combineReducers from 'redux' 가져오기
import { combineReducers } from "redux"
const reducer = combineReducers({
index: (state={}, action)=>{
switch(action.type){
default:
return state
}
},
//user는 user.js파일에서 미리 함수로 만들어 놓아서 import 함수만 해오면 됨
// user:
})
export default reducer
user.js 가져오기
import { combineReducers } from "redux"
import user from './user'
import { combineReducers } from "redux"
import user from './user'
//context와 다른점 : 여기서 초기값을 입력해줌
const initialState = {
user:{
},
posts:{
},
category:{
menu:[ // server db 에서 이렇게 데이터가 왔다고 가정
{
id: '1',
category: '대분류메뉴1',
url: '/posts/1'
},
{
id: '2',
category: '대분류메뉴2',
url: '/posts/2'
},
{
id: '3',
category: '대분류메뉴3',
url: '/posts/3'
},
{
id: '4',
category: '대분류메뉴4',
url: '/posts/4'
},
{
id: '5',
category: '대분류메뉴5',
url: '/posts/5'
}
]
},
}
// const reducer = (state = initialState,action)=>{
// switch(action.type){
// default:
// console.log('start')
// return state
// }
// }
//인자값 1개 객체만 받는다.
const reducer = combineReducers({
index: (state={}, action)=>{
switch(action.type){
default:
return state
}
},
//user는 user.js파일에서 미리 함수로 만들어 놓아서 import 함수만 해오면 됨
user, // == user:user
})
export default reducer
login.jsx 에도 UserLoginAction 가져오는 경로 변경
import {UserLoginAction} from '../reducers/user' //가져오기
logout.jsx 도 수정
import { UserLogoutAction } from '../reducers/user'
이제 menu의 값을 가져오는데도 오류가 생김 -> 아직 index.js의 reducer에 category 가 아직 없음 ! 그리고 category.js에도 아직 menu 복붙을 안함
index.js의 menu 짜르기 -> category.js 의 initialState에 복붙
const initialState ={
menu:[ // server db 에서 이렇게 데이터가 왔다고 가정
{
id: '1',
category: '대분류메뉴1',
url: '/posts/1'
},
{
id: '2',
category: '대분류메뉴2',
url: '/posts/2'
},
{
id: '3',
category: '대분류메뉴3',
url: '/posts/3'
},
{
id: '4',
category: '대분류메뉴4',
url: '/posts/4'
},
{
id: '5',
category: '대분류메뉴5',
url: '/posts/5'
}
]
}
const reducer =(state=initialState, action)=>{
switch(action.type){
default:
return state
}
}
export default reducer
index.js - category 가져와서 category reducer에 넣기
import { combineReducers } from "redux"
import user from './user'
import category from './category'
//context와 다른점 : 여기서 초기값을 입력해줌
const initialState = {
user:{},
posts:{},
category:{},
}
// const reducer = (state = initialState,action)=>{
// switch(action.type){
// default:
// console.log('start')
// return state
// }
// }
//인자값 1개 객체만 받는다.
const reducer = combineReducers({
index: (state={}, action)=>{
switch(action.type){
default:
return state
}
},
//user는 user.js파일에서 미리 함수로 만들어 놓아서 import 함수만 해오면 됨
user, // == user:user
category,
})
export default reducer
=> 필요없는 부분 제거
import { combineReducers } from "redux"
import user from './user'
import category from './category'
//인자값 1개 객체만 받는다.
const reducer = combineReducers({
index: (state={}, action)=>{
switch(action.type){
default:
return state
}
},
//user는 user.js파일에서 미리 함수로 만들어 놓아서 import 함수만 해오면 됨
user, // == user:user
category,
})
export default reducer
index: state가 없다. case HYDRATE 추가해주기 (이유:오리무중)
import {HYDRATE} from 'next-redux-wrapper'
import { combineReducers } from "redux"
import user from './user'
import category from './category'
//인자값 1개 객체만 받는다.
const reducer = combineReducers({
index: (state={}, action)=>{
switch(action.type){
case HYDRATE:
return {
...state,
...action.payload
}
default:
return state
}
},
//user는 user.js파일에서 미리 함수로 만들어 놓아서 import 함수만 해오면 됨
user, // == user:user
category,
})
export default reducer
DevTools
Redux devTools 확장 프로그램 설치하기
여기서 middlewares : 부모님이다
reducer(action) <= middlewares => state
이제 reducer(action)와 state 가 직접적으로 바로 통신하는게 아니라 middlewares를 통해 통신 ~
middlewares 부모님 허락 맡고 만나,,
중간다리 역할이다 (내일 middleware에 대해 더 딥하게 공부 !)
어떠한 action(type)이 들어오면 ---> middlewares ---> state
* middlewares의 역할 : 데이터를 가져온다거나 / 몇 초 뒤에 실행한다거나 / 비동기 통신 등등...
1. configureStore.js
추가한 코드의 의미 : state 상태값을 만들 때, middlewares를 통하도록 하겠다.
createStore의 첫 번째 인자값 : reducer
createStore의 두 번째 인자값 : middlewares를 사용할 지 정하기
import {createWrapper} from 'next-redux-wrapper'
import {applyMiddleware, compose, createStore} from 'redux'
import reducer from '../reducers' //index 생략 가능
import {compostWithDevTools} from 'redux-devtools-extension'
const configureStore=()=>{
const middlewares = []
const enhancer = process.env.NODE_ENV === 'production' //우리는 develope mode
? compose(applyMiddleware(...middlewares))
: composeWithDevTools(applyMiddleware(...middlewares)) // 우리가 사용할 코드
const Store = createStore(reducer, enhancer)
return Store
}
// Store 자체를 가지는게 아닌 Store를 return해 주는 함수를 담아야함
const wrapper = createWrapper(configureStore,{
debug:process.env.NODE_ENV === 'development'
//debug:true (개발모드이므로) 와 같은 의미
})
export default wrapper
composeWithDevTools -> 이 아이가 dispatch를 루팡해옴
실행할 떄마다 dispatch를 저장해 놓음
dispatch로 모든 코드 실행의 history를 냄겨놓음 / 순서대로 수집까지함 => composeWithDevTools의 역할
우리가 원하는 곳으로 갈 수 있음
createWrapper의 두 번째 인자값 : option
debug 추가하면 터미널에 아래와 같은 코드가 나옴
브라우저에도 똑같이 뜬다.
2) redux 확장 프로그램 확인해보기
왼쪽 : dispatch action 값 쭈루루룪 나옴
오른쪽 action: action
state : 현재 내 상태값
diff : 바뀐 값
로그인을 하게 되면
dispatch 사용했기 때문에 왼쪽에 USER_LOGIN (action.type)이 생김 (action 값 훔쳐옴)
login, logout etc ...dispatch 사용할 때마다 ~ 수집
action 값도 확인할 수 있음
JUMP 기능
-> 로그인 / 로그아웃 상태로 돌아가기
장점 : 개발 중 로그인했을 때 쓸 수 있는 상황 등 이럴 때 편함 ! => 상태관리가 편하다
코드의 생산성이 조금 떨어지지만 나머지 모든 것들이 좋다 !
질문
1. {} === {} false 인 이유??
2. 상수 ?
'블록체인 기반 핀테크 및 응용 SW개발자 양성과정 일기' 카테고리의 다른 글
[91-110일차] 어플 개발 with React Native Expo <Before you die > (0) | 2021.08.04 |
---|---|
[99일차] Redux 리액트 리덕스 Middleware Thunk (0) | 2021.07.30 |
[95-97일차 복습] 리액트 (+NEXT) 웹 홈페이지 만들기 기초 (레이아웃 / 로그인 / 회원가입) (0) | 2021.07.29 |
[97일차]20210728 React 리액트 회원가입 로그인 / 리덕스 설치 (0) | 2021.07.28 |
[96일차] 20210727 React 리액트, 레이아웃 CSS, 새로고침할 때 CSS 풀리는 현상, (0) | 2021.07.27 |