리액트도 제대로 못 쓰면서 무슨 리덕스냐...?!
그러기엔 이미 생활코딩 리덕스도 보았고...책 목차도 리덕스다...
한 번 봐두고 나면, 나중에 실습(?)할 때 도움이 될 것이다! ⛱
리덕스는 JS를 위한 상태 관리 프레임워크이다.
왜 리액트와 리덕스는 같이 사용할까?
이것저것 나열했지만, 사용하면서 알아보는게 더 빠르다.
(state, action) => nextState
액션은 type 속성값을 가지는 JavaScript객체이다.
각 액션은 고유한 type 속성값을 사용해야 한다.
function addTodo({ title, priority }) {
return { type: 'todo/Add', title, priority };
}
function removeTodo({ id }) {
return { type: 'todo/REMOVE', id };
}
function removeAllTodo() {
return { type: 'todo/REMOVE_ALL' };
}
store.dispatch(addTodo({ title: '영화 보기', priority: 'high' }));
store.dispatch(removeTodo({ id: 123 }));
store.dispatch(removeAllTodo());
이때, type속성값은 상수 변수로 만드는게 좋다.
export const ADD = 'todo/ADD';
export const REMOVE = 'todo/REMOVE';
export const REMOVE_ALL = 'todo/REMOVE_ALL';
export function addTodo({ title, priority }) {
return { type: ADD, title, priority };
}
// ...생략...
미들웨어는 리듀서가 액션을 처리하기 전에 실행되는 함수이다.
디버깅 목적으로 상태값 변경 시 로그를 출력하거나, 리듀서에서 발생한 예외를 서버로 전송한다.
// 미들웨어 기본 구조
const myMiddleware = store => next => action => next(action);
// 미들웨어 설정 방법
import { createStore, applyMiddleware } from 'redux';
const middleware1 = store => next => action => {
console.log('middleware1 start');
const result = next(action);
console.log('middleware1 end');
return result;
}
const middleware2 = store => next => aciton {
console.log('middleware2 start');
const result = next(action);
console.log('middleware2 end');
return result;
}
const myReducer = (state, action) => {
console.log('myReducer');
return state;
};
const store = createStore(myReducer, applyMiddleware(middleware1, middleware2));
store.dispatch({ type: 'someAction' });
// middleware1 start
// middleware2 start
// myReducer
// middleware1 end
// middleware2 end
액션이 발생할 때마다 이전 상탯값과 이후 상탯값을 로그로 출력한다.
const pringLog = store => next => action => {
console.log(`prev state = ${store.getState()}`);
const result = next(action);
console.log(`next state = ${store.getState()}`);
return result;
}
예외 발생 시 자동으로 서버에 에러 정보를 전송한다.
const reportCrash = store => next => action => {
try {
return next(action);
} catch (err) {
// 서버로 예외 전송
}
};
SET_NAME
이 발생할 때마다 로컬스토리지에 값을 저장한다.
const saveToLS = store => next => action => {
if (action.type === 'SET_NAME') {
localStorage.setItem('name', action.name);
}
return next(action);
};
리듀서는 액션이 발생했을 때 새로운 상탯값을 만드는 함수이다.
아래 코드를 보자.
리덕스는 스토어를 생성할 때 상탯값이 없는 상태로 리듀서를 호출한다.
따라서 매개변수 기본값에 상탯값을 준다.
또, 액션은 타입별로 case 구문을 만들어 준다.
function reducer(state = INITIAL_STATE, action) {
switch (action.type) {
// ...
case REMOVE_ALL:
return {
...state,
todos: [],
};
case REMOVE:
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.id),
};
default:
return state;
}
}
const INITIAL_STATE = { todos: [] };
위 코드보다 더 깊은 값을 수정하려면 가독성이 떨어진다.
불변 객체를 위한 패키지 중 immer
를 사용하면 아래와 같다.
import produce from 'immer';
function reducer(state = INITIAL_STATE, action) {
return produce(state, draft => {
switch (action.type) {
case ADD:
draft.todos.push(action.todo);
break;
case REMOVE_ALL:
draft.todos = [];
break;
case REMOVE:
draft.todos = draft.todos.filter(todo => todo.id !== action.id);
break;
default:
breakl
}
});
}
const reducer = createReduce(INITIAL_STATE, {
[ADD]: (state, action) => state.todos.push(action.todo),
[REMOVE_ALL]: state => (state.todos = []),
[REMOVE]: (state, action) =>
(state.todos = state.todos.filter(todo => todo.id !== action.id)),
});
스토어는 리덕스의 상탯값을 갖는 객체이다.
스토어는 액션이 발생하면 미들웨어를 실행하고, 리듀서를 실행하여 새로운 상탯값으로 변경한다.
그리고 사전에 등록된 모든 이벤트 처리 함수에게 액션처리가 끝났음을 알린다.
아래 코드를 보자.
subscribe메서드로 이벤트 처리 함수를 등록한다.
스토어에 등록된 함수는 액션이 처리될 때마다 호출된다.
그리고 dispatch메서드로 각 액션에 따른 동작이 실행된다.
const INITIAL_STATE = { value: 0 };
const reducer = createReducer(INITIAL_STATE, {
INCREMENT: state => (state.value += 1),
});
const store = createReducer(reducer);
let prevState;
store.subscribe(() => {
const state = store.getState();
if (state === prevState) {
cosole.log('상탯값 동일');
} else {
cosole.log('상탯값 변경');
}
prevState = state;
});
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'OTHER_ACTION' });
store.dispatch({ type: 'INCREMENT' });