프론트엔드에서 사용자의 시각적인 경험은 매우 중대한 요소 중 하나이다. 그 중에서도 사용자가 원하는 홈페이지의 테마를 선택하는 기능은 가히 필수적인 기능이라고 해도 과언이 아니다. 이번 포스트에서는 React와 TypeScript를 사용하여 Create React App(CRA)으로 구축된 포트폴리오 프로젝트에서 테마 전환 기능을 구현하는 방법을 정리해보려 한다.
테마 전환 기능을 구현하는 방식은 여러가지가 있지만, 이번에는 Redux를 사용하여 사용자의 선택 테마를 전역 State로 관리하는 방법을 사용해보았다.
(...)
const themes = {
white: {
primaryBackground: '#fff',
primaryText: '#333',
secondaryText: '#aaa',
toggleBackground: '#aaa',
toggleCircle: '#333',
togglebuttoncolor: '#fff',
secondaryBackground: 'rgba(255, 255, 255, 0.8)', // span 태그의 배경색
hoverBackground: 'rgba(255, 255, 255, 0.3)', // hover 시의 배경색
emphasisColor: '#DAA520', // Soft Gold
},
black: {
primaryBackground: '#333',
primaryText: '#fff',
secondaryText: '#ddd',
toggleBackground: '#aaa',
toggleCircle: '#fff',
togglebuttoncolor: '#333',
secondaryBackground: 'rgba(0, 0, 0, 0.8)', // span 태그의 배경색
hoverBackground: 'rgba(0, 0, 0, 0.6)', // hover 시의 배경색
emphasisColor: '#007BFF',
},
};
우선 필요한 것은 테마의 종류와 해당 테마에 따라 변화해야하는 색들의 종류이다. 이번에 구현한 내 포트폴리오 홈페이지는 화이트/블랙 테마로 구성된다.
포트폴리오 프로젝트에서 사용된 각각의 색상들의 역할은 아래와 같다.
화이트 테마에서의 모습이 이렇다면..
블랙 테마에서는 이렇게 바뀌어야 한다. 또한 모든 글자를 흰색과 검정색으로만 표시할 수는 없다. 각 테마에 맞게 특정 글자나 문장을 강조하는 역할의 글자색이 따로 필요하다.
테마 변경 버튼의 경우 홈페이지의 기본 배경색과 '다른' 독자적인 배경색을 가져야 한다.
프로젝트 Home 컴포넌트에 구현한 버튼들은 기본적으로 마우스가 hover되었을 때 기존에 사용된 색들에 약간의 투명도가 적용되어야 한다.
내 포트폴리오 프로젝트에서 테마를 전환하는 기능은 모든 페이지에서 사용된다. 그렇다면 현재 테마가 무엇인지를 다루는 데이터는 전역적으로 사용되어야 하며 Redux가 그 역할을 수행할 수 있다.
가장 먼저 Store를 작성해야 한다. (Redux 적용 방법은 다른 포스트에서 자세하게 다룰 생각이다.)
// 테마 상태 정의
interface ThemeState {
value: 'white' | 'black';
}
const initialState: ThemeState = {
value: 'white', // 초기 테마 설정
};
Typescript를 사용하고 있기 때문에 Store 내부에서 사용될 타입을 지정해준다. 또 Redux Store의 생성 이후 theme의 초기값도 설정해준다.
const themeSlice = createSlice({
name: 'theme',
initialState,
reducers: {
// 테마 변경 액션
toggleTheme: state => {
if (state.value === 'white') {
state.value = 'black';
} else {
state.value = 'white';
}
},
},
});
홈페이지에서 theme를 변경하는데 사용될 toggleTheme Action을 정의해준다. 조건문을 사용하여 Action이 호출되었을 때 theme의 value를 변경하도록 하였다.
<ToggleButton
onClick={() => dispatch(toggleTheme())}
currentTheme={currentTheme}
>
그리고 필요한 곳에서 toggleTheme Action을 호출하여 테마의 값을 변경할 수 있도록 해준다.
const currentTheme = useSelector((state: RootState) => state.theme.value);
그리고 useSelector Hook을 이용하여 필요한 컴포넌트에서 theme의 State를 가져온다.
<ThemeProvider theme={themes[currentTheme]}>
<Navbar />
<Routes>
(...)
</Routes>
<Footer />
</ThemeProvider>
ThemeProvider는 styled-components 라이브러리에서 제공하는 기능으로 애플리케이션의 모든 스타일에 일관된 테마를 제공하는 역할을 수행한다. 이를 이용하여 정의된 테마 객체를 하위 컴포넌트로 전달하면 된다. ThemeProvider로 인해 모든 하위 컴포넌트들은 'theme' props를 자동으로 전달받게 되어 해당 컴포넌트에서 테마 데이터를 사용할 수 있게 된다.
const HomeWrapper = styled.div`
background-color: ${props => props.theme.primaryBackground};
color: ${props => props.theme.primaryText};
`;
ThemeProvider로 인해 각 컴포넌트들은 현재 테마가 무엇인지에 대한 값을 props로 받아오고 있다. 따라서 styled-components으로 전달된 테마 값을 통해 스타일이 동적으로 변화하도록 하였다.
테마를 전환하는 기능은 단순히 페이지의 색을 바꾸는데 그치는 것이 아니다. 가령 낮과 밤의 조명 조건에 따라 웹사이트를 최적화하여 시각적 피로를 줄이고, 정보의 가독성을 향상하는 역할을 수행할 수 있다. 이러한 특성은 특히 장시간 동안 화면을 사용하는 사용자들에게 중요하며, 네이버나 구글 등의 유력한 IT 회사들이 자사의 홈페이지에 다크 모드 등의 테마를 추가하는 이유이기도 하다.
ThemeProvider의 사용으로 인해 개별 컴포넌트들은 테마에 관한 로직으로부터 분리되고, 다양한 테마 상태에서도 동일한 방식으로 작동할 수 있는 환경을 보장받을 수 있다. 이를 통해, 단일 컴포넌트를 다양한 환경에서 재사용함으로써 개발 시간을 단축하고 코드의 일관성을 유지할 수 있고 더 나아가 새로운 테마를 도입하거나 기존 테마를 개선할 때 필요한 시간과 노력을 대폭 줄일 수 있다.