Redux는 2차 프로젝트를 하면서 사용자가 선택한 타입과 입력한 값의 상태를 관리하기 위해 처음 사용해봤다.
Reducer => New state => Store => New state, dispatch => mapStateToProps, mapDispatchToProps => Partial new State, Bound Action Creator => Props to Connected Component => dispatch(action) => Reducer
액션은 애플리케이션에서 저장소로 보내는 데이터 묶음이다. 이들이 저장소의 유일한 정보원이 된다.
store.dispatch()
코드를 통해 action을 저장소로 보낼 수 있다.
// 실수 방지 차 타입에 들어갈 string을 변수로 설정해 둠
const ADD_TODO = 'ADD_TODO'
// type은 필수 속성
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
액션을 만드는 함수
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
dispatch(addTodo(text))
dispatch(completeTodo(index))
리듀서는 이전 상태와 액션을 받아서 다음 상태를 반환하는 순수 함수이다.
(previousState, action) => newState
(액션은 무언가 일어난다는 사실을 기술하지만, 그 결과 앱의 상태가 어떻게 바뀌는지는 특정하지 않는다. 이것은 리듀서가 하는 일이다.)
모든 상태는 하나의 객체로 구성된다. 상태의 형태를 미리 정해보자.
상태 객체의 형태를 정한 후, 리듀서를 작성하자.
(previousState, action) => newState
형태의 리듀서 함수를 Array.prototype.reduce(reducer, ?initialValue)
로 넘길 예정이다.
!! 리듀서를 순수하게 유지하는 것이 매우 중요하다.
예기치 못한 일은 없어야 하기 때문에 아래의 3개는 리듀서에서 절대 금지이다.
만약 위의 경우의 값을 상태에 저장해야할 경우에는, 이미 계산해서 나온 '값'을 상태에 저장하면 된다.
Reducer 파일 안의 함수에서 switch, case를 통해 경우에 따라 다른 값을 return 한다.
//초기 상태 반환 방법
function todoApp(state = initialState, action) {
// 지금은 아무 액션도 다루지 않고, 주어진 상태를 그대로 반환합니다.
return state
}
SET_VISIBILITY_FILTER
case일 때,function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
state를 변경하지 않는 것이 중요하다. 따라서 Object.assign()
을 통해 복사본을 만들고, 반드시!! 첫 번째 인수에 빈 객체를 전달한다.
(혹은 object spread syntax를 사용하여 { ...state, ...newState } 방식으로 작성할 수도 있다.)
default 상황에서는 이전의 state를 그대로 반환한다. 알 수 없는 액션에 대해서는 이전의 state를 반환해야하기 때문에.
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
case COMPLETE_TODO:
return Object.assign({}, state, {
todos: todos(state.todos, action)
})
default:
return state
}
}
위 코드에서 todoApp은 관리할 상태의 조각만을 넘기고, todos는 그 조각을 어떻게 수정할지 알고 있다.
이 리듀서 조합이 Redux 앱을 만드는 기본 패턴이 된다.
리듀서는 각 상태의 부분을 관리하도록 세분화해서 만들 수 있다. 이 세분화된 리듀서는 메인 리듀서에서 하나의 객체로 조합할 수 있다. 하나의 객체로 조합할 때는 combineReducers(reducers, reducers, reducers...)라는 유틸리티를 사용한다.
import { combineReducers } from 'redux'
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
저장소(Store)는 액션과 리듀서를 함께 가져오는 객체이다.
저장소가 하는 일은 다음과 같다.
- 애플리케이션의 상태 저장
- getState()를 통해 상태에 접근하게 함
- dispatch(action)를 통해 상태를 수정할 수 있게 함
- subscribe(lsitener)를 통해 리스너 등록
combineReducers()를 통해 하나로 합친 여러 리듀서를 가져와서 createStore()에 넘긴다.
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp, window.STATE_FROM_SERVER)
createStore()의 두번째 인수로 초기 상태를 지정해줄 수 있으나, 필수는 아니다.
Redux-saga는 Redux meddleware library 중 하나로,
이를 이해하기 위해서 Middleware와 Generator가 무엇인가에 대해 간략히 소개하려 한다.
Generator 객체는 generator function에 의해 반환되며, iterable proptocol과 iterator protocol을 모두 준수한다.
(출처: MDN)
Generator는 작업의 일시 정지와 재시작이 가능하며 자신의 상태를 관리하는 함수이다.
Generator function 문법은 다음과 같다.
function* generator() {
yield 1;
yield 2;
yield 3;
}
const gen = generator(); // "Generator { }"
function 뒤에 별 * 을 붙여주고, 하나 이상의 yield 표현식을 포함한다.
위의 함수는 아래와 동일하다.
function* generator() {
yield* [1, 2, 3] // yield* 뒤에는 string, array 등 iterable한 객체만 올 수 있다.
}
yield는 함수실행이 일시적으로 정지하는 '위치'다.
Redux middleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
(출처: Redux 공식문서)
미들웨어는 action을 dispatch 할 때, reducer에 앞서 지정된 작업을 실행하는 도구이다.
예를 들어, method 실행 전후 state의 변화를 감지할 수 있고, 비동기 통신의 callback에서 dispatch method를 사용할 수 있다.
비동기처리를 어디에, 어떻게, 어디서 불러와야 하는가를 고민할 때 Redux-saga를 사용한다.
redux-saga is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures.
(출처: Redux-saga 공식문서)
Redux는 동기적으로 실행되기 때문에 비동기적인 동작을 실행할 수 없다.
반면, Redux-saga는 비동기적인 동작을 실행할 수 있다. 이에 더해, 비동기 작업을 할 때 기존 요청을 취소처리할 수 있다.
Creates an Effect description that instructs the middleware to call the function fn with args as arguments.
fn: Function
- A Generator function, or normal function which either returns a Promise as result, or any other value.args: Array<any>
- An array of values to be passed as arguments to fn
함수의 동기적인 호출을 할 때 사용한다.
예를 들어, 첫 번째 parameter로 전달한 함수에, 그 뒤에 있는 parameter를 전달하여 호출한다.
Creates an Effect description that instructs the middleware to schedule the dispatching of an action to the store. This dispatch may not be immediate since other tasks might lie ahead in the saga task queue or still be in progress.
You can, however, expect the store to be updated in the current stack frame (i.e. by the next line of code after yield put(action)) unless you have other Redux middlewares with asynchronous flows that delay the propagation of the action.
action: Object
- see Redux dispatch documentation for complete infoput()은 action을 store로 dispatch하는 역할을 한다. (즉, dispatch와 비슷하다.)
Spawns a saga on each action dispatched to the Store that matches pattern.
pattern: String | Array | Function
- for more information see docs for take(pattern)saga: Function
- a Generator functionargs: Array<any>
- arguments to be passed to the started task. takeEvery will add the incoming action to the argument list (i.e. the action will be the last argument provided to saga)takeEvery()는 특정 액션 타입에 대해 디스패치되는 모든 액션을 처리한다. 특정 액션을 모니터링하고, 그 액션이 실행되면특정 함수를 실행시킨다.
Forks a saga on each action dispatched to the Store that matches pattern. And automatically cancels any previous saga task started previously if it's still running.
Each time an action is dispatched to the store. And if this action matches pattern, takeLatest starts a new saga task in the background. If a saga task was started previously (on the last action dispatched before the actual action), and if this task is still running, the task will be cancelled.
pattern: String | Array | Function
- for more information see docs for take(pattern)saga: Function
- a Generator functionargs: Array<any>
- arguments to be passed to the started task. takeLatest will add the incoming action to the argument list (i.e. the action will be the last argument provided to saga)takeLatest()는 특정 액션 타입에 대해 dispatch된 가장 마지막 액션만을 처리하는 함수이다. 예를 들어, 특정 액션을 처리하고 있는 동안 동일한 타입의 새로운 액션이 dispatch되면, 기존에 하던 작업을 무시 처리하고 새로운 작업을 시작한다.
refers to a way of organizing the control flow using two separate Sagas
The watcher
: will watch for dispatched actions and fork a worker on every actionThe worker
: will handle the action and terminate