December 02, 2019
modules라는 디렉토리 안에 counter / todo 모듈 파일을 생성하기.
modules
└ counter.js
└ todo.js
modules/counter.js :
// counter 모듈
// 1. 액션 타입 정의하기
// 액션 이름 중복 방지를 위해서 앞에 'counter/'안에 속해있다는 접두사를 붙임
const SET_DIFF = 'counter/SET_DIFF'
const INCREASE = 'counter/INCREASE'
const DECREASE = 'counter/DECREASE'
// 2. 액션 함수 생성
export const setDIFF = diff => ({
type: SET_DIFF,
diff,
})
export const increase = () => ({
type: INCREASE,
})
export const decrease = () => ({
type: DECREASE,
})
// 3. 초기 상태를 정의
const initialState = {
number: 0,
diff: 1,
}
// 4. 리듀서 선언
// 리듀서는 export default 로 반드시 내보내줘야함.
// 모듈을 가져다 써야하기 때문임
export default function counter(state = initialState, action) {
switch (action.type) {
case SET_DIFF:
return {
...state,
diff: action.diff,
}
case INCREASE:
return {
...state,
number: state.number + state.diff,
}
case DECREASE:
return {
...state,
number: state.number - state.diff,
}
default:
return state
}
}
modules/todo.js :
// 1. 액션 타입 정의
const ADD_TODO = 'todos/ADD_TODO'
const TOGGLE_TODO = 'todos/TOGGLE_TODO'
// todo 리스트의 고유 idx 설정
let idx = 1
// 2. 액션 함수 생성
export const addTodo = text => ({
type: ADD_TODO,
todo: {
idx: idx++,
text,
},
})
export const toggleTodo = idx => ({
type: TOGGLE_TODO,
idx,
})
// 3. 초깃값 정의
// idx값과 text, 완료여부 객체를 저장
const initialState = []
// 4. 리듀서 내보내기
export default function todos(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return state.concat(action.todo)
case TOGGLE_TODO:
return state.map(todo =>
todo.idx === action.idx ? { ...todo, done: !todo.done } : todo
)
default:
return state
}
}
생성한 리덕스 모듈은 두개. 따라서 리듀서 또한 두개가 내보내짐. 한 프로젝트 내 리듀서가 여러개일 경우, combinReducers
라는 함수를 통해 하나의 리듀서로 통합한다. 이걸 루트 리듀서라고 함!
modules/index.js :
import { combineReducers } from 'redux'
import counter from './counter'
import todo from './todo'
const rootReducer = combineReducers({
counter,
todo,
})
export default rootReducer
index.js
에서 스토어를 생성해서 루트 리듀서를 넣어줌.
_index.js : _
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import * as serviceWorker from './serviceWorker'
// 스토어 생성 함수 불러오기
import { createStore } from 'redux'
// 루트 리듀서 불러오기
import rootReducer from './module'
ReactDOM.render(<App />, document.getElementById('root'))
const store = createStore(rootReducer)
serviceWorker.unregister()
react-redux
라이브러리를 설치하여 리액트 내에서 리덕스를 사용해보자.
index.js에서 Provider 라는 컴포넌트를 불러와서 App
컴포넌트를 감싸 props로 store
를 넣어줌.
index.js :
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import * as serviceWorker from './serviceWorker'
import { createStore } from 'redux'
import rootReducer from './module'
// Provider 컴포넌트 불러오기
import { Provider } from 'react-redux'
const store = createStore(rootReducer)
// App 컴포넌트를 Provider 컴포넌트로 감싼 후,
// store 를 props로 넘겨준다.
// 이렇게 하면 전역에서 store를 공유하며 어디서든 접근이 가능함.
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
serviceWorker.unregister()
props로 필요한 값을 받아만 와서 UI를 구현하는 컴포넌트. component 디렉토리 내, Counter.js 파일을 생성하여 컴포넌트를 만들어준다.
component/Counter.js :
import React from 'react'
// Counter 컴포넌트는 only 읽기 전용, UI 관련 컴포넌트
// 스토어에 직접 접근 X / props를 통해 값을 받기만 한다.
// 전역에서 store에 접근할 수 있음.
function Counter({ number, diff, onIncrease, onDecrease, onSetDiff }) {
const onChange = e => {
// e.target.value는 문자열이므로,
// int 값으로 변환해준다.
onSetDiff(parseInt(e.target.value, 10))
}
return (
<div>
<h1>{number}</h1>
<div>
<input type="number" value={diff} min="1" onChange={onChange} />
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
</div>
)
}
export default Counter
스토어의 상태 조회 or 액션을 디스패치할 수 있는 컴포넌트를 의미함. 다른 프리젠테이셔널 컴포넌트를 불러와서 사용함.
CounterContainer.js :
import React from 'react'
// useSelector = 스토어 상태를 조회하는 리액트 hook
// = getState()
import { useSelector, userDispatch } from 'react-redux'
import Counter from '../components/Counter'
import { increase, decrease, setDiff } from '../module/counter'
function CounterContainer() {
const { number, diff } = useSelector(state => ({
number: state.counter.number,
diff: state.counter.diff,
}))
// useDispatch, 스토어의 dispatch를 함수에서 사용하게 하는 hook임.
const dispatch = userDispatch()
// 각 액션들을 디스패치하는 함수 생성
// 액션 함수를 생성하는 함수를 파라미터값으로 넣어줌
const onIncrease = () => dispatch(increase())
const onDecrease = () => dispatch(decrease())
const onSetDiff = () => dispatch(setDiff(diff))
// 프리젠테이셔널 컴포넌트를 불러와 컴포넌트 안에 props값을 세팅
return (
<Counter
number={number}
diff={diff}
onIncrease={onIncrease}
onDecrease={onDecrease}
onSetDiff={onSetDiff}
/>
)
}
export default CounterContainer
컨테이너 컴포넌트는 App 컴포넌트에서 불러와 렌더링 하면 됨.
App.js :
import React from 'react'
import CounterContainer from './containers/CounterContainer'
function App() {
return (
<div>
<CounterContainer />
</div>
)
}
export default App