2023. 10. 3. 21:49ㆍReact
1. Redux
1-1. Installation
npm install redux
npm install react-redux
1-2. Setting
1) <Provider />
- Redux Store를 이용 가능하게 해주는 컴포넌트인
<Provider />
로 index.js의<App/ >
을 감싼다. <Provider />
는 react-redux에서 가져온다.store
는 별도의 파일에서 만들어서 가져온다.
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
~~~ 생략 ~~~
root.render(
<Provider store={store}>
<App />
</Provider>
);
~~~ 생략 ~~~
2) reducer 생성
- store를 만들기 위해서는 reducer가 필요하므로, reducer를 먼저 만들어주자!
- src 폴더에
/redux/reducer/reducer.js
와/redux/store.js
를 새로 만든다.
/redux/reducer/reducer.js
let initialState = {
count: 0,
};
function reducer(state = initialState, action) {}
export default reducer;
3) store 생성
/src/redux/store.js
import { createStore } from "redux";
import reducer from "./reducer/reducer";
let store = createStore(reducer);
export default store;
4) reducer 여러개 만들기
기능별로 reducer를 따로 만들었다면, combineReducers를 이용해 하나로 묶어줘야 한다.
/reducer/index.js
import { combineReducers } from "redux";
import authenticateReducer from "./authenticateReducer";
import productReducer from "./productReducer";
export default combineReducers({
auth: authenticateReducer,
product: productReducer,
});
store.js
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducer";
// reducer를 묶어준 파일명이 index.js이기 때문에 /reducer까지만 작성해도 자동으로 index.js를 가져온다.
// 이름은 rootReducer가 아니어도 상관 없음!
let store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
이렇게 변경하고 나면 useSelector로 state를 불러올 때 combineReducers에서 지정해둔 키 이름으로 접근해야 한다.
const productList = useSelector((state) => state.product.productList);
1-3. 적용하기
1) state 변경하기
- action을 전달하는 hook인
useDispatch()
를 이용한다. - type과 payload로 이루어진 객체를 인자로 넣는다.
(payload는 필요한 정보를 담아서 보낼 수 있는 선택값!) - useDispatch를 사용하면 reducer에서 받을 수 있다.
~~~ 생략 ~~~
import { useDispatch } from "react-redux";
const ReduxPage = () => {
const dispatch = useDispatch();
const increase = () => {
dispatch({ type: "INCREASE" });
};
~~~ 생략 ~~~
- reducer가 새로운 값을 반환하면 반환된 값으로 state가 변경된다.
- 새로운 객체(주소값)을 return해야 한다는 것에 주의하자!
/redux/reducer/reducer.js
let initialState = {
count: 0,
};
function reducer(state = initialState, action) {
switch (action.type) {
case "INCREASE":
return { ...state, count: state.count + 1 };
default:
return { ...state };
}
}
export default reducer;
2) State 가져오기
- store의 state를 가져오는 hook인
useSelector()
를 이용한다.
import { useSelector } from "react-redux";
const count = useSelector((state) => state.count);
3) 컴포넌트와 state 연결하기 (mapStateToProps)
connect
를 이용하면 state의 값을 컴포넌트의 props와 병합해서 사용할 수 있다.mapStateToProps
는 컴포넌트가 스토어에서 필요한 데이터만 선택할 수 있게 해준다.- mapState라고도 부른다.
mapStateToProps
함수는 Redux Store가 업데이트 될 때마다 호출된다.- 컴포넌트가 필요로 하는 데이터를 객체로 반환해야 한다.
- state와 ownProps(optional)를 파라미터로 받는다.
- 파라미터로 state만 받도록 선언하면 Store 상태가 변경될 때마다 호출된다.
- 파라미터로 두 개의 파라미터를 받도록 선언하면, Store 상태가 변경되거나 래퍼 컴포넌트가 새 props를 받을 때마다 호출된다.
- 반환되는 값이 변경되면 컴포넌트가 리렌더된다.
function mapStateToProps(state) {
const { todos } = state
return { todoList: todos.allIds }
}
export default connect(mapStateToProps)(TodoList)
4) 컴포넌트와 dispatch 연결하기 (mapDispatchToProps)
connect
의 두 번째 매개변수로 mapDispatchToProps를 전달하면 dispatch 하는 함수를 생성하고, 그 함수들을 컴포넌트의 props로 전달할 수 있다.- store.dispatch를 호출하는 것이 가능하기는 하지만 React Redux에서는 컴포넌트가 Store와 직접적으로 소통하는 것을 피하고, connect를 통해 컴포넌트의 props로 연결하는 패턴을 주로 사용한다.
connect
의 두 번째 인자를 지정하지 않으면, 컴포넌트는 기본적으로 dispatch를 받는다.- mapDispatchToProps(
connect
의 두 번째 인자)를 사용하면, props에 dispatch가 제공되지 않는다. - mapDispatchToProps를 제공하면 컴포넌트가 어떤 액션을 dispatch하는지 지정할 수 있어 더 선언적이다.
두 가지 형태(함수 형태와 객체 형태)가 있다.
공식 문서에서는 객체 형태를 권장한다.
4-1) 함수 형태
- 더 많은 커스터마이징이 가능하다.
- 첫 번째 매개변수로 dispatch를 전달받는다.
const mapDispatchToProps = (dispatch) => {
return {
// dispatching plain actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' }),
}
}
- 매개변수를 두 개를 받는다면(두 번째는 optional) 두 번째 매개변수로는 props를 전달받게 되고, 컴포넌트가 새로운 props를 받을 때마다 재호출되어 액션을 dispatch할 함수들을 최신 props를 바탕으로 새로 바인딩해준다.
render() {
return <button onClick={() => this.props.toggleTodo()} />
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
toggleTodo: () => dispatch(toggleTodo(ownProps.todoId))
}
}
bindActionCreators
- 함수 래핑을 단순화하기 위한 함수이다.
- 각 함수를 dispatch로 자동으로 래핑해준다.
- Redux를 모르는 컴포넌트에 액션 크리에이터를 전달하고 싶을 때 쓰면 된다.
import { bindActionCreators } from 'redux'
const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })
// binding an action creator
// returns (...args) => dispatch(increment(...args))
const boundIncrement = bindActionCreators(increment, dispatch)
// binding an object full of action creators
const boundActionCreators = bindActionCreators(
{ increment, decrement, reset },
dispatch
)
// returns
// {
// increment: (...args) => dispatch(increment(...args)),
// decrement: (...args) => dispatch(decrement(...args)),
// reset: (...args) => dispatch(reset(...args)),
// }
- mapDispatchToProps와 함께 쓴 예시
import { bindActionCreators } from 'redux'
// ...
function mapDispatchToProps(dispatch) {
return bindActionCreators({ increment, decrement, reset }, dispatch)
}
// component receives props.increment, props.decrement, props.reset
connect(null, mapDispatchToProps)(Counter)
4-2) 객체 형태
- 더 선언적이고 쉽다.
const mapDispatchToProps = {
increment,
decrement,
reset,
}
2. Redux Middleware (redux-thunk)
Redux는 동기적인 업데이트만 가능하다.
비동기 작업이 필요한 API 요청 등은 Redux로 할 수 없다...
그래서 action이 reducer로 전달되기 전에 중간에 가로채서 비동기 작업을 처리하는 Middleware를 사용한다.
Redux의 Middleware에는 redux-saga와 redux-thunk가 있다.
redux-thunk를 사용해보자!!
2-1. Installation
npm install redux-thunk
2-2. Setting
- applyMiddleware와 thunk를 가져와서 createStore의 인자로 전달한다.
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import productReducer from "./reducer/productReducer";
let store = createStore(productReducer, applyMiddleware(thunk));
export default store;
2-3. 적용하기
react-thunk를 사용하지 않던 기존에는 (동기 방식) dispatch의 인자로 action과 payload를 바로 전달했다.
비동기 작업을 하려면 우선 해당 비동기 작업을 반환하는 미들웨어 함수를 만들어둔다.
이 비동기 작업 로직 안에서 기존 방식대로 dispatch를 호출한다.
function getProducts(searchQuery) {
return async (dispatch, getState) => {
let url = `https:...`;
let response = await fetch(url);
let data = await response.json();
dispatch({ type: "GET_PRODUCT_SUCCESS", payload: { data } });
};
}
export const productAction = { getProducts };
이 미들웨어 함수를 호출할 때는 dispatch를 사용하는 것은 동일하지만 action과 payload가 아닌, 이 미들웨어 함수를 인자로 넣어주면 된다.
import React from "react";
import { productAction } from ".../redux/action/productAction";
import { useDispatch, useSelector } from "react-redux";
const ProductAll = () => {
const dispatch = useDispatch();
const getProducts = () => {
dispatch(productAction.getProducts("searchQuery"));
};
getProducts();
return <div></div>;
};
export default ProductAll;
reducer는 기존과 동일하다.
3. Redux DevTools
state의 변화를 편리하게 확인할 수 있게 도와주는 도구이다.
3-1. Installation
npm install --save redux-devtools-extension
3-2. Setting
composeWithDevTools로 applyMiddleware를 감싸준다.
store.js
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducer";
import { composeWithDevTools } from "redux-devtools-extension";
let store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);
export default store;
3-3. 활용
개발자 도구의 Redux 탭에서 state 변화를 확인할 수 있다.
'React' 카테고리의 다른 글
[React] useRef, forwardRef, useImperativeHandle (1) | 2023.12.12 |
---|---|
[React] Router (version 6) (0) | 2023.10.02 |
[React] Class Component (0) | 2023.10.02 |