프로젝트를 통한 배움 - (4)

응애 나 프론트애긔👶·2022년 12월 1일
0
post-custom-banner

다크모드 구현


메인 페이지를 거의 마무리 단계에 들어갔다.

나름 Todolist를 작성하여 애자일(?)스럽게 작업을 했으며 매주 스프린트도 잘 하는 중이다.

생각보다 다크모드의 구현은 간단했다.

전역적인 상태관리를 사용하여 해당 값이 'light'일 때 스타일 변경 / 'dark'일 때 스타일 변경

이와 같이 스위치를 사용하여 상태를 변경하고 상태에 따른 스타일을 바꿔주면 된다.

스위치 만들기

첫번째로 스위치를 만들어야한다.

디자인을 기획하는 당시 Figma에서 그린 스위치의 디자인을 그대로 따라 갈 생각이다.

// ThemeBtn.js 코드
import React from 'react';
import styled from 'styled-components';
import { useRecoilState } from 'recoil';
import { darkMode } from '../../atoms/atoms';

const SlideThemeBtnContainer = styled.div`
  display: flex;
  align-items: center;
  width: 7rem;
  height: 3rem;
  background-color: black;
  margin-bottom: -2rem;
  border-radius: 1.5rem;
  transition: all 150ms linear;

  &.active {
    background-color: whitesmoke;
    transition: all 150ms linear;
  }
`;

const SlideThemeBtn = styled.div`
  width: 2rem;
  height: 2rem;
  background-color: white;
  margin-left: 0.5rem;
  border-radius: 50%;
  cursor: pointer;
  transform: translateX(0);
  transition: all 150ms linear;

  &.active {
    background-color: black;
    transform: translateX(4rem);
    transition: all 150ms linear;
  }
`;

const ThemeBtn = () => {
  const [themeToggle, setThemeToggle] = useRecoilState(darkMode);

  return (
    <>
      <SlideThemeBtnContainer className={themeToggle ? 'active' : null}>
        <SlideThemeBtn
          className={themeToggle ? 'active' : null}
          onClick={() => {
            setThemeToggle(prev => !prev);
          }}
        />
      </SlideThemeBtnContainer>
    </>
  );
};

export default ThemeBtn;

Recoil을 통한 상태관리

import { atom, selector } from 'recoil';

export const darkMode = atom({
  key: 'darkMode',
  default: false,
});

간단하게 Recoil에서 darkMode의 기본 값을 false로 설정했다.

이후 버튼으로 상태 값을 변경하여 false일 때는 밝은 화면 true일 때는 어두운 화면이 나오도록 만들었다.

CSS효과

먼저 버튼이 클릭이 되면 전역으로 관리되는 버튼의 상태를 변경하도록 설정했다.
darkMode라는 atom 안에는 기본 값으로 false가 들어가있는데
버튼을 클릭할 때 false를 true로 true일 때 false로 바꿔주도록 만들었다.

이후 SlideThemeBtnContainer와 SlideThemeBtn 컴포넌트의 className 또한 darkMode의 상태값에 따라 active를 넣어주도록 했다.

스타일드 컴포넌트에서 &.active 안에 transform과 transition을 사용하여 시각적인 재미 요소를 더했다.

다크모드 적용


const MainContainer = styled.div`
  height: 80vw;
  background-color: ${props => (props.themeMode ? '#C6C2C2' : 'white')};
  transition: all 150ms linear`

const Main = () => {
  const themeMode = useRecoilValue(darkMode);

  return (
    <MainContainer themeMode={themeMode}>
      <NavBar />
      <MainSectionContainer>
        <Carousel />
        <SectionContainer />
      </MainSectionContainer>
      <Footer />
    </MainContainer>
  );
};

마지막으로 darkMode의 상태 값만을 받아와 MainContainer 컴포넌트 안에 props로 넣어준다.

props를 스타일드 컴포넌트에서 사용하여 배경색을 변경하도록 했다.

🚨 자식 컴포넌트 리렌더링


다 끝이 난 줄 알았지만 문제가 하나 발생했다.

