리팩토링 이유
기존에 스스로 리액트를 이용해서 만든 다크모드가 지원되는 투두리스트를 다시 리팩토링하게 되었다. 이유는 java, spring을 공부하면서 솔로프로젝트로 '투두리스트 백엔드 서버' 를 만들게 되었는데 내가 로컬 스토리지를 활용해서 프론트 부분만 만든 투두리스트 앱과 연결하려고 했다.
그러다 보니 이때 사실 고치지 못하고 시간이 없어서 넘어간 다크모드를 구현하는 효율성이 매우 떨어지게, 그리고 번거롭게 구현한 코드가 너무 거슬리고 눈에 밟혔다. 그래서 일단 로컬스토리지 -> 백엔드 서버와의 연결은 잠시 뒤로 미루고 먼저 브랜치를 하나 새로 따서 스파게티 코드를 정리했다.
이전에 가관인 스파게티 코드
DarkModeContext 정의
import { createContext, useState } from 'react';
export const DarkModeContext = createContext();
export function DarkModeProvider({ children }) {
const [darkMode, setDarkMode] = useState(false);
const toggleDarkMode = () => setDarkMode((mode) => !mode);
return (
<DarkModeContext.Provider value={{ darkMode, toggleDarkMode }}>
{children}
</DarkModeContext.Provider>
);
}
DarkModeContext를 따로 정의해서 상태값인 'darkMode'와 다크모드 상태를 토글링하는 함수인 'toggleDarkMode'를 프롭스로 전달해서 Provider가 감싸진 곳에서 'useContext(DarkModeContext)' 를 통해서 사용 할 수 있도록 했다. 이렇게 하면 어느 곳에서 토글링을 하던 프로바이더로 감싸져 있는 모든 컴포넌트에 상태값이 전달된다.
Context의 다크모드를 확인하고 PostCss를 적용
// 모든 클래스 앞에 darkMode 이면 post CSS로 작성된 클래스를 적용함으로 해야했다
// 이게 투두리스트와 같이 단일 기능의 앱이지만 여러 복잡한 기능의 앱에서 다크모드를 구현해야한다면?
<div className={darkMode ? DarkMode.wrapper : 'wrapper'}>
<div className={darkMode ? DarkMode.contentBox : 'contentBox'}>
<button onClick={() => toggleDarkMode()} className='darkModeBtn'>
{darkMode ? 'Light mode' : 'Dark mode'}
</button>
<div className={darkMode ? DarkMode.title : 'title'}>My Tasks</div>
처음에는 다크모드를 어떻게 적용해야 할지 몰라서 위의 코드처럼 적용했다. 사실 어떻게 만들어야 할지 몰라서 생각하던 중에 '다크모드로 상태를 공유하는 거는 알겠는데 이거로 어떻게 색상을 바꿔야하지?' 하는 생각이 들었다. 그래서 생각난게 바로 'PostCSS' !
PostCSS 란?
포스트CSS는 프론트엔드 개발자 혹은 퍼블리셔라면 숙명으로 가지고 가게되는 것이 바로 클래스 명을 짓는 것이다. 예를들어서 module을 통해서 여러 html 파일을 불러온다고 하자
// ✨ A 컴포넌트
import './App.css';
function App() {
return (
<B 컴포넌트 />
<C 컴포넌트 />
);
}
// ✨ B 컴포넌트
import './B.css';
function B컴포넌트() {
return <button classname = {btn}> 추가 </button>
}
// ✨ C 컴포넌트
import './C.css';
function B컴포넌트() {
return <button classname = {btn}> 삭제 </button>
}
// 🔥CSS 파일
- B.css
.btn {
background-color : red;
}
- C.css
.btn {
background-color : blue;
}
예를 들어서 위와 같은 상황이라고 예를 든다면 App 안에 B와 C 컴포넌트가 있는데 들의 버튼 색상이 다르다고 했을때 아래처럼 CSS 파일을 각자 만들어서 임포트를 하게 되어도 둘다 같은 색상으로 보이게 된다. 아마 둘다 같이 'blue' 로 보이게 될 것이다.
이렇게 되면 둘다 클래스명을 다르게 설정해야한다. 그렇게 되면 css 코드를 재사용하기도 어려워지고 규모가 커지면 커질 수록 버튼의 이름을 무수히 많이 지어내야 할 것이다.
이러한 문제를 해결하도록 나온게 'PostCSS' 포스트CSS는 모듈화를 시켜서 해당 컴포넌트에서만 CSS가 적용되도록 한다. 그래서 같은 btn이라는 이름이어도 두가지의 css 파일이 충돌하지 않는다.
// 모든 클래스 앞에 darkMode 이면 post CSS로 작성된 클래스를 적용함으로 해야했다
// 이게 투두리스트와 같이 단일 기능의 앱이지만 여러 복잡한 기능의 앱에서 다크모드를 구현해야한다면?
<div className={darkMode ? DarkMode.wrapper : 'wrapper'}>
<div className={darkMode ? DarkMode.contentBox : 'contentBox'}>
<button onClick={() => toggleDarkMode()} className='darkModeBtn'>
{darkMode ? 'Light mode' : 'Dark mode'}
</button>
<div className={darkMode ? DarkMode.title : 'title'}>My Tasks</div>
위에서 보이는 코드 중에 클래스네임 옆에 삼항연산자로 {darkMode ? DarkMode.title : 'title'} 이런식으로 작성된 코드가 보일 것이다. 이거는 DarkModeContext를 통해서 darkMode가 true이면 PostCSS를 통해서 모듈화된 CSS 파일이 적용되도록 했다. 하지만 여기에는 아주 큰 문제가 있다.
모듈화 되었기 때문에 다크모드 색상만 들어있는 것이 아닌 이 컴포넌트에 들어간 모든 CSS 파일이 들어가 있어야 한다.
.items {
padding: 0.3rem 0rem;
display: flex;
justify-content: space-between;
}
.itemWrapper {
display: flex;
flex-direction: row;
align-items: center;
}
.items .checkbox {
margin-right: 0.3rem;
}
.items .itemText {
display: inline-block;
letter-spacing: -0.5px;
font-weight: 500;
font-size: 1rem;
padding-left: 5px;
color: #dadada;
}
.itemText.checked {
color: #b9b9be;
text-decoration: line-through;
}
.checkbox input {
display: none;
}
......
이런식으로 해당 컴포넌트에서 필요하지도 않은 CSS 파일 내용이 중복해서 계속 불러와졌다. 지금은 간단한 Todo-List이지만 실제 앱이라고 생각했을 때는 계속해서 반복해야 하는 CSS 파일이 무수하게 늘어나게 된다.
해당 컴포넌트 마다 계속 수작업으로 코드를 추가해야함
<div className={darkMode ? DarkMode.title : 'title'}>My Tasks</div>
위와 같은 태그가 계속해서 생긴다면 20개 50개 100개가 생긴다면 그때마다 삼항연사자를 통해서 다크모드인지 확인하고 다크 모드이면 다크모드 css 파일을 그렇지 않으면 그냥 title을 클래스를 붙이도록 해야한다. 이것은 굉장히 불편한 작업이면 내가 놓치거나 나중에 디버깅하기도 엄청 힘들다.
문제점 정리
- 다크모드로 인해 색상이 바뀌는 태그마다 삼항연산자를 수동으로 추가해줘야함
- 포스트CSS로 모듈을 분리했기 때문에 다크모드 색상과 관련되지 않은 모든 CSS 코드를 다시 붙여넣기 해야함
- 프로젝트가 커질 수록 디버깅도 불가능할 뿐더러 불필요한 코드로 인해 용량이 비대해짐
문제점 해결
이 해결은 유튜버 '드림코딩'님이 운영하시는 강의 사이트에서 강의내용을 참고해서 문제 해결을 했습니다.
:root {
--color-gray: #575767;
--color-dark-gray: #0e0e11;
--color-soft-gray: #b9b9be;
--color-accent: #ff1616;
--color-bg: white;
--color-screenBg: beige;
--color-content-border: gainsboro;
}
html.dark {
--color-gray: #dadada;
--color-dark-gray: #dadada;
--color-bg: #141419;
--color-screenBg: #141419;
--color-content-border: #575767;
--color-accent: #27ffc7;
}
▶️ CSS에 색상을 변수로 설정해서 미리 관리하도록 함 : 이게 굉장히 중요한게 예를들어 색상이 들어가는 곳마다 직접 색상을 입력하면 나중에 같은 색상을 수정해야할 때 모든 것을 하나하나 찾아서 수정해야 하지만 이렇게 root에 지정해서 변수로 저장해두면 해당 색상만 바꾸면 된다.
▶️ darkMode 토글 버튼이 클릭되면 html 태그에 dark 클래스가 생기도록 javascript 코드를 만들었다. 그래서 html에 dark 클래스명이 생기면 다크모드 색상이 바뀌어야하는 것만 색상을 바꿔주게 되면 너무나도 손쉽게 다크모드가 완성된다.
import { createContext, useContext, useState } from 'react';
const DarkModeContext = createContext();
export function DarkModeProvider({ children }) {
const [darkMode, setDarkMode] = useState(false);
const toggleDarkMode = () => {
setDarkMode(!darkMode);
updateDarkMode(!darkMode);
};
return (
<DarkModeContext.Provider value={{ darkMode, toggleDarkMode }}>
{children}
</DarkModeContext.Provider>
);
}
// ✨ html 도큐먼트에 dark 클래스명을 토글링하는 로직
const updateDarkMode = (darkMode) => {
if (darkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
};
export const useDarkMode = () => useContext(DarkModeContext);
총 결론
- 처음 설계가 정말 중요하다. 왜 클린 아키텍처,, 왜 시니어 개발자로 갈수록 아키텍처 설계능력이 중요하지 혹은 애초에 앱을 설계할 때 코드가 유연성을 가지도록 잘 기초를 설계해서 시작하는 것이 중요하다는 것을 잘 알게 되었다.
- 간단한 다크모드 같은 기능도 어떻게 하느냐에 따라 이렇게 효율성이 달라지는데 하물며 다른 것이겠냐는 생각이 들었다. 역시 개발자는 계속해서 리팩토링 리팩토링!
- 리팩토링을 하고나니 묵은 체증이 내려가는 듯한 시원함을 느꼈다. 앞으로 더 해야할게 많지만 점차 발전시켜보자
깃헙 링크
https://github.com/Kyunju/todolist-reactproject
레퍼런스
드림코딩 아카데미 리액트강의 : https://academy.dream-coding.com/courses/react
'개발일지' 카테고리의 다른 글
[KPT] Section 4을 마치며 하는 회고 feat.코드스테이츠 백엔드 (1) | 2023.06.08 |
---|---|
[솔로 프로젝트] TodoList Test 작성 및 API 문서화 하기 1탄 (0) | 2023.06.08 |
[프로젝트일지 - 에러 해결] JPA 연관관계 맵핑 문제 오류 feat.클래스 이름 바꿀 때는 조심 또 조심! (0) | 2023.05.27 |
[TIL] Cloud 배포에 대해서 feat. 코드스테이츠, AWS (0) | 2023.05.25 |
[QnA 게시판 오류일지 - 1] 질문 등록을 위한 HTTP post 요청시 응답 오류 코드 500 해결하기 (0) | 2023.05.15 |