Redux 연습하기
상품 리스트 페이지와 장바구니 페이지로 단순화해서 만들기
// 구조를 살펴보면 다음과 같다
index.js – App.js - Nav.js
- ItemListContainer.js - Item.js
- ShoppingCart.js - CartItem.js
- OrderSummary.js
state: { itemList: […], cartItemList: […], ...}
App.js에 모든 state가 있는 상황에서 장바구니의 물건을 업데이트하려면?
→ 수많은 props drilling
이 발생함!
→ 전역 상태를 담고 있는 Store가 있다면 해결 가능
Cmarket Shopping App
Create React App으로 만든 리액트 앱에 Redux를 붙인 구조의 앱
// 구조
* 아이템 리스트 페이지(ItemListContainer)와 장바구니 페이지(ShoppingCart)의 2개의 페이지로 구성
* Store의 initial state에는 전체 아이템 목록(items), 장바구니 목록(cartItmes)로 구성
* 각 페이지 컴포넌트(ItemListContainer, ShoppingCart)와 components 폴더의
여러 컴포넌트에서 Store(state)에 접근이 필요(Redux의 hooks, useDispatch, useSelector 활용)
위의 구조를 기반으로 하여 해당 app을 구현한 코드
https://github.com/racyde/im-sprint-cmarket-redux/tree/master/src
Action
말 그대로 어떤 액션(행동, 이벤트)를 할 것인지 정의해 놓은 객체(Object)이다
//예시
{
type: ‘ADD_TO_CART’,
payload: request
}
type은 필수로 지정(일종의 별명)해 줘야 하며, 나머지는 선택적 부분
→ Action을 통해 변화하는 부분을 직관적으로 알기 쉽게 해 준다
Dispatch
Dispatch는 Action을 전달하는 메소드
→ dispatch()의 전달 인자로 Action 객체가 전달된다
그리고 Reducer를 호출하여 state의 값을 바꾸는 역할을 함
Store
말 그대로 state가 관리되는 오직 하나 뿐인 저장소의 역할
→ Redux 앱의 state가 저장되어 있는 공간
보통, 구성 컴포넌트들 전체에서 사용할 수 있는 Store를 만들기 위해 전체 애플리케이션을 <Provider>
컴포넌트로 감싸면서 시작해야 한다
→ index.js
와 같은 파일에서 코드 작성 필요한 듯?
const store = createStore(rootReducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Reducer
Reducer는 현재의 state
와 Action
을 이용해서 새로운 state
를 만들어 내는 순수 함수(pure function) 이다
// Reducer의 예시
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TO_CART:
return Object.assign({}, state, {
cartItems: [...state.cartItems, action.payload]
})
…(생략)
default:
return state;
}
}
// if 문 같은 다른 조건문으로 작성해도 무방함
Reducer의 Immutablility(불변성)
Redux의 state 업데이트는 immutable한 방식으로 변경해야 한다!
Redux의 장점인 변경된 state를 로그로 남기기 위해서 꼭 필요한 작업
immutable 하게 state를 변경하기 위해서는 immutable한 메서드를 사용하거나 하는 등의 방법으로, 원본 값을 직접적으로 건드리지 않게만 잘 코딩하면 된다
→ 위의 예시에서 Object.assign
메서드(immutable)을 사용한 것처럼 말이다
Redux Hooks
Action, Reducer, Dispatch, Store의 개념들을 연결(connect)시키기 위한 방법 중의 하나이다
(다른 하나는 connect parameter를 이용하는 방법)
→ Redux Hooks가 비교적 사용이 쉽고, 최근에 나온 방식
Redux 공식 문서를 통해 내용을 정리하였다
https://react-redux.js.org/api/hooks
Hooks | React Redux
API > Hooks: the `useSelector` and `useDispatch` hooks`
react-redux.js.org
useSelector()
useSelector()는 컴포넌트와 state를 연결하는 역할
→ 컴포넌트에서 useSelector 메소드를 통해 store의 state에 접근
어떤 컴포넌트에서 useSelector를 사용해야 할지 고민해보자
useSelector의 전달인자: 콜백 함수
→ 그 콜백 함수의 전달인자로는 state 값이 들어감
// 기본 구조
const result: any = useSelector(selector: Function, equalityFn?: Function)
작동 로직
함수 컴포넌트가 렌더링될 때, 선언한 selector 함수가 호출되고 그 결과가 useSelector() hook에서 반환된다(캐시된 결과는 구성 요소의 이전 렌더링과 동일한 함수 참조인 경우 selector를 다시 실행하지 않고 hook에 의해 반환될 수 있다)
Action이 Redux Store로 dispatch될 때, selector의 결과가 마지막 결과와 다른 경우에만, useSelector()가 강제로 다시 렌더링한다
기본적으로 비교는 참조 값과의 엄격한 비교(===) 이다
useSelector()를 사용하면 새로운 객체를 반환할 때 마다 기본적으로 다시 렌더링된다
Store에서 여러 value값들을 찾길 원한다면, 다음과 같이 할 수 있다
* 하나의 필드 값을 반환하는 호출마다 useSelector()를 호출하기
* `Reselect` 또는 이와 유사한 라이브러리를 사용하여, 하나의 객체 안의 여러 값(value)를 반환하지만
그 value 값 중 하나가 변경되었을 때만 새 객체를 반환하는 메모형 Selector 만들기
* `shallowEqual` 함수를 useSelector() 안의 인자(equalityFn 자리;2번째 인자 자리)로 사용
// 활용 예시
import { shallowEqual, useSelector } from 'react-redux'
// later
const selectedData = useSelector(selectorReturningObject, shallowEqual)
아래는 기본적인 useSelector() 사용 형태이다
// 기본 사용 형태
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => { // 특정 컴포넌트 내에서
// useSelector() 사용 구문
const counter = useSelector((state) => state.counter)
// useSelector의 전달인자로 콜백함수 (state) => state.counter
// 그 콜백 함수의 전달인자는 state
…
//리턴 부분
return <div>{counter}</div>
}
props를 전달받는 경우의 useSelector() 사용은?
import React from 'react'
import { useSelector } from 'react-redux'
export const TodoListItem = (props) => { // 컴포넌트에서 props를 전달 받아서
// useSelector() 사용 구문
const todo = useSelector((state) => state.todos[props.id])
…
//리턴 부분
return <div>{todo.text}</div>
}
useDispatch()
Action 객체를 Reducer로 전달해주는 메소드
어떤 컴포넌트에서 useDispatch()를 이용해 Action을 Reducer로 전달해줄 수 있을지 고민이 필요
// 기본 사용 형태
import React from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch() // useDispatch() 사용
…
// 리턴 부분
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: 'increment-counter' })}>
Increment counter
</button>
</div>
)
}
자식 컴포넌트에 dispatch를 사용하여 콜백 함수를 전달할 때, useCallback
을 사용하여 메모라이징할 수 있다.
자식 컴포넌트에서도 렌더링을 최적화하기 위해, React.memo()를 사용하여 콜백 함수 레퍼런스의 변경에 따른 불필요한 렌더링을 피할 수 있다
import React, { useCallback } from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
const incrementCounter = useCallback(
() => dispatch({ type: 'increment-counter' }),
[dispatch]
)
return (
<div>
<span>{value}</span>
<MyIncrementButton onIncrement={incrementCounter} />
</div>
)
}
export const MyIncrementButton = React.memo(({ onIncrement }) => (
<button onClick={onIncrement}>Increment counter</button>
))
useStore()
기본 형태
const store = useStore()
<Provider>
구성 요소에 전달된 동일한 Redux Store에 대한 참조를 반환
이 hook는 자주 사용하지 않아야 한다. 기본적으로 이 hook 보다는 useSelector() hook을 사용하는 것을 추천함
대신, Reducer의 교체와 같이 Store에 접근이 필요한 경우에 유용하게 사용할 수 있다
import React from 'react'
import { useStore } from 'react-redux'
export const CounterComponent = ({ value }) => {
const store = useStore()
// EXAMPLE ONLY! Do not do this in a real app.
// The component will not automatically update if the store state changes
return <div>{store.getState()}</div>
}
'SE Bootcamp 내용 정리' 카테고리의 다른 글
Linux - 사용 권한과 환경변수 (0) | 2021.11.08 |
---|---|
클라이언트 빌드와 배포 (0) | 2021.11.08 |
react - 상태 관리 1(기본 상태 관리, Redux) (0) | 2021.11.02 |
react - 컴포넌트 디자인 (0) | 2021.10.28 |
Web Server - 서버 만들기 연습 (0) | 2021.10.28 |