바로 themeMode의 값이 변경 되면서 Main 컴포넌트가 리렌더링 될 때 자식 컴포넌트들도 불필요한 렌더링이 일어난다는 것이다.

이 문제의 근본적인 문제를 알기 위해선 컴포넌트가 언제 리렌더링 되는지 알아야한다.

  1. 새로운 props가 들어올 때
  2. state가 변할 때
  3. 부모 컴포넌트가 리렌더링 할 때
  4. forceUpdate()를 사용할 때

이중 우리는 부모 컴포넌트가 리렌더링 될 때 자식 컴포넌트들이 리렌더링 되는 상황이다.

MainContainer 안에 있는 NavBar, MainSectionContainer, Footer 뿐만 아니라
MainSectionContainer 안에 있는 Carousel, SectionContainer 그리고 그 안에 안에 있는 자식 컴포넌트까지
불필요한 리렌더링을 해야만한다.

React Developer Tools를 사용하여 버튼을 클릭 시 일어나는 리렌더링을 확인했다.

MainSectionContainer의 배경색만 바꿀 생각이였지만 모든 컴포넌트가 리렌더링 된다.

이를 해결하기 위해 React.memo를 사용하였다.

React.memo

React.memo의 memo는 Memorization(기억/암기) 라는 뜻이다.
즉 무언가를 기억한다는 뜻이다.

React.memo는 컴포넌트가 렌더링한 결과를 기억한다.
그리고 다음 렌더링이 일어날 때 props가 같다면 React는 기억한 결과를 재사용하게 된다.

React.memo는 두개의 인자를 받을 수 있다.

React.memo(Component, [function(prevProps, nextProps)]);

첫번째 인자는 Memorization 할 컴포넌트
두번째 인자는 props를 비교하기 위한 함수를 넣을 수 있다.

두번째 인자로 비교함수를 넣는 이유는 props를 비교할 때 얕은 비교를 하기 때문인데 이는 reference 즉 참조 타입을 비교할 때 사용하기 위함이다.

사용 결과

위에 코드에서 React.memo를 사용할 곳은 MainContainer 컴포넌트 안에 있는 자식요소들이다.

export defalut 다음에 React.memo로 컴포넌트를 감싸고 보내주면 문제가 해결된다.


느낀점


먼저 React.memo와 useCallback, useMemo를 사용한 성능 최적화에 대해 공부를 했다.
예전 강의를 들을 때도 그렇고 성능 최적화에 대한 글들을 보는데
성능 최적화를 한답시고 무분별하게 useCallback과 useMemo React.memo를 쓰지 말라는 이야기를 들었다.

물론 성능 최적화를 하면 좋긴하지만 프론트에서 그런 드라마틱한 성능 최적화는 바라기 어렵다는 이야기였다.
그리고 useCallback, useMemo 무분별하게 사용하면 가독성도 떨어지게 된다.

그럼에도 프론트에서 할 수 있는 최선의 최적화는 필요로하다고 생각했고 위와 같은 Hooks와 React.memo라는 HOC도 배우는 좋은 경험이였다.

어차피 다른 컴포넌트의 font 색상이나 다른 color들 또한 바꿔야하기 때문에 리팩토링하면서 다시 살펴봐야한다.

그 다음으론 darkMode의 변경 여부를 false와 true가 아닌 'light'와 'dark'로 수정해야 할 거 같다.
물론 false와 true로도 동작하는데 뭔가 직관적이지 못하다는 생각이 들었다.

더 나아가 'light'/'dark'가 아닌 색상까지 넣어 관리하는게
나중에 유지보수에 더욱 도움이 되지 않을까 생각이된다.

마지막으론 React Developer Tool 사용법이다.
이렇게 좋은 tool이 있다는 것을 이제야 알았다니 ...
컴포넌트들의 정보와 렌더링을 확인까지 꽤나 좋은 확장 프로그램을 알게 되어 매우 만족스러웠다.


참조 사이트 🙏

React.memo
https://ui.toast.com/weekly-pick/ko_20190731

React Developer Tool
https://m.blog.naver.com/pjt3591oo/221907792621

post-custom-banner

0개의 댓글