[React] 캘린더 컴포넌트 만들기 4

Chloé·2021년 3월 24일
1

리덕스를 이용해 다수의 컴포넌트로 데이터 전달하기

이전 포스팅에서는 자식(Header) -> 부모(Calendar) -> 자식(Body)으로 데이터를 전달했다. 그런데 useState를 이용해 데이터를 변화시켜도, 마지막 자식 컴포넌트(Body)에서 리렌더링 되지 않는 문제가 발생했다. 이를 해결하지 못해, redux를 사용해보기로 마음먹었다.

0. 리덕스 기본 세팅하기

여러가지 자료를 참고해 index.js에 store를 세팅했다.

1. 접근하기

  1. moment 라이브러리를 이용해 현재 날짜를 불러오고, 이를 baseDate의 기본값으로 설정해 store에 저장한다.
  2. 리덕스 액션을 이용해 이 baseDate를 하나 감소시키거나, 증가시키는 함수를 작성한다.
  3. 각 컴포넌트들에서 이 baseDate를 받아오도록 한다. 달력의 헤더와 날짜 부분이 baseDate를 기준으로 렌더링 된다.
  4. 달력의 헤더 버튼에 리덕스 액션 함수를 설정한다.
  5. 헤더 버튼을 누르면 리덕스 액션 함수가 실행되며, store에 저장된 baseDate가 변경된다. 이 때 baseDate를 받아 설정된 각 컴포넌트의 내용도 리렌더링 된다.
  • 처음에는 자식 컴포넌트에서 날짜를 증가시키거나 감소시키고, 이렇게 증/감된 날짜를 dispatch를 이용해 state를 재설정해주려고 했다.
    • 이 경우 store의 state가 undefined로 표기된다.
    • 원래 state의 경우, Moment 객체가 state 내의 baseDate라는 항목의 date 값으로 겹겹히 주어져 있다. 그러므로 자식 컴포넌트에서 이를 불러오기 위해서는 state.baseDate.date를 호출해야 한다.
    • 그러나 컴포넌트에서 변경한 값을 반환할 때는 Moment 객체를 바로 반환하게 되어 있다. 초기값과 형태가 다르기 때문에 undefined로 나타나는 것이 아닐까 짐작해본다.
  • 따라서 리덕스 액션 함수에서 state.date로 Moment 객체를 불러와 변경하는 방식을 택했다.
    • 이 경우, 개별 컴포넌트에서 useState를 이용해 날짜값을 변경하는 함수를 다시 사용할 필요가 없다.

2. 리덕스 모듈 설정하기

import { createAction, handleActions } from "redux-actions";
import moment from "moment";

const DEC_MONTH = "baseDate/DEC_MONTH";
const INC_MONTH = "baseDate/INC_MONTH";

export const decMonth = createAction(DEC_MONTH);
export const incMonth = createAction(INC_MONTH);

const initialState = {
  date: moment(),
}

export default handleActions(
  {
    [DEC_MONTH]: (state, action) => ({
      ...state,
      baseDate: state.date.add(-1, 'months'),
    }),
    [INC_MONTH]: (state, action) => ({
      ...state,
      baseDate: state.date.add(1, 'months'),
    })
  },
  initialState
);
  • src/store/modules/baseDate.jsDEC_MONTHINC_MONTH를 정의했다.
  • 기본값으로는 현재 날짜를 불러오기 위해 moment()를 사용했다.

3. 함수형 컴포넌트에서 state의 기본 값 불러오기

Header 컴포넌트에 해당하는 calHeader.js

import React, { useState, useEffect, useCallback } from "react";
import moment from "moment";


function Calendar(props) {
  const baseDate = useSelector((state) => state)
  const curDate = baseDate.baseDate.date.clone()
  const viewMonth = curDate.format('YYYY - MM')
  • useSelector를 이용해 store에 저장된 state를 통째로 받아온다.
  • 해당 state 내에 정의된 baseDate를 불러온 뒤, 이 baseDate가 갖고 있는 요소인 date를 다시 한 번 불러온다.
  • store의 state가 변경되면서 curDate와 viewMonth도 자동으로 리렌더링되므로, 버튼을 누를 때 setViewMonth를 이용해 헤더 날짜 표기 부분을 다시 불러와 줄 필요가 없어졌다.

Body 컴포넌트에 해당하는 calBody.js

  const baseDate = useSelector((state) => state)
  const curDate = baseDate.baseDate.date.clone()
  const firstDateOfMonth = curDate.clone().startOf('month')
  const firstDayOfMonth = firstDateOfMonth.get('d')
  const firstDate = firstDateOfMonth.clone().add(-firstDayOfMonth, 'days')
  • moment 객체에 method를 적용할 경우, 원본이 되는 moment 객체 자체가 변경되며, 따라서 같은 컴포넌트 내에서 이를 참조하는 값들까지도 모두 변경되는 현상이 발생했다. 그래서 메소드를 적용하여 새로운 변수를 정의해 줄 때, clone()을 이용했다.

컴포넌트에서 store의 state를 받아줄 때 주의할 점?

  • 각 컴포넌트에서 useSelector를 이용해 state를 받아 줄 때는, state의 일부만 꺼내지 말고 전체 state를 모두 받아준다.
    • 스택오버플로우를 참고했다.
    • baseDate로 state.baseDate.date를 꺼내 줄 수도 있지만, 이 경우 store의 state가 변경되더라도 이를 인식하지 못해 리렌더링이 되지 않는 상황이 발생했다. state를 모두 받아줄 경우는 무사히 리렌더링 된다.

4. 함수형 컴포넌트에서 리덕스 액션 함수 정의하기

calHeader.js

import { useDispatch, useSelector } from 'react-redux';
import * as actions from "../../store/modules/baseDate";

  const dispatch = useDispatch();

  const decMonth = useCallback(() => {
    dispatch(actions.decMonth());
  }, [dispatch])

  const incMonth = useCallback(() => {
    dispatch(actions.incMonth());
  }, [dispatch])
  • useDispatchuseCallback을 이용했다.
  • 컴포넌트 내의 decMonth 함수가 동작할 경우, 이는 baseDate 모듈에 정의된 액션들 중 decMonth라는 함수를 불러와 dispatch 해준다.
  return (
    <div className="calHeaderWrapper">
      <div className="calHeaderBtn calHeaderBeforeBtn">
        <LeftOutlined 
          style={{ color: "#EFBF43"}}
          onClick={() => { 
            decMonth();  
          }}
        />
      </div>
      <div className="calHeaderNow">
        { viewMonth }
      </div>
      <div className="calHeaderBtn calHeaderAfterBtn">
        <RightOutlined 
          style={{ color: "#EFBF43"}}
          onClick={() => { 
            incMonth();
          }}
        />
      </div>
    </div>
  );
  • 버튼 onClick시 해당 함수들이 작동하도록 설정해준다.

Header의 버튼을 클릭할 경우 store의 state가 변경되며, 이에 따라 Header와 Body 컴포넌트에서 state를 받아주는 모든 값이 성공적으로 리렌더링 된다.

profile
클로이 데일리 로그

0개의 댓글