일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 리엑트
- Firebase
- 리액트
- csr
- react-query
- 자바스크립트
- 배열
- NextJS
- useMutation
- 겹치는 선분의 길이
- 투두리스트
- 생명주기
- google firebase
- Next
- programmers
- JavaScript
- 넥스트
- next-pwa
- Redux
- Recoil
- 파이어베이스
- todolist
- lifecycle
- customModal component 만들기
- JS
- 리덕스
- SSR
- 달리기경주
- React
- debounce
- Today
- Total
끄적끄적
[상태관리라이브러리] Redux(리덕스) 본문
Redux 란?
애플리케이션 상태를 관리하기위한 오픈 소스 JavaScript 라이브러리이다.
따라서, 꼭 React가 아니어도 사용할 수 있다는 것이다.
React에서 컴포넌트별로 데이터를 주고 받으려면 어떻게 하는가??
바로 속성값을 이용해서 주고 받는다. 부모 ↔ 자식 사이에서 데이터 교환이 일어날 수 있다.
하지만 프로그램이 복잡해지고 깊이가 깊어지면 최상위 컴포넌트에서 최하위 컴포넌트까지 데이터를 주려면 .. 타고 타고 타고... 내려가야한다.
이럴때 리덕스를 사용하면 편하다.
리덕스는 상태를 관리하기 위한 상태저장 컴포넌트라고 생각하면 편하다. 어디서든 데이터를 가져오고 수정할 수 있게 하는 것이다.
①액션(Action)
액션은 type속성을 가진 Javascript 객체다.
store.dispatch({ type:'todo/ADD', title:'영화보기', priority:'high' }); //todo를 붙인 이유는 type 이름 중복을 피하기 위해 접두사를 붙인 것이다.
store.dispatch({ type:'todo/REMOVE', id:123 });
store.dispatch({ type:'todo/REMOVE_ALL' });
위와 같이 type은 필수로 속성값을 갖어야하며 그 뒤는 자유롭다.
type이 필수여야하는 이유는 type을 보고 어떤 기능을 수행할지 알기 때문이다(물론 이 type을 작성하는 본인이 그 type에 맞는 기능을 작성해야 한다)
또한 priority도 항상 존재하도록 하는 것이 바람직하다.
위와같이 일일히 작성하는 방법도 있지만, 생성자를 통해 간단하게 action을 생성하는 방법도 있다.
function addTodo({ title, priority }){
return {type:'todo/ADD', title, priority};
}
그리고 action type은 변수로 만들어 관리하는 것이 좋다. (리듀서에서도 사용해야하기 때문에 export한다)
export const ADD = 'todo/ADD';
export function addTodo({ title, priority }){
return {type:ADD, title, priority};
}
②미들웨어(Middleware)
미들웨어는 리듀서가 액션을 처리하기 전에 실행되는 함수다. (필수는 아니다)
디버깅 목적으로 상탯값 변경에 대한 로그를 출력하거나, 리듀서에서 발생환 예외를 서버로 전송하는 등의 목적으로 작성한다.
리덕스 사용시 특별히 미들웨어를 설정하지 않았다면 발생한 액션은 곧바로 리듀서로 보내진다.
미들웨어의 구조: const myMiddleware = store => next => actoin => next(action);
화살표 함수가 3개 중첩된 것이다. 이를 화살표함수를 사용하지 않으면 다음과 같다.
const myMiddleware = function(store){
return function(next){
return function(action){
return next(action);
};
};
};
코드에서 알수 있듯 미들웨어는 스토어와 액션 객체를 기반으로 필요한 작업을 수행한다. 그리고 최종적으로 리듀서 함수가 호출된다.
미들웨어를 설정하는 예제 코드이다.
import {createStore, applyMiddleware } from 'redux';
const middleware1 = store => next => action => {
console.log('미들웨어1 실행');
const result = next(action);
console.log('미들웨어1 종료');
return result;
}
const myReducer = (state, action) => {
console.log('myReducer');
return state;
}
const store = createStore(myReducer, applyMiddleware(middleware1));
store.dispatch({ type : 'someAction' });
③리듀서(Reducer)
리듀서는 액션이 발생했을 때 새로운 상탯값을 만드는 함수이다.
(state, action) => nextState
리듀서를 작성하는 방법1 (switch문)
functoin reducer(state = INITIAL_STATE, action){
switch(action.type){
//...
case REMOVE_ALL:
return{
...state,
todos: [] // state는 불변 객체로 관리해야 하므로 수정 할 때마다 새로운 객체를 만들어야 함
};
case REMOVE:
return{
...state,
todos: state.todos.filter(todo => todi.id !== action.id), //filter문은 배열에서 조건에 안맞는 건 걸러내는 역할
};
default: // action.type에 맞는 조건문이 없으면 default실행
return state;
}
}
const INITIAL_STATE = { todos: [] };
리듀서를 작성하는 방법2 (immer(이머))
immer란? 불변 객체를 관리하는 기능이다.
import produce from 'immer';
const person = {name:'ps', age:27'};
const newPerson = produce{person, draft => {
draft.age = 32;
}
이를 이용하면 리듀서에서 상태 수정을 더욱 간단하게 진행 할 수 있다.
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:
break;
}
});
}
리듀서를 작성하는 방법3 (createReducer함수)
switch문보다 간결하게 리듀서 함수를 작성할 수 있다.
const reducer = createReducer(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),
});
※리듀서 작성 시 주의해야할 것
-
데이터 참조
리덕스의 상탯값은 불변 객체이기 때문에 언제든지 객체의 레퍼런스가 변경될 수 있다.
따라서, 객체를 참조할 때는 객체의 레퍼런스가 아니라 고유한 ID값을 이용하는 것이 좋다. -
순수 함수
리듀서는 순수 함수로 작성되어야 한다. (순수 함수란? 동일한 입력값에는 동일한 출력값이 나오는 함수. 즉, 랜덤함수나 render함수 같은 건 순수함수가 아니다)
④스토어(Store)
스토어는 리덕스에서 상태값을 갖는 객체다.
스토어는 액션이 발생하면 미들웨어 함수를 실행하고, 리듀서를 실행하여 상탯값을 새로운 상탯값으로 변경한다.
그리고 사전에 등록된 이벤트 처리 함수에게 액션의 처리가 끝났음을 알린다.
다음은 store의 subscribe 메서드를 통해 상탯값 변경 여부를 검사하는 코드다.
const INITAL_STATE = { value : 0 };
const reducer = createReducer(INITIAL_STATE, {
INCREMENT : state => (state.value + 1),
});
const store = createStore(reducer);
let prevState;
store.subscirebe( () => { //subscribe 메서드를 이용해 이벤트 처리 함수를 등록한다.
const state = store.getState();
if(state === prevState){ //state는 불변객체이기때문에 이렇게 단순한 ===로 동등 비교를 진행할 수 있다.
console.log("상탯값이 변경 없음");
}else{
console.log("상탯값이 변함");
}
prevState = state;
});
store.dispatch({ type : 'INCREMENT' });
store.dispatch({ type : 'DECREMENT' }); //등록되지 않은 액션이 발생하면 '상탯값 같음'로그를 출력한다.
참고서적 : 실전 리액트 프로그래밍(프로그래밍 인사이트, 2019) 이재승 지음
'React' 카테고리의 다른 글
[React+Redux]간단하게 만들어보는 TodoList(2) (2) | 2020.11.27 |
---|---|
[React+Redux]간단하게 만들어보는 Todolist(1) (0) | 2020.11.27 |
[React] 자주 쓰이는 React 생명 주기(함수형편, useEffect) (0) | 2020.11.25 |
[React] 자주 쓰이는 React 생명 주기(Class편) (0) | 2020.11.25 |
[React + Firebase] OAuth 소셜 로그인 (0) | 2020.11.25 |