useSelect 렌더링 문제 해결
react devtools를 설치했다면 브라우저 개발자 도구에서 기능을 사용할 수 있다.
Components → 설정 → Highlight updates when components render 켜주기
불필요한 렌더링이 일어남을 확인할 수 있다.
useSelector 동작 원리
useSelector로 구독하는 값이 dispatch 후 결과값이 달라지면 리랜더링을 실행한다.
여기서 객체로 구독을 하면 내용이 같아도 객체 자체는 변경된 것으로 인식하기 때문에 랜더링이 일어난다.
useSelector 문제 해결 방법
- Object를 새로 만들지 않도록 State 쪼개기
- 새로운 Equality Function 사용
: equality function으로 shallowEqual을 할당해주면 obj 값을 비교할 때 내용을 비교해줌
적용해보기
// ImageModalContainer.js
import React from 'react';
import { useSelector } from 'react-redux';
import ImageModal from '../components/ImageModal';
function ImageModalContainer() {
const { modalVisible, bgColor, src, alt } = useSelector(state => ({
modalVisible: state.imageModal.modalVisible,
bgColor: state.imageModal.bgColor,
src: state.imageModal.src,
alt: state.imageModal.alt,
}));
...
- 1번
import React from 'react';
import { useSelector } from 'react-redux';
import ImageModal from '../components/ImageModal';
function ImageModalContainer() {
const modalVisible = useSelector(state => state.imageModal.modalVisible);
const bgColor = useSelector(state => state.imageModal.bgColor);
const src = useSelector(state => state.imageModal.src);
const alt = useSelector(state => state.imageModal.alt);
- 2번
import React from 'react';
import { useSelector, shallowEqual } from 'react-redux';
import ImageModal from '../components/ImageModal';
function ImageModalContainer() {
const { modalVisible, bgColor, src, alt } = useSelector(
state => ({
modalVisible: state.imageModal.modalVisible,
bgColor: state.imageModal.bgColor,
src: state.imageModal.src,
alt: state.imageModal.alt,
}),
shallowEqual
);
// PhotoListContainer.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PhotoList from '../components/PhotoList';
import { fetchPhotos } from '../redux/photos';
function PhotoListContainer() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchPhotos());
}, [dispatch]);
const { photos, loading } = useSelector(state => ({
photos:
state.category.category === 'all'
? state.photos.data
: state.photos.data.filter(
photo => photo.category === state.category.category
),
loading: state.photos.loading,
}));
...
- 2번
const { photos, loading } = useSelector(state => ({
photos:
state.category.category === 'all'
? state.photos.data
: state.photos.data.filter(
photo => photo.category === state.category.category
),
loading: state.photos.loading,
}), shallowEqual);
shallowEqual을 사용했는데도 리랜더링이 발생했다.
왜일까?
photos의 filter 함수가 매번 새로운 배열을 반환하기 때문에 리랜더링이 발생한다.
- 해결
filter 함수를 바깥에서 동작하도록 한다.
import React, { useEffect } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import PhotoList from '../components/PhotoList';
import { fetchPhotos } from '../redux/photos';
function PhotoListContainer() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchPhotos());
}, [dispatch]);
const { category, allPhotos, loading } = useSelector(
state => ({
category: state.category.category,
allPhotos: state.photos.data,
loading: state.photos.loading,
}),
shallowEqual
);
const photos =
category === 'all'
? allPhotos
: allPhotos.filter(photo => photo.category === category);
...
Redux Reselect를 통한 렌더링 최적화
위 PhotoListContainer의 문제점
- category가 실질적으로 랜더링하는 과정에서 사용되지 않음
- useSlector State들이 개발 과정에서 점점 추가되면 photos가 변하지 않아도 State에 변화가 일어날 때마다 filter를 실행하게 됨
→ reselect를 사용
https://github.com/reduxjs/reselect
- 적용
// PhotoListContainer.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PhotoList from '../components/PhotoList';
import { fetchPhotos } from '../redux/photos';
import selectFilterPhotos from '../redux/selector/selectFilterPhotos';
function PhotoListContainer() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchPhotos());
}, [dispatch]);
const photos = useSelector(selectFilterPhotos);
const loading = useSelector(state => state.photos.loading);
...
// selectFilterPhotos.js
import { createSelector } from 'reselect';
export default selectFilteredPhotos = createSelector(
[state => state.photos.data, state => state.category.category],
(photos, category) =>
category === 'all'
? photos
: photos.filter(photo => photo.category === category)
);
reselect는 memoization 기법을 사용해 함수에 똑같은 인자가 들어오면 미리 캐시된 값을 반환한다.
'web' 카테고리의 다른 글
[React Test] 리액트 테스트에 대해서 - RTL, Jest, 쿼리함수 (0) | 2022.08.10 |
---|---|
[React 최적화] 실습 4. 이미지 갤러리 페이지(3) - 병목함수 memoization 적용 (0) | 2022.08.08 |
[React 최적화] 실습 4. 이미지 갤러리 페이지(1) - Layout Shift, react-lazyload (0) | 2022.08.03 |
[React 최적화] 실습 3. 일반 홈페이지(3) - 캐시, css 최적화 (0) | 2022.08.01 |
[React 최적화] 실습 3. 일반 홈페이지(2) - 동영상, 폰트 최적화 (0) | 2022.07.30 |