점점 어플리케이션에 요구되는 기능들이 늘어나게 되면서, 자바스크립트 코드가 많은 상태를 관리해야 하는 필요성이 늘어나고 있다. 그렇기에 Flux의 흐름을 따라 상태변화가 일어나는 시점에 제약을 두어 상태변화를 예측 가능하게 만들었다.
엄격한 단방향 데이터 흐름을 따른다 = Flux의 흐름을 따름
생명주기
사용자가 store.dispatch(action)
를 호출
앱 내의 어디서든 호출 가능
Redux store가 지정된 reducer 함수를 호출
같은 입력으로 몇번의 호출에도 항상 같은 출력값이 나와야 함
root reducer가 각 reducer의 출력값을 합쳐서 하나의 상태트리로 만듦
Redux store가 root reducer에 의해 반환된 상태트리를 저장
어플리케이션에서 store로 보내는 데이터 묶음으로, store의 유일한 정보원이다.
type
속성을 가져야 한다.type
은 일반적으로 문자열 상수(대문자)로 정의되며, 띄어쓰기에는 '_'를 사용한다.type
외에 액션 객체의 구조는 자유롭게 생성 가능하다.const ADD_TODO = 'ADD_TODO';
{
type : "ADD_TODO",
text: "Hello Redux"
}
Action 생성자의 조건
Action을 만드는 함수로, 단지 액션을 반환하는 역할을 한다.
리덕스를 사용할 때 필수적으로 사용해야 하는 것은 아니지만, 나중에 컴포넌트에서 액션을 발생시킬 때 더 쉽게 사용할 수 있도록 해준다.
// 기존 함수형 - 다른 component에서 사용해야 하기 때문에 export 해주기
export function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
// 화살표함수형 (더 간단히 사용 가능)
export const addTodo = (text) => ({
type: ADD_TODO,
text
})
어플리케이션의 상태가 어떻게 바뀌는지 정의해주는, 이전상태와 액션을 받아서 다음상태를 반환해주는 순수함수
작성법
초기 상태 정의
Redux는 처음에 reducer를 undefined
의 상태로 호출하기 때문에, 초기상태를 정의해주어야 한다.
const initialState = {
visibilityFilter: SHOW_ALL,
todos: [],
};
function todoApp(state, action) {
if(typeof state === "undefined") {
return initialState
}
return state;
}
// 위의 todoApp reducer를 더 간단하게 작성하면,
function todoApp(state = initialState, action) {
return state;
}
상태변경 로직 구성
조건
- state값을 직접 변경하지 않고, 복사본을 만들어서 사용해야 한다.
- default case
에 대해서는(알 수 없는 action에 대한) 이전의 state값을 반환한다.
visibiliyFilter
와 다른 액션들의 로직까지 구현해보자면,
function todoApp(state = initialState, action) {
switch(action.type) {
case SET_VISIVILITY_FILTER :
return Object.assign({}, state, {
visibilityFilter : action.filter
});
case ADD_TODO :
return Object.assign({}, state, {
todos : [
...state.todos, {
text: action.text,
completed: false
}]
});
case COMPLETED_TODO :
return Object.assign({}, state, {
todos : [
...state.todos.slice(0, action.index),
Object.assign({}, state.todos[action.index], {
completed: true,
}),
...state.todos.slice(action.index + 1)
]
});
default :
return state;
}
}
// Object.assign()대신 object spread 문법 사용하면
function todoApp(state = initialState, action) {
switch(action.type) {
case SET_VISIBILITY_FILTER :
return {
...state,
visibilityFilter : action.filter
}
...
}
}
// 초기상태는 직접 넣어주기
function todos(state = [], action) {
switch(action.type) {
case ADD_TODO :
return [
...state, {
text: action.text,
completed: false
}
];
case COMPLETE_TODO:
return [
...state.slice(0, action.index),
Object.assign({}, state[action.index], {
completed: true
}),
...state.slice(action.index + 1)
];
default:
return state;
}
}
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter;
default:
return state;
}
}
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
};
}
여기서 combineReducers()
라는 유틸리티를 통해 독립된 reducer들을 합쳐줄 수 있다.
아래의 코드는 위의 todoApp()
과 동일한 기능을 하는 코드이다.
import {combineReducers} from 'redux';
const todoApp = combineReducers({
todos,
visibilityFilter
});
export default todoApp;
'무엇이' 일어날지 정의한 Action
, 이 Action에 따라 상태를 '어떻게' 변경시킬지 정의한 Reducer
를 가져오는 객체인 Store
, 단 하나만 존재할 수 있다.
하는 일
getState()
를 통해 상태에 접근dispatch(action)
을 통한 상태의 변경subscribe(listener)
를 통한 리스너 등록생성법
여러개의 Reducer를 합쳐주는 combineReducers()
를 통해 합쳐진 것을 가져와서 createStore()
에 넘기기
import {createStore} from 'redux';
import todoApp from './reducers';
const store = createStore(todoApp);
rootReducer
로 리듀서들을 합쳐서 사용하기combineReducers
함수를 사용해서 합쳐주기import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';
const rootReducer = combineReducers({
counter,
todos
});
export default rootReducer;
createStore
를 통해 리덕스 스토어 생성Provider
의 props로 store
를 넣어서 App 컴포넌트를 감싸게 되면 렌더링하는 모든 컴포넌트가 리덕스 스토어에 접근할 수 있게 된다.서버에서 데이터를 주고받는 state값이 생기게 되면 좀 더 복잡해지지만,
기본적인 내용은 이렇다.
그동안 redux를 사용해보면서 큰그림으로만 정리되어 있는 느낌이 강했는데,
이번에 이렇게 정리를 하면서 좀 더 구체적인 그림이 그려진 것 같다.
능숙하게 다룰 수 있을때까지 연습하기!
📌 참고하였습니다 :)
수정이 필요한 부분이 있다면 댓글로 알려주세요!