React-Redux 핵심 요약본

ECMs·2020년 2월 27일
12

React

목록 보기
2/2
post-thumbnail
post-custom-banner

요약본

아래 내용들은 빠르게 Redux를 사용하기 위해 핵심 부분만 정리해놓은 글이다. 필요한 부분만 쏙속 찾아서 보면 된다.

프로젝트 구조

  • components : 화면에 실제로 그려지는 컴포넌트를 담는 폴더
  • containers : 리덕스 스토어와 컴포넌트를 이어주는 매개체를 담는 폴더
  • modules : 리덕스의 State, Reducer를 정의한 파일들을 담는 폴더

리덕스 사용 프로세스

  1. moduels 폴더에 값, 그리고 액션 정의하기
  2. modules/index.js에서 combineReducers 사용해서 rootReducer 만들어주기
  3. containers 폴더에서 Container 만들고 connect 함수 사용해서 컴포넌트와 리덕스 연동하기

모듈 작성하기

  1. Action Type 정의하기
  2. Action Type 반환하는 함수 만들어주기
  3. 초기 상태 작성하기
  4. Reducer 함수 만들기

Action Type 정의

const ACTIONE_NAME = "module_name/ACTION_NAME";

Action Type 반환하는 함수 만들기

export const action = () => ({ type : ACTION_NAME });

or

import { createAction } from 'redux-actions'
export const action = createAction(ACTION_NAME);

파라미터가 존재하는 경우

export const action = createAction(ACTION_NAME, data => data);
// 이 작업이 필수는 아니며 생략해도 동일하게 작동
// 다만 넣어주는 것이 가독성이 좋아진다.
// 함수가 어떤 파라미터를 필요로 하는지 파악할 수 있다.

결과는 아래처럼 나온다.

{
    type : ACTION_NAME,
    payload : data
}

초기 상태 작성

const initialState = {
    number : 0
}

Reducer 함수 작성

아래 4가지 조건 만족

  • Reducer 함수는 이전 상태와 액션 객체를 파라미터로 받는다
  • 파라미터 외의 값에는 의존하면 안된다
  • 이전 상태는 절대로 건드리지 않고, 변화를 준 새로운 상태 객체를 만들어서 반환한다
  • 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과 값을 반환해야 한다
function reducer(state = initialState, action) {
  switch (action.type) {
    case ACTION_NAME:
      return {
        ...
      };
    default:
      return state;
  }
}
  
or


import { handleActions } from 'redux-actions';
(...)
const reducer = handleActions({
    [ACTION_NAME] : (state, action) => ({
  		...
  })
);
  
  
// 파라미터가 존재하는 경우

const reducer = handleActions({
    [ACTION_NAME] : (state, {payload : data}) => ({
      ...
    })
);
  

rootReducer 연결

moduels/index.js

import { combineReducers } from 'redux';
const rootReducer = combineReducers({
  reducer1,
  reducer2,
});
export default rootReducer;

리액트와 리덕스 연동

src/index.js

import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules';

const store = createStore(rootReducer);

ReactDOM.render(
  <Provider store={store}>
  	<App />
  </Provider>,
  document.getElementById("root")
);

dev-tool 연결 하려면

import { composeWithDevTools } from "redux-devtools-extension";
const store = createStore(rootReducer, composeWithDevTools());

컨테이너 만들기

connect 함수 이용하기

Redux와 연동하기 위해서는 connect 함수를 사용해야 한다.

connect(mapStateToProps, mapDispatchToProps)(연동하고싶은 컴포넌트)

mapStateToProps는 Store에 담긴 값을 받아서 Props로 변환시켜주는 역할을, mapDispatchToProps는 Action을 Props에 담는 역할을 한다.


mapStateToProps

const mapStateToProps = state => ({
  data: state.counter.number
});

mapDispatchToProps

import { action } from "../modules/something";
const mapDispatchToProps = dispatch => ({
  action_name: () => dispatch(action())
});

or

import { bindActionCreators } from 'redux'

const mapDispatchToProps = dispatch =>
  bindActionCreators(
    {
      action1,
      action2
    },
    dispatch
  );

Container

const SomeContainer = ({ data, action }) => {
  return (
    <Some number={data} onChange={action} />
  );
};
export default connect(mapStateToProps, mapDispatchToProps)(SomeContainer);

Hooks 사용하기

useSelector

const result = useSelector(상태 선택 함수);
import {useSelector} from 'react-redux';
(...)
const SomeContainer = () => {
    const data = useSelector(state => state.some.number)
  return (
    <Some number={data}/>
  );
};

useDispatch

const dispatch = useDispatch();
import React, {useCallback} from "react";
(...)
 const SomeContainer = () => {
  const dispatch = useDispatch();
  const onClick = useCallback(() => dispatch(click()), [dispatch]);
  // 파라미터 존재하는 경우
  const onChange = useCallback((input) => dispatch(change()), [dispatch]);
  return (
    <Some
      onClick={onClick}
      onChange={onChange}
    />
  );
};

useAction
lib/useActions.js

import { bindActionCreators } from 'redux'
import { useDispatch } from 'react-redux'
import { useMemo } from 'react'

export function 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]
  )
}

첫 번째 파라미터 : 액션 생성 함수로 이루어진 배열
두 번째 파라미터 : 이 배열 안에 원소가 바뀌면 액션 다시 디스패치

import useActions from '../lib/useActions'
(...)
 const SomeContainer = () => {
  const [onClick, onChange] = useActions(
    [click, change],
    []
  );
  return (
    <Some
      onClick={onClick}
      onChange={onChange}
    />
  );
};

주의할 점

connect 함수의 경우 props가 바뀔때만 렌더링을 다시 하지만 useSelector는 아니므로 React.memo(SomeContainer) 사용해서 최적화 해주기

export default React.memo(SomeContainer);

다만 React.memo를 항상 사용하는 것이 능사는 아니다. React.memo를 사용해야 하는 경우는 다음과 같은 경우다.
1. 순수 함수 컴포넌트
2. 자주 렌더링을 해야하는 함수
3. 같은 props로 자주 리 렌더링 되는 함수
4. 컴포넌트가 props 비교를 필요로 하는 많은 UI 요소를 가지고 있는 경우

만약 props가 자주 변동되는 환경이라면 React.memo를 사용한다고 해도 컴포넌트를 다시 렌더링 해야하므로 불필요한 비교 과정만 추가하는 꼴이다! 현명하게 사용하자.

profile
=ㅅ=
post-custom-banner

3개의 댓글

comment-user-thumbnail
2020년 2월 27일

잘 봤습니당 ㅎㅎ React.memo는 무조건 하는게 좋을까요? 별로 비교가 필요 없을 경우에는 memo를 하면 오히려 비교한다고 성능이 안 좋아진다고도 봤어가지구요 ㅎㅎ.

1개의 답글
comment-user-thumbnail
2021년 4월 9일

React + Redux-Saga 으로 [Modules + Components + Hooks + Pages] 구조로 Containers 없이할려는데 어떻게해야 구조를 잘 짤수있을지 조언 해주실수있을까요?
Containers를 쓰지않고 로직을 Hooks폴더에서 다 처리하고 Components에서 최소한의 코드작성으로 보여주고싶습니다.

답글 달기