위코드 마지막 프로젝트를 하면서 가장 잘 익히고 싶었던 기능이 리덕스였는데 프로젝트 기한에 쫓기다 보니 제대로 공부를 못했었다. 마지막 달에 기업협업을 하면서 감사하게도 소스코드를 오픈해 주셔서 실무에서 사용되는 코드들을 공부할 수 있는 기회가 있었는데 확실히 책이나 유투브를 통해서 접했던 것 보다 훨씬 더 심도있게 리덕스를 사용하고 있었다. 네트워크 요청같은 비동기 작업을 위한 redux-saga 같은 미들웨어의 사용도 필수적인 것 같다.
우리가 맡은 작업은 인터렉션이 필요한 부분은 아니어서 처음엔 굳이 리덕스를 사용해야 하나? 싶은 생각도 들었지만 리덕스를 사용할 줄 모르면 리액트를 쓸 줄 모르는 것과 마찬가지라는 팀장님의 말씀을 듣고 리덕스/리덕스 미들웨어를 공부하고 있다. 하지만 계속해서 복습이 필요할 것 같다.
store에 정보를 전달하기 위한 데이터 묶음을 말한다.
const ADD_TODO = "ADD_TODO";
const AddTodo = (text)=>{
type : ADD_TODO,
text
}
각각의 상태변화를 어떻게 시킬것인지에 대한 관리정보가 들어있으며 store와 정보를 주고받는다.
(전달된 action을 보고 어떻게 state를 update할지)
초기상태(initialState)와 switch문(optional)이 들어있다.
많은 리듀서들을 합쳐서 하나의 reducer로 관리할 수 있게 한다.
import {combineReducers} from 'redux',
(...)
const todoApp = combineReducers({visibilityfilter, todos});
앱의 전체 상태를 저장할 수 있는 저장소이다. 생성방법은 아래와 같다.
최상위의 index.js 파일 내에서 스토어를 만들어 줄 수 있다
import {createStore} from 'redux';
import reducers from './modules';
//리듀서들을 store 안에 넣어서 서로 연결해준다.
const store = createStore(reducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
(...)
//이후 render함수 아래에 전달해준다.
<Provider store={store}>
스토의 내의 상태를 가져와준다.
console.log(store.getState());
스토어에 있는 리듀서들에게 action을 전달해준다.
각각의 리듀서들은 자신에게 맞는 action 이 들어온다면 store의 상태를 교체하게 된다.
store.dispatch(addTodo('new Todo'))
처음에는 provider가 왜 필요한지 이해하지 못했다.
provider 또한 하나의 컴포넌트로 이해하면 되는데 react로 작성된 컴포넌트들을 Provider컴포넌트 안에 넣으면 하위의 컴포넌트들이 provider를 통해서 redux store에 접근할 수 있게 되는 것이다.
import {Provider} from 'react-redux';
(...)
<Provider store={store}>
<App/> //이제 App컴포넌트는 store에 접근할 수 있게 된다.
</Provider>
connect함수는 provider 컴포넌트 하위에 존재하는 컴포넌트들이 store에 접근할 수 있게 해준다.
import {connect}from 'react-redux';
(...)
// 데이터와 함수들이 props 로 붙은 컴포넌트 생성
const CounterListContainer = connect(
mapStateToProps,
mapDispatchToProps
)(CounterList);
connect 함수의 첫번째 인수로 들어가는 함수 혹은 객체
mapStatateProps의 첫번째 인자로는 State, 두번째 인자(optional)로는 우리가 원하는 객체를 담아서 넘겨준다.
//인자가 하나인 경우
const mapStateToProps = (state) => ({
counters: state.get('counters')
});
//인자가 두개인 경우
const mapStateToProps=(state,ownProps)=>({
todo : state.todos[ownProps.id]
})
//이 함수 자체가 필요없는 경우에는 connect 함수의 첫번째 인수로 null 을 입력해준다.
export default connect(null, mapToDispatch)(App);
connect 함수의 두번째 인자가 된다.
store에 접근한 컴포넌트가 store의 상태를 바꾸기 위해 dispatch를 사용할 수 있게 만들어준다.
mapDispatchToProps의 첫번째 인자로는 Redux의 dispatch 가 오게 되고 이를 통해서 store의 상태를 변경할 수 있게 된다.
const mapDispatchToProps = (dispatch) => ({
onIncrement: (index) => dispatch(actions.increment(index)),
onDecrement: (index) => dispatch(actions.decrement(index)),
onSetColor: (index) => dispatch(actions.setColor({index: index, color: getRandomColor()}))
});
아래와 같이 불러올 객체를 Map 함수로 맵핑해준다.
var Map = Immutable.Map;
(...)
// 초기 상태를 정의합니다
var data = Map({
a: 1,
b: 2,
c: Map({
d: 3,
e: 4,
f: 5
})
})
data.toJS()
data.get('a') //일반 키 값
data.get('c','e') //깊숙한 키 값
const newData = data.set('a',4) //a의 값을 4로 변경한다.
const newData = data.set(['c','e'],4 // 깊숙한 키 값 변경
const state = {
number: 1,
dontChangeMe: 2
};
const nextState = produce(state, draft => {
draft.number += 1;
});
console.log(nextState);
// { number: 2, dontChangeMe: 2 }
일일히 액션 생성자를 만들지 않고 자동화 시킬 수 있도록 해준다.
export const increment = (index) =>({
type : type.INCREMENT,
index})
//CreateAction을 이용해서 하기와 같이 간단하게 변환할 수 있다.
export const increment = createAction(type.INCREMENT)
파라미터로 전달한 값은 payload 에 일괄 저장된다.
예를 들어
export const setColor = createAction(types.SET_COLOR);
setColor({index : 5, color : 'fff'})
//위 setColor 의 파라미터들은 하기와 같이 전달된다.
{types:SET_COLOR,
payload : {index : 5,
color : 'fff'}}
다만 이 경우는 코드를 보았을때 어떤 파라미터를 필요로 하는지 알아보기 어려울 수 있으므로 주석을 잘 작성해 주는 것이 좋다.
const reducer = handleActions({
[INCREMENT]:(state,action)=>({
counter : state.counter + action.payload}),
counter : 0})
// 초기 상태를 정의합니다
const initialState = Map({
counters: List([
Map({
color: 'black',
number: 0
})
])
});
export default handleActions({
[CREATE]: (state, action) => {
const counters = state.get('counters');
return state.set('counters', counters.push(
Map({
color: action.payload,
number: 0
})
))
},