[React.js]리액트 react-redux, reselect라이브러리

2021. 7. 30. 21:34Web_Programming/React

 

 

 

💻 react-redux

 

기존에 리액트에서 리덕스 사용

👉 useEffect 훅 & useReducer 훅을 사용하여 상태값을 업데이트

 

react-redux

👉 useSelector 훅으로 상태값 업데이트 , 자동으로 값 변경시에만 렌더링 가능

 

 

App 컴포넌트

 

Provider에 store를 넘겨 사용 : 액션 처리 시, 이벤트를 받아 하위 컴포넌트가 렌더링 되도록

import React from 'react';
import FriendMain from './friend/container/FriendMain';
import TimelineMain from './timeline/container/TimelineMain';
import { Provider } from 'react-redux';
import store from './common/store';

export default function App() {
  return (
    <Provider store={store}>
      <div>
        <FriendMain />
        <TimelineMain />
      </div>
    </Provider>
  )
}

 

import React from 'react';
import { getNextFriend } from '../../common/mockData'; // 데이터베이스 API 샘플코드
import { addFriend } from '../state';
import FriendList from '../component/FriendList';
import { useSelector, useDispatch } from 'react-redux';

export default function FriendMain() {
  // 데이터
  const friends = useSelector(state => state.friend.friends);
  const dispatch = useDispatch();
  /*  react-redux 사용전
  const [, forceUpdate] = useReducer(v => v+1, 0);
  
  // 스토어 함수로 상태변경을 감지하고 강제 업데이트
  useEffect(() => {
    let prevFriends = store.getState().friend.friends;
    const unsubscribe = store.subscribe(() => {
      const friends = store.getState().friend.friends;
      if (prevFriends !== friends) {
        forceUpdate();
      }
      prevFriends = friends;
    });
    return () => unsubscribe();
  }, []);
  */

  // 이벤트
  function onAdd() {
    const friend = getNextFriend();
    dispatch(addFriend(friend));
  }
  
  // 렌더링
  return (
    <div>
      <button onClick={onAdd}>친구 추가</button>
      <FriendList friends={friends} />
    </div>
  );
}

 

💡 useSelector 훅

 

react-redux에서 상탯값을 가져올 때 사용되는 훅으로 아래와 같이 사용됩니다.

const friends1 = useSelector(state => state.friend.friends);
const friends2 = useSelector(state => state.friend.friends);
const friends3 = useSelector(state => state.friend.friends);

여러 값을 가져올 때, 위처럼 분리해서도 가능하고 하나의 배열 및 객체로 가져오는 것도 가능합니다.

const [friends1, friends2] = useSelector(state => [state.friend.friends1, state.friend.friends2]);

❗ 그냥 위처럼 사용 하면, 배열은 계속 새로 생성되어 계속 렌더링이 되는 문제가 발생

 

👉 react-redux에서 제공하는 값 비교 함수 shallowEqual을 세 번째 인자로 넣어줍니다.

값 비교 후 변경 시에만 렌더링

import { shallowEqual } from 'react-redux';

const [friends1, friends2] = useSelector(
  state => [state.friend.friends1, state.friend.friends2], 
  shallowEqual
);

 

+ TIP . 별도의 커스텀 훅으로 관리

import { shallowEqual } from 'react-redux';

function useMySelector(selector) {
  return useSelector(selector, shallowEqual);
}

const [v1, v2] = useMySelector(state => [state.v1, state.v2]);
const v3 = useMySelector(state => state.v3); //비효율적(하위 속성 모두 비교) 배열로 반환권장
const [v4] = useMySelector(state => [state.v4]);

💻 reselect 라이브러리

 

: 메모제이션을 가능하게 해주는 라이브러리

 

아래와 같이 createSelector를 이용하면,

👉 배열의 값이 변경될 때만, 함수가 수행되도록 해주는 메모제이션기능을 수행

import { createSelector } from 'reselect';

const getFriends = state => state.friend.friends;
export const getAgeLimit = state => state.friend.ageLimit;
export const getShowLimit = state => state.friend.showLimit;

export const getFriendsWithAgeLimit = createSelector(
  [getFriends, getAgeLimit],
  (friends, ageLimit) => friends.filter(item => item.age <= ageLimit)
)

export const getFriendsWithAgeShowLimit = createSelector(
  [getFriendsWithAgeLimit, getShowLimit],
  (friendsWithAgeLimit, showLimit) => friendsWithAgeLimit.slice(0, showLimit)
)

 

사용 코드

const [
  ageLimit,
  showLimit,
  friendsWithAgeLimit,
  friendsWithAgeShowLimit,
] = useSelector(state => [
    getAgeLimit(state),
    getShowLimit(state),
    getFriendsWithAgeLimit(state),
    getFriendsWithAgeShowLimit(state),
  ],
, shallowEqual)
/* 사용전
const [
  ageLimit,
  showLimit,
  friendsWithAgeLimit,
  friendsWithAgeShowLimit,
] = useSelector(state => {
  const { ageLimit, showLimit, friends } = state.friend;
  const friendWithAgeLimit = friends.filter(item => item.age <= ageLimit);
  return [
    ageLimit,
    showLimit,
    friendsWithAgeLimit,
    friendsWithAgeLimit.slice(0, showLimit),
  ]
}, shallowEqual)
*/

reselcet라이브러리 사용 전

👉 useSelector 훅이 액션이 처리될 때마다 현재 상태의 데이터를 반환

+ friends 값과 ageLimit 값이 변경되지 않았다면 추가적으로 렌더링을 하지는 않지만 filter 연산은 무조건 수행

 

reselector 라이브러리 사용

👉 friends 와 ageLimit 값이 변경이 되었을 경우에만 filter 연산 수행

+ 셀렉터 코드의 분리를 통해서 코드의 개선 효과

 


💻 리덕스 사용 팁

 

리덕스에서,

 

💡 리듀서에서만 간단하게 관리하는 상태값이라면 별도의 공통함수를 만들면 좀 더 간편하게 사용할 수 있습니다.

 

// 공통 함수
function createSetValueAction(type) {
  return (key, value) => ({ type, key, value });
}
function setValueReducer(state, action) {
  state[action.key] = action.value;
}

// 액션, 액션 크리에이터
const SET_VALUE = 'horong/SET_VALUE';
const setValue = createSetValueAction(SET_VALUE);

// 리듀서
function reducer = createReducer(INITIAL_STATE, {
  [SET_VALUE]: setValueRecucer,
  // ...
});

// 컴포넌트 (상태값을 저장할 이름과 값을 입력)
dispatch(setValue('key', value));

 

 

💡 리덕스 코드의 규모가 커지면, 코드를 액션/리듀서의 코드를 분리하는 것이 좋습니다.

💡 또한 코드에서 코드의 이해력과 가독성을 높이기 위해 액션 함수를 객체로 변경하여 묶는 것도 좋습니다.

// 액션, 액션크리에이터
export const types = {
  SET_VALUE: 'common/SET_VALUE',
  SET_XXX: 'thisComponent/SET_XXX',
}

export const actions = {
  setValue: createSetValueAction(SET_VALUE),
  setXXX: v => ({ type: 'XXX', data })
}

// 공통 함수
function createSetValueAction(type) {
  return (key, value) => ({ type, key, value });
}
function setValueReducer(state, action) {
  state[action.key] = action.value;
}

// 리듀서
function reducer = createReducer(INITIAL_STATE, {
  [SET_VALUE]: setValueRecucer,
  // ...
});

 

반응형