react redux
업데이트:
카테고리: react
/Redux란?
상태관리 라이브러리
- component 안에서 데이터를 전달하려면 state를 바꾼다.
- State 는 바꿀 수 있고, 바꿀 때 마다 리랜더링이 된다.
Data Flow
이벤트 발생 → dispatch로 보냄 → Store에서 State를 변경 → UI 변경
- Action
- Js 객체
type
: 수행하는 작업의 유형을 지정payload
: 선택적으로 데이터를 보내는데 사용
- Reducer
- 애플리케이션의 상태의 변경 사항을 결정하고 업데이트의 상태를 반환
- action object, return 으로next state를 반환
- store의 내부의 상태를 업데이트 한다.
- Redux Store
- 앱의 전체 상태 트리를 보유한다.
- 변경이 발생하면 action으로 객체를 보낸ㄴ다.
- Method가 있는
객체
설치 : npm install redux --save
앱 생성 : npx create-react-app ./ --template typescript
→ 타입스크립트 기반으로 앱 생성
Reducer
store에 들어갈 state와 state를 변경할 함수를 정의하는 곳
불변성을 지켜야되는 이유 → state가 변경되면 redux가 인식하여 해당 state를 사용하는 컴포넌트에 리랜더링을 요구
reducers
폴더 생성
리듀서 코드
// 초기값은 0, action의 타입은 문자형으로 들어온다.
const couter = (state = 0, action: {type: string}) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
break;
}
}
export default couter;
// 액션이름앞에 파일 이름 넣기
// 액션 타입 설정
export const INCRESE = "COUNT/INCRESE";
// 타입과 사용할 reducers를 정의
// INCRESE를 부르면, count 변수가 불러나온다.
export const increaseCount = count => ({ type: INCRESE, count});
// 초기값 설정
const initalState = {
count: 0
};
// Reducer 설정
const counter = (state = initalState, action) => {
switch (action.type) {
case INCRESE:
return {
...state,
count: action.count + 1
};
default:
return state;
}
}
앱에 리덕스 적용하기
import { createStore } from 'redux';
import couter from './reducers'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
// reducer를 불러온다.
const store = createStore(couter);
const render = () => root.render(
<React.StrictMode>
// app 컴포넌트에 적용
<App
value={store.getState()}
onIncrement={() => store.dispatch({type: "INCREMENT"})}
onDecrement={() => store.dispatch({type: "DECREMENT"})}
/>
</React.StrictMode>
);
// 실시간으로 반영
render();
store.subscribe(render)
Combine Reuducer
여러가지 reducers를 파일로 만들어주고 index 파일에서 combine 시켜준다.
import { combineReducers } from "redux";
// import todos from './todos';
import counter from './counter'
// 컴바인 시킬 리듀스 함수
const rootReducer = combineReducers({
counter
})
export default rootReducer;
store.dispatch
를 통해서 해당하는 타입에 원하는 데이터를 보낸다.
Provider
모든 요소에 대해서 Store에 있는 State의 접근을 할 수 있도록 하는 것
해당 모듈은 react-redux에 존재
설치 : npm install react-redux --save
최상위 컴포넌트에 <Provider>
로 감싸준다.
<React.StrictMode>
// store 속성에 store를 넣어준다.
<Provider store={store}>
<App
value={store.getState()}
onIncrement={() => store.dispatch({type: "INCREMENT"})}
onDecrement={() => store.dispatch({type: "DECREMENT"})}
/>
</Provider>
</React.StrictMode>
접근
useSelector
: 스토어의 값을 가져온다.
// reducers/index.tsx 의 하단
// state의 타입을 정해주기 위해서 사용
export type RootState = ReturnType<typeof rootReducer>;
// todos의 데이터를 가져옴
const todos: string[] = useSelector((state: RootState) => state.todos);
useDispath
: 스토어에 데이터를 보낸다.
const dispatch = useDispatch();
const addTodo = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// dispatch의 원하는 함수와 데이터를 보내어 store에 저장한다.
dispatch({type: "ADD_TODO", text: todoValue});
setTodoValue("");
}
Redux 미들웨어
액션을 dispatch에 전달하고 리듀서에 도달하는 순간 지정된 작업을 실행한다.
로깅, 충돌보고, 비동기 API, 통신, 라우팅등을 위해 사용
import { createStore, applyMiddleware } from 'redux';
// 실행할 미들웨어 설정
const loggerMiddleware = (store: any) => (next: any) => (action: any) => {
console.log("store", store)
console.log("action", action)
next(action)
}
// 미들웨어 적용 및 store에 추가
const middleware = applyMiddleware(loggerMiddleware);
const store = createStore(rootReducer, middleware);
Redux thunk
설치: npm install redux-thunk --save
웹에서 비동기 작업을 실시할 때 많이 사용
useEffect(() => {
// 해당 부분에 에러가 발생한다.
dispatch(fetchPosts());
}, [dispatch]);
const fetchPosts = (): any => {
return async function fetchPostsThunk(dispatch: any, getState: any) {
const response = await axios.get("https://jsonplaceholder.typicode.com/posts");
dispatch({type: "FETCH_POSTS", payload: response.data})
}
}
⇒ Actions 는 객체여야 하는데 함수를 dispatch하고 있어서 그렇다! 그래서 thuck미들 웨어를 통해 함수를 dispatch 할 수 있도록 하는 것
thunk 적용후
// index.tsx에 적용
const middleware = applyMiddleware(thunk, loggerMiddleware);
react-toolkit
- toolkit에서 제공되는
configureStore
을 통해서 먼저 store를 제작한다.export const store = configureStore({ reducer: { }, })
-
스토어를 react에 제공
import { Provider } from 'react-redux'; import { store } from './app/store'; root.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> );
-
state Slice 제공
export interface CounterState { value: number; status: 'idle' | 'loading' | 'failed'; } const initialState: CounterState = { value: 0, status: 'idle', };
export const counterSlice = createSlice({ name: 'counter', initialState, // The `reducers` field lets us define reducers and generate associated actions reducers: { increment: (state) => { // Redux Toolkit allows us to write "mutating" logic in reducers. It // doesn't actually mutate the state because it uses the Immer library, // which detects changes to a "draft state" and produces a brand new // immutable state based off those changes state.value += 1; }, decrement: (state) => { state.value -= 1; }, // Use the PayloadAction type to declare the contents of `action.payload` incrementByAmount: (state, action: PayloadAction<number>) => { state.value += action.payload; }, }, // The `extraReducers` field lets the slice handle actions defined elsewhere, // including actions generated by createAsyncThunk or in other slices. extraReducers: (builder) => { builder .addCase(incrementAsync.pending, (state) => { state.status = 'loading'; }) .addCase(incrementAsync.fulfilled, (state, action) => { state.status = 'idle'; state.value += action.payload; }) .addCase(incrementAsync.rejected, (state) => { state.status = 'failed'; }); },
redux-persist
리덕스의 store는 페이지를 새로고침할 경우 state가 전부 날아간다. 그래서 이것에 대응방안으로 localStorage
/ session
에 저장한다. 이 작동을 대신하기 위해서 redux-persist
를 사용한다.
설치: npm install redux-persist
사용
// reducers/index.tsx
import { combineReducers } from "redux";
➊ import { persistReducer } from "redux-persist";
// 로컬 스토리지 저장
➋ import storage from "redux-persist/lib/storage";
// import storageSession from "redux-persist/lib/storage/session"
const persistConfig = {
key: "root",
// localStorage에 저장합니다.
storage,
// auth, board, studio 3개의 reducer 중에 auth reducer만 localstorage에 저장합니다.
whitelist: ["auth"]
// blacklist -> 그것만 제외합니다
};
// reducer
const rootReducer = combineReducers({
auth,
board,
studio
});
➍ export default persistReducer(persistConfig, rootReducer);
// src/index.js
import { createStore, applyMiddleware, compose } from "redux";
➊ import { persistStore } from "redux-persist";
➋ import { PersistGate } from "redux-persist/integration/react";
const store = createStore(rootReducer);
➌ const persistor = persistStore(store);
const Root = () => (
<Provider store={store}>
➍ <PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
);
redux-saga
redux는 무조건 동기적으로 dispatch가 이루어진다. dispatch를 여러번 할 경우도 로직은 2번 작성해야되니 불편하다.
이것을 대신하기 위한 미들웨어로 비동기적으로 disapatch를 사용할 수 있고, 내부 메소드를 활용하여, 사용자의 부주의로 인하여 동일한 api를 여러번 요청할 경우 마지막 req만 받아온다.
generator 문법
// 함수앞에 * 을 붙여준다.
// next로 다음 yield를 호출한다.
const gen = function* () {
console.log(1);
yield;
console.log(2);
yield;
console.log(3);
yield;
console.log(4)
}
const gener = gen()
// gener() - gener{<suspended>}
gener().next() -> 1
gener().next() -> 2
gener().next() -> 3
gener().next() -> 4
gener().next() -> undifined