가장 많이 사용하는 패턴은 프레젠테이셔널 컴포넌트와 컨테이너 컴포넌트를 분리 하는 것이다.
프레젠테이셔널컴포넌트 : 컨테이너 컴포넌트에서 props를 받아서 화면에 UI를 보여주기만 한다.
컨테이너 컴포넌트 : 리덕스와 연동되어 있는 컴포넌트로, 리덕스로부터 상태를 받아오기도 하고 리덕스 스토어에 액션을 디스패치 하기도 한다.
$ npm i redux react-redux
src/modules
액션타입, 액션 생성 함수, 리듀서 함수를 기능별로 파일 하나에 몰아서 작성하는 방법을 선호한다. 위와 같은 구조 방식을 Ducks 패턴이라고 한다.
modules/counter.js
액션 타입
리듀서함수 -> counter
리듀서 함수에는 현재 상태를 참조하여 새로운 객체를 생성해서 반환한다.
스토어를 만들때(createStore()
) 하나의 리듀서만 필요하므로 여러개의 리듀서들을 합쳐야 한다.
modules/index.js
src/index.js
src/containers
컴포넌트를 리덕스와 연동하려면 react-redux에서 제공하는 connect 함수를 사용해야 한다.
connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)
mapStateToProps : 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수
mapDispatchToProps : 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수
src/containers/CounterContainer.js
컴포넌트에서 액션을 디스패치하기 위해 각 액션 생성 함수를 호출하고 dispatch로 감싸는 작업이 번거로울수 있다. 특히 액션 생성 함수의 개수가 많아질경우 더더욱 그럴 것이다. 이럴때 사용하는 방법 들을 살펴보자.
1. bindActionCreators()
bindActionCreators
: 리덕스에서 제공하는 유틸 함수이다.
2. 객체화
$ npm i redux-actions
export const potatoName = createAction(액션타입명, () => {})
두번째 파라미터는 생략 해도되지만 이 함수를 넣어 줌으로써 코드를 보았을때 이액션 생성 함수의 파라미터로 어떤 값이 필요한지 쉽게 파악 할수 있다.
const reducer = handleActions({
[액션타입명]: (state,action)=>({...state, input:action.payload})
//...
},초기state값)
export default reducer
payload 이름을 새로 설정해 주면
action.payload
가 정확히 어떤 값을 의미하는지 쉽게 파악 할수 있다.const reducer = handleActions({ [액션타입명]: (state,{payload:input})=>({...state, input}) //... },초기state값) export default reducer
useSelector HOOK을 사용하면 connect함수를 사용하지 않고도 리덕스의 상태를 조회 할수 있다.
const 결과 = useSelector(상태_선택_함수)
상태_선택_함수
: mapStateToProps 와 형태가 똑같다const dispatch = useDispatch();
dispatch({type:'SAMPLE_ACTION});
useDispatch
를 사용할 때는useCallback
과 함게 사용하는 것을 습관화 하자( 최적화 할수 있다)
const store = useStore();
store.dispatch({type:'SAMPLE_ACTION'});
store.getState();
컴포넌트 내부에서 리덕스 스토어 객체를 직접 사용할수 있다. 하지만 정말 어쩌다가 스토어에 직접 접근해야하는 상황에만 사용해야 한다. 이를 사용하는 때는 흔치 않을 것이다.
공식 문서에서 그대로 복사하여 사용할수 있다.
이 훅을 사용하면, 여러 개의 액션을 사용해야 하는 경우 코드를 훨씬 깔끔하게 정리하여 작성할 수 있다.
src/lib/useActions
import { bindActionCreators } from 'redux';
import { useDispatch } from 'react-redux';
import { useMemo } from 'react';
const useActions = (actions, deps) => {
const dispatch = useDispatch();
return useMemo(
() => {
if (Array.isArray(actions)) {
return actions.map((a) => bindActionCreators(a, dispatch));
}
return bindActionCreators(actions, dispatch);
},
deps ? [dispatch, ...deps] : [dispatch],
);
};
export default useActions;
useActions
는 두 가지 파라미터가 필요하다.첫번째 : 액션 생성 함수로 이루어진 배열
두번째: deps배열( 이안에 들어있는 원소가 바뀌면 액션을 디스패치하는 함수를 새로 만든다.)
connect함수를 사용하면 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링될 때 해당 컨테이너 컴포넌트의 props가 바뀌지 않는다면 리렌더링이 자동으로 방지되어 성능이 최적화 된다.
하지만 useSelector를 사용하면 최적화 작업이 자동으로 이루어 지지 않는다 . 하지만 최적화를 위해서React.memo(컴포넌트명)
을 사용하면 해결할수 있다.