Redux
2023. 1. 9. 21:13ㆍ프로그래밍/React
- 목차
Redux
자바스크립트 애플리케이션을 위한
상태 관리 라이브러리
Redux 데이터 Flow (단방향 데이터 흐름)
- React Componenet: 이벤트 발생
- Action: 리듀서 함수에게 액션을 발생시키라고 명령함 (dipatch)
- Reducer: 스토어 내부 상태 업데이트
- Redux Store: 새롭게 업데이트 된 상태를 이용해 다시 렌더링
- React Componenet: 이벤트 발생 ...
Action
간단한 자바스크립트 객체
type 속성: 수행하는 작업 유형 지정
payload 속성: redux 저장소에 데이터를 보낼 때 사용
{ type: 'LIKE_ARTICLE', articleId: 42 }
{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } }
{ type: 'ADD_TODO', text: 'Read the Redux docs.' }
Reducer
애플리케이션 상태의 변경 사항을 결정하고
업데이트된 상태를 반환하는 함수
전달된 인수를 이용해 로직을 처리하고 store 내부의 상태를 업데이트함
(previousState, action) => nextState
이전 State, Action 객체를 받은 후, next state를 반환함
Reducer 는 순수 함수이기 때문에 함수 내부에서 다음과 같은 작업을 하지 않아야 함
- arguments 를 변경하는 것
- API 호출, 라우팅 전환 같은 사이드 이펙트를 수행하지 않아야 함
- 비순수함수 호출 (Date.now() 또는 Math.random() 등)
Redux Store
애플리케이션의 전체 상태 트리를 보유함
내부 상태를 변경하는 유일한 방법은 해당 상태에 대한 Action을 전달하는 것
Redux Store는 클래스가 아니라 메서드를 가지고 있는 객체임
Dispatch
Store의 내장 함수 중 하나
리듀서 함수에게 Action을 발생시키라고 명령함
Action을 인자로 전달하여 사용함
dispatch(action)
리덕스 + 타입스크립트 프로젝트 생성
npx create-react-app [프로젝트 이름] --template typescript
리덕스 라이브러리 설치
npm install redux --save
Reducer 생성
Root Reducer
import { combineReducers } from 'redux';
import todos from './todos';
import counter from './counter';
const rootReducer = combineReducers({
todos,
counter,
});
export default rootReducer;
Sub Reducer
enum ActionType {
ADD_TODO = 'ADD_TODO',
DELETE_TODO = 'DELETE_TODO',
}
interface Action {
type: ActionType;
text: string;
}
const todos = (state = [], action: Action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.text];
case 'DELETE_TODO':
return;
default:
return state;
}
};
export default todos;
Store 생성
const store = createStore(rootReducer);
getState()
애플리케이션의 현재 상태 트리를 반환함
스토어의 리듀서가 반환한 마지막 값과 같음
subscribe()
change listener 를 추가함
작업이 전달될 때마다 호출되며 상태 트리의 일부가 잠재적으로 변경되었을 수 있음
그 후 getState() 를 호출하여 콜백 내부의 현재 상태 트리를 읽을 수 있음
const render = () =>
root.render(
<Provider store={store}>
<App
onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
/>
</Provider>
);
render();
store.subscribe(render);
Provider
모든 중첩 구성 요소에서 Redux Store 저장소를 사용할 수 있도록 해줌
대부분의 프로젝트에서는 최상위 컴포넌트에서 렌더링함
import { Provider } from 'react-redux';
const store = createStore(rootReducer);
...
const render = () =>
root.render(
<Provider store={store}>
<App
onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
/>
</Provider>
);
render();
store.subscribe(render);
useSelector
Provider 로 둘러싸인 컴포넌트에서 store 에 접근 가능
const counter = useSelector((state: RootState) => state.counter);
useDispatch
store 에 있는 dispatch 함수에 접근하는 hooks
const counter = useSelector((state: RootState) => state.counter);
const todos: string[] = useSelector((state: RootState) => state.todos);
const dispatch = useDispatch();
const addTodo = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
dispatch({ type: 'ADD_TODO', text: todoValue });
setTodoValue('');
};
Redux Middleware
액션을 전달하고(dispatch) 리듀서에 도달하는 순간 사이에
미리 지정된 작업을 실행할 수 있게 해줌
Redux Logging Middleware 생성
const loggerMiddleware = (store: any) => (next: any) => (action: any) => {
console.log('store', store);
console.log('action', action);
next(action);
};
export default loggerMiddleware;
applyMiddleware
미들웨어 함수를 applyMiddleware 함수에 인자로 전달함
인자로 여러 개의 미들웨어를 전달할 수 있음
전달된 모든 미들웨어는 순서대로 실행됨
const middleware = applyMiddleware(loggerMiddleware);
const store = createStore(rootReducer, middleware);