스터디/KAKAOCLOUDSCHOOL
[React] - 개발자 지망생 스터디 - 29일차(1)
shineIT
2022. 12. 12. 10:11
8. redux 실습
1) 프로젝트 생성 및 필요한 라이브러리 설치
$yarn create react-app redux
$cd redux
$yarn add redux react-redux
2) UI 작업
# Counter 와 ToDo 배치
> Components 디렉토리 생성
🗂redux -> 📁 src -> (CREATE) 📁 components
> components 디렉토리에 카운터를 위한 Counter.jsx 파일 생성
🗂redux -> 📁 src -> 📁 components -> (CREATE) 📄 Counter.jsx
import React from "react"; const Counter = ({number, onIncrease, onDecrease}) => { return (<div> <h1>{number}</h1> <div> <button onClick={onIncrease}>+1</button> <button onClick={onDecrease}>-1</button> </div> </div>); } export default Counter;
> components 디렉토리에 ToDos.jsx 파일을 생성하고 작성
🗂redux -> 📁 src -> 📁 components -> (CREATE) 📄 ToDos.jsx
import React from "react"; //하나의 항목을 출력하기 위한 컴포넌트 const ToDoItem = ({todo, onToggle, onRemove}) => { return (<div> <input type = "checkbox"/> <span>텍스트</span> <button>삭제</button> </div>); } //여러 개의 ToDoItem을 출력할 컴포넌트 const ToDos = ({input, todos, onChangeInput, onInsert, onToggle, onRemove,}) => { const onSubmit = (e) => { e.preventDefault(); }; return ( <div> <form onSubmit={onSubmit}> <input/> <button type="submit">등록</button> </form> <div> <ToDoItem /> <ToDoItem /> <ToDoItem /> <ToDoItem /> <ToDoItem /> </div> </div> ); } export default ToDos;
> App.js 파일 수정
🗂redux -> 📁 src -> 📄 App.js
import React from "react"; import Counter from "./components/Counter"; import ToDos from "./components/ToDos"; function App() { return ( <div> <Counter number={0}/> <hr/> <ToDos/> </div> ); } export default App;
3) redux 관련 모듈 생성
> modules 디렉토리 생성
🗂redux -> 📁 src -> (CREATE) 📁 modules
> counter.js 파일을 생성
🗂redux -> 📁 src -> 📁 modules -> (CREATE) 📄 counter.js
//액션 타입 정의 const INCREASE = 'counter/INCREASE'; const DECREASE = 'counter/DECREASE'; //액션 생성 함수 정의 export const increase = () => ({type:INCREASE}); export const decrease = () => ({type:DECREASE}); //초기 상태를 정의 const initialState = { number:0 } //리듀서 함수 정의 const counter = (state=initialState, action) => { switch( action.type){ case INCREASE: return {number:state.number + 1}; case DECREASE: return {number:state.number - 1}; default: return state; } }; export default counter;
> todos.js 파일을 생성
🗂redux -> 📁 src -> 📁 modules -> (CREATE) 📄 todos.js
//액션 타입 정의 const CHANGE_INPUT = 'todos/CHANGE_INPUT'; const INSERT = 'todos/INSERT'; const TOGGLE = 'todos/TOGGLE'; const REMOVE = 'todos/REMOVE'; //액션 생성 함수 정의 export const changeInput = input => ({ type:CHANGE_INPUT, input }); let id = 3; //샘플 데이터를 2개 삽입할 것이기 때문에 3을 지정 export const insert = text => ({ type:INSERT, todo:{ id:id++, text, done:false } }); export const toggle = id => ({ type:TOGGLE, id }); export const remove = id => ({ type:REMOVE, id }); //초기 상태 정의 const initialState = { input: '', todos: [{ id: 1, text: 'Node', done: true, }, { id: 2, text: 'React', done: false, }] }; // 리듀스 함수 정의 const todos = (state=initialState, action) => { switch( action.type){ case CHANGE_INPUT: return {...state, input:action.input}; case INSERT: return {...state, todos:state.todos.concat(action.todo)}; case TOGGLE: return {...state, todos:state.todos.map( todo => todo.id === action.id ? {...todo,done:!todo.done}:todo)}; case REMOVE: return {...state, todos:state.todos.filter(todo => todo.id !== action.id)}; default: return state; } } export default todos;
4) redux 관련 모듈 작성
> modules 디렉토리 안에 index.js 파일을 생성하고 작성
🗂redux -> 📁 src -> 📁 modules -> (CREATE) 📄 index.js
import { combineReducers } from 'redux'; import counter from './counter'; import todos from './todos'; const rootReducer = combineReducers({ counter, todos, }); export default rootReducer;
> index.js 수정
🗂redux -> 📁 src -> 📄 index.js
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; //추가 부분 (line 8 ~ 11) import { legacy_createStore as createStore } from 'redux'; import {Provider} from 'react-redux'; import rootReducer from './modules'; const store = createStore(rootReducer); const root = ReactDOM.createRoot(document.getElementById('root')); root.render( //변경 부분 (line 16 ~ 18) <Provider store={store}> <App /> </Provider> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
4) 컨테이너 컴포넌트 작업
> 컨테이너 컴포넌트 : redux를 사용하는 컴포넌트
> src 디렉토리에 containers 디렉토리 생성
🗂redux -> 📁 src -> (CREATE) 📁 containers
#컨테이너 컴포넌트 생성
> react-redux 의 connect 함수 사용
connet(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)
☑︎ mapStateToProps는 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해서 생성하는 함수이고
☑︎ mapDispatchToProps는 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수
> containers 디렉토리에 CounterContainer.jsx 파일을 생성하고 작성
🗂redux -> 📁 src -> 📁 containers -> (CREATE) 📄 CounterContainer.jsx
import React from 'react'; import {connect} from 'react-redux'; import Counter from '../components/Counter'; import {increase, decrease} from '../modules/counter'; const CounterContainer = ({number, increase, decrease}) => { return ( <Counter number={number} onIncrease={increase} onDecrease={decrease} /> ); }; const mapStateToProps = state => ({ number: state.counter.number }); const mapDispatchToProps = dispatch => ({ increase: () => {dispatch(increase());}, decrease: () => {dispatch(decrease());} }) export default connect(mapStateToProps, mapDispatchToProps)(CounterContainer);
> App.js 수정
🗂redux -> 📁 src -> 📄 App.js
import React from "react"; import CounterContainer from "./containers/CounterContainer"; import ToDos from "./components/ToDos"; function App() { return ( <div> <CounterContainer/> <hr/> <ToDos/> </div> ); } export default App;
> ToDos를 위한 ToDosContainer.jsx 파일을 생성하고 작성
🗂redux -> 📁 src -> 📁 containers -> (CREATE) 📄 ToDosContainer.jsx
import React from "react"; import {connect} from 'react-redux'; import {changeInput, insert, toggle, remove} from '../modules/todos'; import ToDos from "../components/ToDos"; const ToDosContainer = ({input, todos, changeInput, insert,remove}) => { return (<ToDos input={input} todos={todos} onChangeInput={changeInput} onInsert={insert} onToggle={toggle} onRemove={remove}/>); } export default connect(({todos}) => ({ input:todos.input, todos:todos.todos }), {changeInput,insert,toggle,remove} )(ToDosContainer);
> App.js 수정 (ToDos -> ToDosContainer)
🗂redux -> 📁 src -> 📄 App.js
import React from "react"; import CounterContainer from "./containers/CounterContainer"; import ToDosContainer from './containers/ToDosContainer'; function App() { return ( <div> <CounterContainer/> <hr/> <ToDosContainer/> </div> ); } export default App;
> components 디렉토리의 ToDos.jsx 수정
🗂redux -> 📁 src -> 📁 components ->📄 ToDos.jsx
import React from "react"; //하나의 항목을 출력하기 위한 컴포넌트 const ToDoItem = ({todo, onToggle, onRemove}) => { return (<div> <input type = "checkbox" onClick={()=>onToggle(todo.id)} checked={todo.done} readOnly={true}/> <span style={{textDecoration:todo.done? 'line-through':'none'}}> {todo.text} </span> <button onClick={()=>onRemove(todo.id)}>삭제</button> </div>); } //여러 개의 ToDoItem을 출력할 컴포넌트 const ToDos = ({input, todos, onChangeInput, onInsert, onToggle, onRemove,}) => { const onSubmit = (e) => { e.preventDefault(); onInsert(input); onChangeInput(''); }; const onChange = (e) => onChangeInput(e.target.value); return ( <div> <form onSubmit={onSubmit}> <input value={input} onChange={onChange}/> <button type="submit">등록</button> </form> <div> {todos.map(todo => ( <ToDoItem todo={todo} key={todo.id} onToggle={onToggle} onRemove={onRemove}/> ))} </div> </div> ); } export default ToDos;
9. MiddleWare
1) 개요
> 액션이 디스패치 된 후 리듀서에서 해당 액션을 받아서 작업을 수행하기 전이나 후에 추가적인 작업을 할 수 있도록 해주는 것
> 작업을 수행하기 전에는 유효성 검사 같은 작업을 많이 하고 작업이 수행된 후에는 로그 기록을 많이 함.
> 유사한 용도로 사용되는 것들을 부르는 명칭으로는 Filter, Interceptor, AOP 등이 있음
2) 직접 생성한 미들웨어 적용
> src 디렉토리에 middlewares 디렉토리 생성
🗂redux -> 📁 src -> (CREATE) 📁 middlewares
> middlewares 디렉토리에 미들웨어로 사용할 mymiddleware.js 파일 만들고 작성
🗂redux -> 📁 src -> 📁 middlewares -> (CREATE) 📄 mymiddleware.js
const mymiddleware = store => next => action => { //동작을 로깅 console.log(action); //다음 미들웨어나 리듀서에게 전달 const result = next(action) //작업이 끝난 후 확인 console.log(store.getStore()); return result; } export default mymiddleware;
> src 디렉토리의 index.js 파일 수정
🗂redux -> 📁 src -> 📄 index.js
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { legacy_createStore as createStore, applyMiddleware } from 'redux'; import {Provider} from 'react-redux'; import rootReducer from './modules'; import mymiddleware from './middlewares/mymiddleware'; const store = createStore(rootReducer, applyMiddleware(mymiddleware)); const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App /> </Provider> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
3) 외부 라이브러리를 이용한 로그 기록
> 설치 : $yarn add redux-logger
> src 디렉토리의 index.js 파일 수정 (+logger)
🗂redux -> 📁 src -> 📄 index.js
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { legacy_createStore as createStore, applyMiddleware } from 'redux'; import {Provider} from 'react-redux'; import rootReducer from './modules'; import mymiddleware from './middlewares/mymiddleware'; import logger from 'redux-logger'; const store = createStore(rootReducer, applyMiddleware(mymiddleware, logger)); const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App /> </Provider> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
가상의 API Server 만들기
# 설치
> $npm install --location=global json-server
> $yarn global add json-server
# 프로젝트 루트 디렉토리에 가상의 데이터를 작성 - data.json
{ "posts":[{ "id":1, "content":"React", "done":true },{ "id":2, "content":"Node", "done":false }] }
#서버 실행 :
> $npx json-server ./data.json --port 4000
# 브라우저에서 확인
> localhost:4000/posts
> localhost:4000/posts/1
# react 프로젝트에서 외부 서버의 데이터를 proxy를 통해서 가져오기
> package.json 파일을 열어서 "proxy":"http://localhost:4000" 추가 - 서버의 도메인을 작성해야 함
🗂 redux -> 📄 package.json
{[ ... ],[ ... ] }, "proxy":"http://localhost:4000" }
> 요청을 수정
http://localhost:4000/posts → /posts
httpl://localhost:4000/posts/1 → /posts/1