react에서
Redux 사용 예제입니다.
예제는 유투버, code Scalper님의 강의를 들으면서 공부한 내용입니다.
Redux의 기본 원리
화면 결과
폴더 구조
폴더 안 파일 구조
react Redux 예제 구현 순서
컴포넌트 파일 구성
- CRA로 프로젝트 생성, components 폴더 구성
- components 폴더 내에 Subscribers.js, Display.js, Views.js 세 개의 파일 생성
- App.js에서 위의 하위 components 삽입
리덕스
- redux 폴더 안에, 하위 폴더 생성(comments, subscribers, views)
- 각 하위 폴더 마다, types.js, actions.js, reducer.js 순서로 파일 생성
- types.js : action type을 변수로 작성 후 export
- actions.js: types.js에서 action type 변수를 import 해서 action 코드 작성 후 export
- reducer.js: types.js에서 action type 변수를 import, reducer 함수를 switch문으로 작성 후 export
- 코드 간결화를 위해 redux 폴더 안에 index.js 생성하여 모든 action을 다 import 한 후 export
스토어
- store.js에서 import {createStore} from 'redux';
- createStore에 인자로 생성된 리듀서 함수를 넣어줌 //const store = createStore(rootReducer);
- App.js에서 Provider를 react-redux에서 import 시킴
-
function App() { return ( <Provider store={store}> <div className="App"> ... </div> </Provider> ); }
- store import 후, <Provider store={store}> 태그로 전체를 감싸줌
- combineReducer를 통해서 각 reducer 함수를 하나로 합침
-
const rootReducer = combineReducers({ views: viewsReducer, //state.views로 state값 바뀜 subscribers: subscriberReducer, //state.subscribers comments: commmentsReducer })
- rootReducer.js에서 각 reducer를 import 하여 하나로 합친 후 export
- store.js에서 rootReducer import 후 createStore(rootReducer)로 변경
화면 출력
- 각 컴포넌트에서 import { connect } from 'react-redux';
- export default connect(mapStateToProps, mapDispatchToProps)(컴포넌트);
- mapStateToProps: state 상태 값을 props로 넘김
- mapDispatchToProps: state 상태 변경을 props로 disaptch
미들웨어 - Redux 확장 기능
- import logger from 'redux-logger';
- import {createStore, applyMiddleware} from 'redux';
- createStore(rootReducer, applyMiddleware(logger)); 로 미들웨어 logger 적용
- console.log를 통해 이벤트 history 확인
Redux Devtools 크롬 extension - Redux 확장 기능
- 크롬 웹스토어에서 redux devtools 추가 설치
- import { composeWithDevTools } from 'redux-devtools-extension';
- const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(...middleware)));
- 개발자 화면 Redux에서 디버깅 툴 확인
Redux thunk (액션을 순차적으로 일어나도록 실행) - Redux 확장 기능
- import thunk from 'redux-thunk';
- const middleware = [logger, thunk]
- json.placeholder의 더미 데이터를 이용해 비동기 호출
-
export const fetchComments = () => { //fetch 이벤트 작성 //redux-thunk를 썼기때문에 return으로 dispatch를 인자로 넘겨받아 액션 함수를 사용함 //redux-thunk를 이용하여 fetch 이벤트를 순차적으로 발생시킴 return(dispatch) => { dispatch(fetchCommentRequest()) fetch("https://jsonplaceholder.typicode.com/comments") .then( res => res.json()) .then(commments => dispatch(fetchCommentSuccess(commments)) ) .catch(err => dispatch(fetchCommentFailure(err))) } }
- 나머지 fetchCommentSuccess, fetchCommentFailure, fetchCommentRequest action 코드, reducer 함수 작성
라이브러리 설치 코드
cra 설치
npm install creat react-app react-redux
redux 설치
npm install redux react-redux
middleware 설치
npm install redux-logger
redux devtools extionsion 설치
npm install --save redux-devtools-extension
redux thunk 설치
npm install redux-thunk
파일 코드
Subscribers.js
import React from 'react';
import {connect} from 'react-redux';
//import { addSubscriber } from '../redux/subscribers/actions'; //아래 index.js로 코드 간결하게
import { addSubscriber } from '../redux/index'; // '/index 삭제해도됨
// const Subscribers = (props) => {
// return (
// <div className='items'>
// <h2>구독자수: {props.count}</h2>
// <button onClick={()=>props.addSubscriber()}>구독하기</button>
// </div>
// );
// };
const Subscribers = ({count, addSubscriber}) => { //destructuring
return (
<div className='items'>
<h2>구독자수: {count}</h2>
<button onClick={()=>addSubscriber()}>구독하기</button>
</div>
);
};
const mapStateToProps = (state) => { //props.count
console.log(state);
return {
count: state.subscribers.count //count가 props로 전달됨
}
}
// const mapDispatchToProps = (dispatch) => { //함수로 전달
// return {
// addSubscriber: () => dispatch(addSubscriber())//addSubscriber가 props로 전달됨
// }
// }
const mapDispatchToProps = {
addSubscriber //객체로 전달, addSubscriber: addSubscriber
}
export default connect(mapStateToProps, mapDispatchToProps)(Subscribers);
view.js
import React, { useState } from 'react';
import {connect} from 'react-redux';
import { addView, removeView } from '../redux/index';
const Views = ({count, addView, removeView}) => { //destructuring
const [number, setNumber] = useState(1)
return (
<div className='items'>
<h2>조회 수: {count}</h2>
{/* input value가 onChange될때마다 number값에 저장 */}
<input type='text' value={number} onChange={(e) => setNumber(e.target.value)} />
<button onClick={()=>addView(number)}>증가</button>
<button onClick={()=>removeView(number)}>감소</button>
</div>
);
};
const mapStateToProps = (state) => { //props.coun
return {
count: state.views.count //count가 props로 전달됨
}
}
const mapDispatchToProps = {
addView: (number) => addView(number), //메서드로 전달
removeView, //객체 전달
}
export default connect(mapStateToProps, mapDispatchToProps)(Views);
Display.js
import React from 'react';
import {connect} from 'react-redux';
const Display = (props) => {
return (
<div>
<p>구독자수{props.count}</p>
</div>
);
};
const mapStateToProps = (state) => { //props.count
return {
count: state.subscribers.count //count가 props로 전달됨
}
}
export default connect(mapStateToProps)(Display);
Commpents.js
import React, {useEffect} from 'react';
import { connect } from 'react-redux';
import {fetchComments} from '../redux';
const Comments = ({fetchComments, loading, comments}) => {
useEffect(() => {
fetchComments()
}, [])
const commentItems = 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'>
{commentItems}
</div>
);
};
const mapStateToProps = ({comments}) =>{
return {
comments: comments.items.slice(0,8) //8개만 출력
}
}
const mapDispatchToProps = {
fetchComments
}
export default connect(mapStateToProps, mapDispatchToProps)(Comments);
App.js
import './App.css';
import Subscribers from './components/Subscribers';
// npm install redux react-redux
import {Provider} from 'react-redux';
import store from './redux/store'
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;
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 #dedede;
}
Redux 폴더
subscribers 폴더 > types.js
export const ADD_SUBSCRIBER = 'ADD_SUBSCRIBER';
export const REMOVE_SUBSCRIBER = 'REMOVE_SUBSCRIBER';
subscribers 폴더 > actions.js
import {ADD_SUBSCRIBER,REMOVE_SUBSCRIBER} from './types';
export const addSubscriber = () => {
return {
type: ADD_SUBSCRIBER
}
}
export const removeSubscriber = () => {
return {
type: REMOVE_SUBSCRIBER
}
}
subscribers 폴더 > reducer.js
import {ADD_SUBSCRIBER,REMOVE_SUBSCRIBER} from './types';
const initialState = { //state 초깃값
count : 100
}
const subscriberReducer = (state = initialState, action) => {
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;
views 폴더 > types.js
export const ADD_VIEW = 'ADD_VIEW';
export const REMOVE_VIEW = 'REMOVE_VIEW';
views 폴더 > actions.js
import {ADD_VIEW, REMOVE_VIEW} from './types';
export const addView = (num) => {
return {
type: ADD_VIEW,
payload: Number(num)//num을 숫자로 변환
}
}
export const removeView = (num) => {
return {
type: REMOVE_VIEW,
payload: Number(num)
}
}
views 폴더 > reducer.js
import {ADD_VIEW, REMOVE_VIEW} from './types';
const initialState = { //state 초깃값
count : 0
}
const viewsReducer = (state = initialState, action) => {
switch(action.type) {
case ADD_VIEW:
return {
...state,
count: state.count + action.payload
}
case REMOVE_VIEW:
return {
...state,
count: state.count - action.payload
}
default: return state;
}
}
export default viewsReducer;
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';
comments 폴더 > actions.js
import {FETCH_COMMENTS, FETCH_COMMENTS_REQUEST, FETCH_COMMENTS_SUCCESS, FETCH_COMMENTS_FAILURE} from './types';
export const fetchComments = () => {
//fetch 이벤트 작성
//redux-thunk를 썼기때문에 return으로 dispatch를 인자로 넘겨받아 액션 함수를 사용함
//redux-thunk를 이용하여 fetch 이벤트를 순차적으로 발생시킴
return(dispatch) => {
dispatch(fetchCommentRequest())
fetch("https://jsonplaceholder.typicode.com/comments")
.then( res => res.json())
.then(commments => dispatch(fetchCommentSuccess(commments)) )
.catch(err => dispatch(fetchCommentFailure(err)))
}
}
const fetchCommentSuccess = (comments) => {
return {
type: FETCH_COMMENTS_SUCCESS,
payload: comments
}
}
const fetchCommentFailure = (err) => {
return {
type: FETCH_COMMENTS_FAILURE,
payload: err
}
}
const fetchCommentRequest = () => {
return {
type: FETCH_COMMENTS_REQUEST
}
}
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 commmentsReducer = (state=initialState, action) => {
switch(action.type){
case FETCH_COMMENTS_REQUEST:
return {
...state,
loading: true,
}
case FETCH_COMMENTS_SUCCESS:
return {
...state,
items: action.payload,
loading: false
}
case FETCH_COMMENTS_FAILURE:
return {
...state,
err: action.payload,
loading: true
}
default: return state;
}
}
export default commmentsReducer;
redux 폴더 > index.js
//코드의 간결화를 위해 하나로 합침
//가져오자마다 export 시킴
export { addSubscriber, removeSubscriber } from './subscribers/actions';
export { addView, removeView } from './views/actions';
export {fetchComments} from './comments/actions';
rootReducer.js
import { combineReducers } from "redux";
import subscriberReducer from "./subscribers/reducer";
import viewsReducer from "./views/reducer";
import commmentsReducer from "./comments/reducer";
const rootReducer = combineReducers({
views: viewsReducer, //state.views로 state값 바뀜
subscribers: subscriberReducer, //state.subscribers
comments: commmentsReducer
})
export default rootReducer;
store.js
import {createStore, applyMiddleware} from 'redux';
import rootReducer from './rootReducer';
import logger from 'redux-logger';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk'; //npm install redux-thunk
//middleware가 여러개가 될수 있기 때문에 변수를 만들고, logger를 담아줌
const middleware = [logger, thunk]
//const store = createStore(rootReducer, applyMiddleware(logger));
//spread operator로 [logger] 배열(껍데기)안의 내용물만 전달됨 -위와 똑같이 작동
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(...middleware)));
export default store;
App.js
import {createStore, applyMiddleware} from 'redux';
import rootReducer from './rootReducer';
import logger from 'redux-logger';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk'; //npm install redux-thunk
//middleware가 여러개가 될수 있기 때문에 변수를 만들고, logger를 담아줌
const middleware = [logger, thunk]
//const store = createStore(rootReducer, applyMiddleware(logger));
//spread operator로 [logger] 배열(껍데기)안의 내용물만 전달됨 -위와 똑같이 작동
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(...middleware)));
export default store;
반응형
'개발 > React' 카테고리의 다른 글
[react] react-datepicker 커스텀 (0) | 2021.09.02 |
---|---|
[react] pagination 구현 (ft. react-js-pagination) (0) | 2021.07.25 |
[react] Refusing to install package with name "react-redux" under a package 오류 해결 (0) | 2021.07.11 |
[react] POST url/[object%20Object] 404 (Not Found) 오류 해결 (2) | 2021.07.09 |
[react] Property 'x' does not exist on type '{}'.ts 오류 해결 (0) | 2021.07.07 |
댓글