React + Redux
이번 시간에는 React Redux를 통해서 카운터와 로그인 기능을 리덕스로 간단하게 구성해보자.
actions
actions 폴더에 있는 파일들은 dispatch의 인자로 줄 action 객체를 생성
바닐라 JS + Redux 같이, action 객체엔 type이란 키가 필요한데 이 type은 reducer 내부에서 switch-case 구문이 조건이 된다.
먼저 counterActions.js를 보자.
src/actioins/counterActions.js
const increment = () => {
return {
type: "INCREMENT"
};
};
const decrement = () => {
return {
type: "DECREMENT"
};
};
export default {
increment,
decrement
};
userActions.js 는 사용자의 정보가 포함하는 객체를 리턴한다.
src/actions/userActions.js
const loginUser = user => {
return {
type: "LOG_IN",
user
};
};
const logoutUser = () => {
return {
type: "LOG_OUT"
};
};
export default {
loginUser,
logoutUser
};
src/actions/index.js
import counterActions from "./counterActions";
import userActions from "./userActions";
const allActions = {
counterActions,
userActions
}
export default allActions;
index.js에서 이들을 한번에 하나로 묶어준다.
추후 allActions.counterActions.increment() 로 호출하게 되면 { type: "INCREMENT" } 객체가 리턴된다.
이친구가 dispatch 함수의 인자로 reducer에 전달된다면 상태 엡데이트가 이루어진다.
reducers
reducers에선 상태를 생성하고 관리에 들어간다.
actions 폴더에서 생선된 객체들이 reducer 함수의 인자 action으로 들어가는 것이다.
src/reducers/counterReducer.js
const counter = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
};
export default counter;
userReducer.js 크게 다르지 않다. action.type 에 따라 데이터를 state에 넣어주는 역할을 한다.
대신 여기선 state의 불변성을 지켜주어야 한다!!
src/reducers/userReducers.js
const currentUser = (state = {}, action) => {
switch(action.type) {
case "LOG_IN":
return { ...state, user: action.user, login: true};
case "LOG_OUT":
return { ...state, user: "", login: false}
default:
return state;
}
};
export default currentUser;
index.js 에서 combineReducers() 를 사용해 두 Reducer를 하나로 정의해주었다.
src/reducers/index.js
import counter from "./counterReducer";
import currentUser from "./userReducer";
import { combineReducers } from "redux";
const rootReducer = combineReducers({ counter, currentUser});
export default rootReducer;
연결, 사용
이렇게 만들어진 Reducer를 하위 컴포넌트가 사용할 수 있도록 연결해줘야 한다.
createStore 로 저장소를 만들고 index.js 에 Provider를 사용해 연결한다.
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from "react-redux";
import { createStore } from "redux";
import rootReducer from './reducers';
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
rootReducer에 두 가지 리듀서가 합쳐져 있기 때문에 store에 두 가지 상태에 모두 접근이 가능하다.
이제 App.js 에서 state를 사용하려면
connect 와 mapStateToProps 와 같은 함수를 써야한다.
처음 배울떄는 어렵지만 나중에
React Hooks의 등장으로 react-redux에 userSelector , useDispatch 가 추가된다.
먼저 userSelector를 사용해보자
const counter = useSelector(state => state.counter);
const currentUser = useSelector(state => state.currentUser);
userSelector 로 만들어진 store에 접근하는 코드
리듀서에서 만들어진 state가 할당
userDispatch 는 더간단하다
const dispatch = useDispatch();
Reducer에 action을 전해주려면 dispatch를 이용한다는 사실을 기억하고 아래코드를 보자
dispatch(allActions.counterActions.increment());
위에서 언급한 것처럼 allActions.counterActions.increment() 는 객체를 반환한다.
( ) 를 넣어주었으니 함수가 즉시 실행되고
결국 { type: "INCREMENT" } 가 dispatch 함수의 인자로 들어가는 것.
dispatch({ type: "INCREMENT" });
이제 이친구는 createStore에서 인자로 준 rootReducer를 뒤져서 해당하는 타입을 찾을 것이다.
src/reducers/counterReducer.js
const counter = (state = 0, action) => {
switch(action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
export default counter;
counterReducer.js 를 보면 state를 하나 증가시키고 값을 저장한다.
때문에 리렌더링이 이루어지고 App.js의 counter 변수가 변하게 된다.
이렇게 리덕스로 상태관리가 가능하게 된다.
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import allActions from "./actions";
import './App.css';
function App() {
const counter = useSelector(state => state.counter);
const currentUser = useSelector(state => state.currentUser);
const dispatch = useDispatch();
const name = "KIM";
useEffect(() => {
dispatch(allActions.userActions.loginUser(name));
}, []);
return (
<div>
{currentUser.login ? (
<div>
<div>Hello, {currentUser.user}</div>
</div>
) : (
<div>
<div>Login</div>
<button
onClick={() => dispatch(allActions.userActions.loginUser(name))}
>
Login Kim
</button>
</div>
)}
<h1>{counter}</h1>
<button onClick={() => dispatch(allActions.counterActions.increment())}>
+ 1
</button>
<button onClick={() => dispatch(allActions.counterActions.decrement())}>
- 1
</button>
</div>
);
}
export default App;
이상없이 작동
thunk
만약 비동기 요청을 state에 담아야 한다면 어떻게 할까?
useEffect() 안에 비동기로 데이터를 받는 함수를 작성한 다음 바로 호출하는 방법도 있지만.
actions / reducers 폴더를 활용하면 더 좋을 것 같다.
redux로 비동기 로직을 처리해야 한다면, 우리는 미들웨어의 도움을 받아야 한다.
대표적으로 redux-thunk 가 있다.
이 친구를 사용하면 action 객체를 가로채서 비동기 작업을 기다린 후 다시 전송이 가능하다.
우선 설치를 해준다.
npm install redux-thunk --save && yarn add redux-thunk
import thunk from 'redux-thunk';
import { createStore,applyMiddleware} from "redux";
const store = createStore(rootReducer, applyMiddleware(thunk));
이제 비동기로 데이터를 받아와보자.
영화 정보를 제공하는 API를 사용하겠다.
src/actions/popularActions.js
import axios from 'axios';
const API_KEY = "7db8b1ffbba88aaa67068565d84fe99f"
const fetchPopularMovies = data => {
return {
type: "FETCH_POPULAR",
data
};
};
const fetchingPopularMovies = () => {
return dispatch => {
return axios
.get (
`https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}&language=en-US`
)
.then (response => {
dispatch(fetchPopularMovies(response.data.results));
})
.catch(error => {
throw error;
})
}
}
export default { fetchPopularMovies, fetchingPopularMovies};
fetchingPopularMovie() 에서 thunk가 사용되었다.
dispatch를 잡았다가 axios요청이 끝나면 fetchPopularMovies() 에 인자로 보내준다.
Reducer로 작성
src/reducers/popularReducer.js
const popularMovies = (state = [], action) => {
switch(action.type) {
case "FETCH_POPULAR":
return [...state, ...action.data];
default:
return state;
}
};
export default popularMovies;
이 친구들은 각각 폴더의 index.js 에 추가해주면 된다.
App.js 에선 이렇게 사용한다.
src/App.js
const popularMovies = useSelector(state => state.popularMovies);
useEffect(() => {
dispatch(allActions.popularActions.fetchingPopularMovies());
}, []);
action을 보면 thunk 부분을 연결해주는데
thnk 가 있는 fetchingPopularMovies에선 dispatch를 잡고 비동기를 수행한후 액션 생성자로 넘겨준다.
그럼 이번 시간은 마치고 2번째로 넘어가보자
'프론트 엔드 > React' 카테고리의 다른 글
React 상태관리를 위한 필수 라이브러리 - Redux2 (0) | 2021.08.28 |
---|---|
API.1 - React에서 사용하는 많이쓰는 Http 통신라이브러리 - use-http (0) | 2021.08.26 |
React-testing-library 를 사용해서 TDD개발 흐름과 test로 null값 체크 해보기 (0) | 2021.08.25 |