* 리덕스 참조 :
2) React - 생활코딩 4 - Redux , 글 생성 , 수정,삭제 구현 Object.Assign 깊은 복사
3 )React 따라하며 배우는 노드, 리액트 시리즈(John Ahn) - 3 Redux (리덕스 ), 로그인 , 회원 가입 , 인증 체크
vscode 확장 패키지 추가
1. - Auto Import - ES6, TS, JSX, TSX
2. - Reactjs code snippets
3. - ESLint
4. - Prettier - Code formatter
Visual Studio Code 폴더/파일 아이콘 변경하기
리액트 프로젝트 생성
npx create-react-app 경로
예) npx create-react-app E:\react-app2
* 리액트 리덕스에 필요한 패키지 설치
1.리덕스 설치
$ yarn add redux react-redux
2. redux-logger 설치
& yarn add redux-logger
3. 크롬 웹스토어에서 redux-devtool 설치
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko
4. redux-devtools-extension 설치
$ yarn add redux-devtools-extension
redux-devtools 사용법 및 설정 (구글 검색 redux extension devtools)
https://github.com/zalmoxisus/redux-devtools-extension
5.리덕스에서 비동기 작업을 처리 위해 redux-thunk 설치
$ yarn add redux-thunk
소스 : https://github.com/braverokmc79/react-redux-setting
Toolkit 적용 소스 : https://github.com/braverokmc79/react-redux-toolkit-setting
1. 리액트 리덕스 (Redux) 튜토리얼 순한맛 1/2
store.js
const redux = require('redux'); const reuxLogger = require('redux-logger'); const createStore = redux.legacy_createStore; const applyMiddleware = redux.applyMiddleware; const logger = reuxLogger.createLogger(); const combineReducers = redux.combineReducers; //action //action-type const ADD_SUBSCRIBER = 'ADD_SUBSCRIBER'; const ADD_VIEWCOUNT = 'ADD_VIEWCOUNT'; const addSubscriber = () => { return { type: ADD_SUBSCRIBER } } const addViewCount = () => { return { type: ADD_VIEWCOUNT } } //reducers const subscribeState = { subscribers: 356 } const subscriberReducer = (state = subscribeState, action) => { switch (action.type) { case ADD_SUBSCRIBER: return { ...state, subscribers: state.subscribers + 1 } default: return state; } } const viewState = { viewCount: 100 } const viewReducer = (state = viewState, action) => { switch (action.type) { case ADD_VIEWCOUNT: return { ...state, viewCount: state.viewCount + 1 } default: return state; } } const rootReducer = combineReducers({ view: viewReducer, subscriber: subscriberReducer, }) //store const store = createStore(rootReducer, applyMiddleware(logger)); console.log("store 정보 :", store); //subscribe -view - dispatch // console.log("1.store : ", store.getState()); // store.dispatch(addSubscriber()); // console.log("2.store : ", store.getState()); // store.subscribe(() => { // console.log('subscribe ===>>', store.getState()); // }); store.dispatch(addSubscriber()); store.dispatch(addSubscriber()); store.dispatch(addSubscriber()); store.dispatch(addViewCount()); store.dispatch(addViewCount());
출력=>
store 정보 : { dispatch: [Function (anonymous)], subscribe: [Function: subscribe], getState: [Function: getState], replaceReducer: [Function: replaceReducer], '@@observable': [Function: observable] } action ADD_SUBSCRIBER @ 20:08:55.279 prev state { view: { viewCount: 100 }, subscriber: { subscribers: 356 } } action { type: 'ADD_SUBSCRIBER' } next state { view: { viewCount: 100 }, subscriber: { subscribers: 357 } } action ADD_SUBSCRIBER @ 20:08:55.282 prev state { view: { viewCount: 100 }, subscriber: { subscribers: 357 } } action { type: 'ADD_SUBSCRIBER' } next state { view: { viewCount: 100 }, subscriber: { subscribers: 358 } } action ADD_SUBSCRIBER @ 20:08:55.283 prev state { view: { viewCount: 100 }, subscriber: { subscribers: 358 } } action { type: 'ADD_SUBSCRIBER' } next state { view: { viewCount: 100 }, subscriber: { subscribers: 359 } } action ADD_VIEWCOUNT @ 20:08:55.284 prev state { view: { viewCount: 100 }, subscriber: { subscribers: 359 } } action { type: 'ADD_VIEWCOUNT' } next state { view: { viewCount: 101 }, subscriber: { subscribers: 359 } } action ADD_VIEWCOUNT @ 20:08:55.286 prev state { view: { viewCount: 101 }, subscriber: { subscribers: 359 } } action { type: 'ADD_VIEWCOUNT' } next state { view: { viewCount: 102 }, subscriber: { subscribers: 359 } }
2. 리액트 리덕스(Redux)실습강좌 ( 프로젝트에 넣기)
1.리덕스 설치
$ yarn add redux react-redux
2.구독 리덕스 만들기
1) src 디렉토리 안에 redux 디렉토리 생성후 redux\subscribers 디렉토리를 생성한다.
types.js 파일 생성 후 구독 추가 삭제 상수 설정
src\redux\subscribers\types.js
export const ADD_SUBSCRIBER = 'ADD_SUBSCRIBER'; export const REMOVE_SUBSCRIBER = 'REMOVE_SUBSCRIBER';
2) action 생성
src\redux\subscribers\actions.js
import { ADD_SUBSCRIBER, REMOVE_SUBSCRIBER } from './types'; export const addSubscriber = () => { console.log("1.action - addSubscriber:"); return { type: ADD_SUBSCRIBER } } export const removeSubscriber = () => { console.log("1.action - removeSubscriber:"); return { type: REMOVE_SUBSCRIBER } }
3) reducer 생성
src\redux\subscribers\reducer.js
import { ADD_SUBSCRIBER, REMOVE_SUBSCRIBER } from './types'; const initialState = { count: 370 } const subscriberReducer = (state = initialState, action) => { console.log("2. action.type :", action.type); switch (action.type) { case ADD_SUBSCRIBER: return { ...state, count: state.count + 1 } case REMOVE_SUBSCRIBER: return { ...state, count: state.count - 1 } default: return state; } } export default subscriberReducer;
4) store 생성
src\redux\store.js
import { legacy_createStore as createStore, applyMiddleware } from 'redux'; import subscriberReducer from './subscribers/reducer'; const store = createStore(subscriberReducer ); export default store;
5) App.js 설정
store 정보를 가져와서 Provider store={store} 로 App 전체를 감싸준다.
src\App.js
import './App.css'; import store from './redux/store'; import { Provider } from 'react-redux'; import Subscribers from './components/Subscribers'; import Display from './components/Display'; function App() { return ( <Provider store={store}> <div className="App"> <Subscribers /> <Display /> </div> </Provider> ); } export default App;
6) 컴포넌트에서 리덕스 사용 방법
components 디렉토리 생성후 Subscribers.js 컴포넌트 생성한다.
공식 문서 : https://react-redux.js.org/api/connect
src\components\Subscribers.js
import React from 'react' import { connect } from 'react-redux'; import { addSubscriber, removeSubscriber } from "../redux/subscribers/actions"; const Subscribers = ({ count, addSubscriber, removeSubscriber }) => { return ( <div className='items'> <h2>Subscribers 컴포넌트 구독자 수:{count}</h2> <button onClick={() => addSubscriber()}>구독 하기!</button> <button onClick={() => removeSubscriber()}>구독 취소하기!</button> </div> ) } //공식 문서 : 설정 //https://react-redux.js.org/api/connect const mapStateToProps = (state) => { //console.log(state, 'state'); return { count: state.count } } //함수 방식 const mapDispatchToProps = (dispatch) => { return { addSubscriber: () => dispatch(addSubscriber()), removeSubscriber: () => dispatch(removeSubscriber()) } } //객체방식 // const mapDispatchToProps = { // addSubscriber, // removeSubscriber // } //Subscribers 컴포넌트를 export 하기 전에 mapStateToProps , 와 mapDispatchToProps 연결 설정을한다. export default connect(mapStateToProps, mapDispatchToProps)(Subscribers)
src\components\Display.js
import React from 'react' import { connect } from 'react-redux'; const Display = (props) => { return ( <div> <p>Display 컴포넌트 구독자 수 : {props.count}</p> </div> ) } const mapStateToProps = (state) => { return { count: state.count } } export default connect(mapStateToProps)(Display);
3. 조회수 리덕스 만들기
1)src\redux\views\types.js
export const ADD_VIEW = "ADD_VIEW";
2)src\redux\views\actions.js
import { ADD_SUBSCRIBER, REMOVE_SUBSCRIBER } from './types'; export const addSubscriber = () => { console.log("1.action - addSubscriber:"); return { type: ADD_SUBSCRIBER } } export const removeSubscriber = () => { console.log("1.action - removeSubscriber:"); return { type: REMOVE_SUBSCRIBER } }
3)src\redux\views\reducer.js
import { ADD_SUBSCRIBER, REMOVE_SUBSCRIBER } from './types'; const initialState = { count: 370 } const subscriberReducer = (state = initialState, action) => { console.log("2. action.type :", action.type); switch (action.type) { case ADD_SUBSCRIBER: return { ...state, count: state.count + 1 } case REMOVE_SUBSCRIBER: return { ...state, count: state.count - 1 } default: return state; } } export default subscriberReducer;
4) App.js 에서 <Display /> 임포트
4. 컴포넌트의 리덕스 import from 값을 줄이기.
1)src\redux\index.js 파일 생성
export { addSubscriber, removeSubscriber } from "./subscribers/actions"; export { addView } from "./views/actions";
2)컴포넌트에서 다음과 같이 값을 줄여 줄수 있다.
src\components\Subscribers.js
//import { addSubscriber, removeSubscriber } from "../redux/subscribers/actions"; import { addSubscriber, removeSubscriber } from "../redux";
5. reudcer 들을 하나로 묶으기 combineReducers
1) 다음과 같이 reducer 들만 가져와서 하나로 묶어 줄수 있다.
src\redux\rootReducer.js
import { combineReducers } from 'redux'; import subscriberReducer from './subscribers/reducer'; import viewReducer from './views/reducer'; const rootReducer = combineReducers({ subscribers: subscriberReducer, views: viewReducer }); export default rootReducer;
2)store.js 설정
src\redux\store.js
import { legacy_createStore as createStore, applyMiddleware } from 'redux'; //import subscriberReducer from './subscribers/reducer'; import rootReducer from './rootReducer'; //const store = createStore(subscriberReducer ); const store = createStore(rootReducer); export default store;
3)컴포넌트에서 다음과 같이 사용한다
src\components\Subscribers.js
~ ~ //공식 문서 : 설정 //https://react-redux.js.org/api/connect //rootReducer.js 에 설정한 combine 설정값을 가져온다. // const rootReducer = combineReducers({ // subscribers: subscriberReducer, // views: viewReducer // }); const mapStateToProps = ({ subscribers }) => { //console.log(state, 'state'); return { count: subscribers.count } } ~
6. redux-logger 및 redux-devtools-extension 설치
1) redux-logger 설치
$ yarn add redux-logger
2) 크롬 웹스토어에서 redux-devtool 설치
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko
3) redux-devtools-extension 설치
$ yarn add redux-devtools-extension
redux-devtools 사용법 및 설정 (구글 검색 redux extension devtools)
https://github.com/zalmoxisus/redux-devtools-extension
4) store 설정
src\redux\store.js
import { legacy_createStore as createStore, applyMiddleware } from 'redux'; //import subscriberReducer from './subscribers/reducer'; //rootReducer combine 적용 import rootReducer from './rootReducer'; import logger from 'redux-logger'; import { composeWithDevTools } from 'redux-devtools-extension'; //1.기본 //const store = createStore(rootReducer); //2.logger 사용시 //const store = createStore(rootReducer, applyMiddleware(logger)); //3.logger && composeWithDevTools 사용시 const middleware = [logger] const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(...middleware))); export default store;
7. 리덕스에서 비동기 작업을 처리 위해 redux-thunk 설치
1) 설치
$ yarn add redux-thunk
2) store 에서 redux-thunk 임포트 후 middleware 에 추가만 해주면 된다.
src\redux\store.js
import { legacy_createStore as createStore, applyMiddleware } from 'redux'; //import subscriberReducer from './subscribers/reducer'; //rootReducer combine 적용 import rootReducer from './rootReducer'; import logger from 'redux-logger'; import thunk from 'redux-thunk'; import { composeWithDevTools } from 'redux-devtools-extension'; //1.기본 //const store = createStore(rootReducer); //2.logger 사용시 //const store = createStore(rootReducer, applyMiddleware(logger)); //3.logger && composeWithDevTools 사용시 const middleware = [logger, thunk] const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(...middleware))); export default store;
3)액션을 동기식 처리를 위한 코딩 이미지와같이 Comments.js 컴포넌트 파일과 redux comments 파일을 생성한다.
thunk 가 있음므로서 action 에서 dispatch 가 가능해 진다.
jsonplaceholder 더미데이터
https://jsonplaceholder.typicode.com/comments
4) src\reux\comments\types.js
export const FETCH_COMMENTS = "FETCH_COMMENTS"; export const FETCH_COMMENTS_REQUEST = "FETCH_COMMENTS_REQUEST"; export const FETCH_COMMENTS_SUCCESS = "FETCH_COMMENTS_SUCCESS"; export const FETCH_COMMENTS_FAILURE = "FETCH_COMMENTS_FAILURE";
5) src\reux\comments\actions.js
import { FETCH_COMMENTS, FETCH_COMMENTS_REQUEST, FETCH_COMMENTS_SUCCESS, FETCH_COMMENTS_FAILURE } from "./types" //thunk 사용 설정으로 dispatch 함수 설정이 가능함 export const fetchComments = () => { return (dispatch) => { //동기식으로 순차적으론 진행 가능. dispatch(fetchCommentRequest()); fetch("https://jsonplaceholder.typicode.com/comments") .then(res => res.json()) .then(comments => dispatch(fetchCommentSuccess(comments)) ) .catch(error => dispatch(fetchCommentFailuer(error))); } } export const fetchCommentRequest = () => { return { type: FETCH_COMMENTS_REQUEST } } export const fetchCommentSuccess = (comments) => { return { type: FETCH_COMMENTS_SUCCESS, payload: comments } } export const fetchCommentFailuer = (error) => { return { type: FETCH_COMMENTS_FAILURE, payload: error } }
6) src\reux\comments\reducer.js
import { FETCH_COMMENTS, FETCH_COMMENTS_REQUEST, FETCH_COMMENTS_SUCCESS, FETCH_COMMENTS_FAILURE } from "./types" const initialState = { items: [], loading: false, err: null } const commentsReducer = (state = initialState, action) => { switch (action.type) { case FETCH_COMMENTS_REQUEST: return { ...state, location: true } case FETCH_COMMENTS_SUCCESS: return { ...state, items: action.payload, location: false } case FETCH_COMMENTS_FAILURE: return { ...state, err: action.payload, location: false } default: return state; } } export default commentsReducer;
7)src\redux\index.js
export { addSubscriber, removeSubscriber } from "./subscribers/actions"; export { addView } from "./views/actions"; export { fetchComments } from "./comments/actions";
8)src\redux\rootReducer.js
import { combineReducers } from 'redux'; import subscriberReducer from './subscribers/reducer'; import viewReducer from './views/reducer'; import commentsReducer from './comments/reducer'; const rootReducer = combineReducers({ subscribers: subscriberReducer, views: viewReducer, comments: commentsReducer }); export default rootReducer;
9) src\components\Comments.js
import React, { useEffect } from 'react' import { connect } from 'react-redux'; import { fetchComments } from '../redux'; const Comments = ({ fetchComments, loading, comments }) => { useEffect(() => { fetchComments(); }, []) const commentsItems = loading ? (<div>is loading...</div>) : ( comments.map(comment => ( <div key={comment.id}> <h3>{comment.name}</h3> <p>{comment.email}</p> <p>{comment.body}</p> </div> )) ); return ( <div className='comments'> {commentsItems} </div> ) } const mapStateToProps = ({ comments }) => { return { comments: comments.items, loading: comments.loading } } const mapDispatchToProps = { fetchComments } export default connect(mapStateToProps, mapDispatchToProps)(Comments);
10)App.css
.App { text-align: center; } .items{ border-bottom:1px solid #333; margin-bottom: 1rem; padding-bottom: 1rem; } .comments{ display: grid; grid-template-columns: repeat(4,1fr); grid-gap: 1rem; } .comments > div{ border: 1px solid #333; }
11)App.js
Comments 임포트
import './App.css'; import store from './redux/store'; import { Provider } from 'react-redux'; import Subscribers from './components/Subscribers'; import Display from './components/Display'; import Views from './components/Views'; import Comments from './components/Comments'; function App() { return ( <Provider store={store}> <div className="App"> <Comments /> <Subscribers /> <Views /> <Display /> </div> </Provider> ); } export default App;
실행
3. 옛날 리덕스를 최신 리덕스 Toolkit으로 바꿔보자!
https://redux-toolkit.js.org/introduction/getting-started
1. 리덕스 Toolkit 다음 4가지를 한방에 처리해 준다.
①combinereducer
② thunk
③applyMiddleware
④composeWithDevTools
소스 : https://github.com/braverokmc79/react-redux-toolkit-setting
◆ 기존 코드 => 리덕스 Toolkit 적용 코드
1 ) reducer 에서 적용 방법
기존 코드
redux/comments/reducer.js
import { FETCH_COMMENTS, FETCH_COMMENTS_REQUEST, FETCH_COMMENTS_SUCCESS, FETCH_COMMENTS_FAILURE } from "./types" const initialState = { items: [], loading: false, err: null } const commentsReducer = (state = initialState, action) => { switch (action.type) { case FETCH_COMMENTS_REQUEST: return { ...state, location: true } case FETCH_COMMENTS_SUCCESS: return { ...state, items: action.payload, location: false } case FETCH_COMMENTS_FAILURE: return { ...state, err: action.payload, location: false } default: return state; } } export default commentsReducer;
types 을 만들어 줄 필요가 없다.
전개 연산 복사를 할필요가 없다.
default 처리를 할 필요가 없다.
name: "commentsReducer", name 임의 고유 아이디값 입력한다.
===> 리덕스 Toolkit 처리로 다음과 같이 변경한다.
import { createSlice } from "@reduxjs/toolkit"; const initialState = { items: [], loading: false, err: null } const commentsSlice = createSlice({ name: "commentsReducer", initialState, reducers: { fetchCommentsRequest(state, action) { state.location = true }, fetchCommentsSuccess(state, action) { state.items = action.payload; state.location = false; }, fetchCommentsFailure(state, action) { state.err = action.payload; state.location = false; } } }); export const commentsActions = commentsSlice.actions; export default commentsSlice.reducer;
2 ) actions 에서 적용 방법
기존 코드
src/redux/comments/actions.js
import { FETCH_COMMENTS, FETCH_COMMENTS_REQUEST, FETCH_COMMENTS_SUCCESS, FETCH_COMMENTS_FAILURE } from "./types" //thunk 사용 설정으로 dispatch 함수 설정이 가능함 export const fetchComments = () => { return (dispatch) => { //동기식으로 순차적으론 진행 가능. dispatch(fetchCommentRequest()); fetch("https://jsonplaceholder.typicode.com/comments") .then(res => res.json()) .then(comments => dispatch(fetchCommentSuccess(comments)) ) .catch(error => dispatch(fetchCommentFailuer(error))); } } export const fetchCommentRequest = () => { return { type: FETCH_COMMENTS_REQUEST } } export const fetchCommentSuccess = (comments) => { return { type: FETCH_COMMENTS_SUCCESS, payload: comments } } export const fetchCommentFailuer = (error) => { return { type: FETCH_COMMENTS_FAILURE, payload: error } }
types 이 필요없고 redux/comments/reducer.js 에서 export 한
commentsActions 을 import 후 함수값만 호출하면 된다.
더이상 object 로 type 과 payload 만들어서 전달할 필요없이 함수에 매개변수로 직접 넣으면 된다.
===> 리덕스 Toolkit 처리로 다음과 같이 변경한다.
import { commentsActions } from './reducer'; export const fetchComments = () => { return (dispatch) => { dispatch(commentsActions.fetchCommentsRequest()); fetch("https://jsonplaceholder.typicode.com/comments") .then(res => res.json()) .then(comments => dispatch(commentsActions.fetchCommentsSuccess(comments)) ) .catch(error => ( dispatch(commentsActions.fetchCommentsFailure(error)) )); } }
3 ) store 에서 적용 방법
①combinereducer
② thunk
③applyMiddleware
④composeWithDevTools
===> toolkit 으로 한방에 처리
기존 store.js 와 combineReducers 처리인 rootReducer.js 코드를 하나의 파일로 간단하게 변경처리 가능하다.
redux/store.js
import { legacy_createStore as createStore, applyMiddleware } from 'redux'; import rootReducer from './rootReducer'; import logger from 'redux-logger'; import thunk from 'redux-thunk'; import { composeWithDevTools } from 'redux-devtools-extension'; const middleware = [logger, thunk] const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(...middleware))); export default store;
redux/rootReducer.js
import { combineReducers } from 'redux'; import subscriberReducer from './subscribers/reducer'; import viewReducer from './views/reducer'; import commentsReducer from './comments/reducer'; const rootReducer = combineReducers({ subscribers: subscriberReducer, views: viewReducer, comments: commentsReducer }); export default rootReducer;
===>리덕스 Toolkit 처리로 다음과 같이 변경한다.
redux/store.js
import { configureStore } from "@reduxjs/toolkit"; import subscriberReducer from './subscribers/reducer'; import viewReducer from './views/reducer'; import commentsReducer from './comments/reducer'; const store = configureStore({ reducer: { subscribers: subscriberReducer, views: viewReducer, comments: commentsReducer } }) export default store;
4) 컴포넌트에서는 기존과 동일하게 사용하면된다.
댓글 ( 4)
댓글 남기기