기존 진행했던 투두리스트 프로젝트를 TypeScript로 리팩토링 하면서 스타일링에 대한 공부를 더 해보고 싶었다.
Styled Components와 ThemeProvider를 사용해서 다크모드 구현을 해보기로 했다. 과정을 최대한 자세하게 정리하면서 기능에 대해 더 이해하고, 또 기록해보려고 한다.
1. React TypeScript project 셋업하기
yarn create react-app my-app --template typescript
2. 모듈 설치
npm install styled-components @types/styled-components
3. 폴더에 라이트모드와 다크모드 Theme을 가진 Theme 파일 생성하기
// src/theme.ts
const lightTheme = {
colors: {
background: '#ffffff',
text: '#000000',
},
};
const darkTheme = {
colors: {
background: '#222222',
text: '#ffffff',
},
};
export { lightTheme, darkTheme };
4. src 폴더에 ThemeProvider 컴포넌트 만들기
- React.createContext: Context를 생성하는 함수 → 전역적으로 데이터를 공유하고 전달하는 메커니즘으로, 컴포넌트간의 명시적인 props 전달없이도 데이터를 공유, 컴포넌트 계층 구조의 깊은 곳에 있는 컴포넌트에서도 데이터에 접근할 수 있음!
- 기본 값을 가진 컨텍스트 객체 생성, 현재 다크모드와 다크모드를 토글하는 함수를 기본 제공하고 있음
- toggleDarkMode: () => {} : 빈 함수라는 뜻!아직은 아무 작업도 수행하지 않지만, 다크 모드를 토글하는 기능에 사용! 현재 다크모드 상태를 확인하여 상태를 반대로 변경하는 역할! → ThemeProvider 컴포넌트에서 적절하게 구현 필요
- ThemeProvider 정의, children prop을 받는 함수형 컴포넌트(FC, Functional Component)
→ 여기서 children은 ThemeProvider로 감싼 모든 컴포넌트 트리
- ReactNode? → React에서 제공하는 타입, React 컴포넌트의 자식(children)으로 들어갈 수 있는 다양한 타입들을 포함하는 유니온(Union) 타입
- React.FC에서 children 속성은 이러한 ReactNode 타입을 사용하여 자식 컴포넌트를 받아올 수 있도록 선언됨
- useState로 상태 설정 & useCallback 메모이제이션으로 불필요한 렌더링 방지
- 기본값 false 즉 light mode
- useCallback 훅 사용하여 toggleDarkMode함수 memoization → darkMode의 상태가 변경되지 않는 한 해당함수는 다시 렌더링 되지 않는다!
- useEffect 훅은 localStorage에서 사용자의 선호 모드를 읽어와 darkMode 상태를 초기화하는 데 사용
- localStorage.getItem('darkMode')를 사용하여 다크 모드 환경 설정을 검색하고, 이 값이 'true'로 설정되어 있으면, darkMode 상태를 해당 값으로 업데이트
- 다른 클라우드 스토리지에서 적용 해보기
- 추가 useEffect: 여기서의 useEffect 훅은 darkMode 상태가 변경될 때마다 localStorage에 darkMode 상태를 지속시킴.
- localStorage.setItem('darkMode', JSON.stringify(darkMode))를 사용하여 darkMode 상태를 문자열로 localStorage에 저장
- *JSON.stringify: 문자열로 직렬. why? localStorage에는 문자열 형태로만 데이터를 저장할 수 있기 때문에, JavaScript 객체나 배열과 같은 복잡한 데이터를 저장하려면 이를 문자열로 변환해야 함!!!
*만약 localStorage에서 다시 가져와서 JavaScript 객체로 변환하려면 JSON.parse; JSON 형식의 문자열을 JavaScript 객체로 파싱(parse)하는 메서드
- ThemeProvider 컴포넌트는 ThemeContext.Provider를 반환.
이를 통해 모든 하위 컴포넌트가 ThemeProvider에서 제공하는 테마에 접근 가능- styled-components에서 제공하는 StyledThemeProvider는 테마를 컴포넌트 트리 내의 모든 styled 컴포넌트에 제공
- StyledThemeProvider 내부에서 렌더링되어 ThemeProvider로 감싼 모든 컴포넌트에서 테마에 접근
// src/ThemeProvider.tsx
import React, { useState, useEffect, ReactNode, useCallback } from 'react';
import { ThemeProvider as StyledThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './theme';
interface ThemeContextType {
darkMode: boolean;
toggleDarkMode: () => void;
}
// 1. React.createContext : ~
export const ThemeContext = React.createContext<ThemeContextType>({
darkMode: false,
toggleDarkMode: () => {},
});
// 2. ThemeProvider 정의, ~
interface ThemeProviderProps {
children: ReactNode;
}
// 3. React.FC에서 children 속성 ~
// 4. useState로 상태 설정, useCallback 메모이제이션으로 불필요한 렌더링 방지
const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
const [darkMode, setDarkMode] = useState(false); // 기본값 false, 즉 light mode
const toggleDarkMode = useCallback(() => {
setDarkMode((prevDarkMode) => !prevDarkMode);
}, []);
// 5. useEffect 훅은 localStorage에서 사용자의 선호 모드를 읽어와 darkMode 상태를 초기화하는 데 사용
useEffect(() => {
const isDarkModePreferred = localStorage.getItem('darkMode') === 'true';
setDarkMode(isDarkModePreferred);
}, []);
// 다른 스토리지에서 불러올 때 이부분 변경해서 적용시켜보자
// 6. 여기서의 useEffect 훅은 darkMode 상태가 변경될 때마다 localStorage에 darkMode 상태를 지속시킴.
useEffect(() => {
localStorage.setItem('darkMode', JSON.stringify(darkMode));
}, [darkMode]);
return (
// 7. ThemeProvider 컴포넌트는 ThemeContext.Provider를 반환 ~
<ThemeContext.Provider value={{ darkMode, toggleDarkMode }}>
<StyledThemeProvider theme={darkMode ? darkTheme : lightTheme}>
{children}
</StyledThemeProvider>
</ThemeContext.Provider>
);
};
export default ThemeProvider;
5. index.tsx 에서 ThemeProvider 컴포넌트로 루트 컴포넌트 감싸주기
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import ThemeProvider from './ThemeProvider';
ReactDOM.render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>,
document.getElementById('root')
);
6. components 폴더 안에 DarkModeToggle 컴포넌트 생성 다크모트토글 기능 만들기
// src/components/DarkModeToggle.tsx
import React, { useContext } from 'react';
import { ThemeContext } from '../ThemeProvider';
const DarkModeToggle: React.FC = () => {
const { darkMode, toggleDarkMode } = useContext(ThemeContext);
// ThemeContext를 import하고 useContext 훅을 사용하여 ThemeProvider에서 darkMode 상태와 toggleDarkMode 함수에 접근
return (
<label>
<input
type="checkbox"
checked={darkMode}
// checked 속성은 darkMode 상태의 값으로 설정되므로, 다크 모드가 활성화되면 체크되고 비활성화되면 해제
onChange={() => toggleDarkMode()}
// onChange 이벤트를 처리하기 위해 toggleDarkMode 함수를 사용하고 다크 모드 상태를 토글
/>
Toggle Dark Mode
</label>
);
};
export default DarkModeToggle;
7. App.tsx 에서 ThemeProvider 컴포넌트와 DarkModeToggle 컴포넌트 적용 해주기
이 단계에서 ThemeProvider 컴포넌트와 DarkModeToggle 컴포넌트를 사용하여 메인 앱 컴포넌트인 App에 적용하는데 styled-components에서 제공하는 styled 함수를 사용하여 Container 스타일드 컴포넌트를 만든다.
이 컨테이너는 ThemeProvider에서 제공하는 테마의 배경색과 텍스트 색을 사용하게 된다.
// src/App.tsx
import React from 'react';
import DarkModeToggle from './components/DarkModeToggle';
import styled from 'styled-components';
const Container = styled.div`
background-color: ${(props) => props.theme.colors.background};
color: ${(props) => props.theme.colors.text};
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
`;
const App: React.FC = () => {
return (
<Container>
<h1>Hello, Dark Mode!</h1>
<DarkModeToggle />
</Container>
);
};
export default App;
https://github.com/hellokeitha/darkmode.git
GitHub - hellokeitha/darkmode: implement dark mode in a React TS application using styled Components and ThemeProvider
implement dark mode in a React TS application using styled Components and ThemeProvider - GitHub - hellokeitha/darkmode: implement dark mode in a React TS application using styled Components and Th...
github.com
'What I Learnd > WIL' 카테고리의 다른 글
WIL - Supabase 사용해서 개발자 스터디 커뮤니티웹 만들기 (0) | 2023.08.14 |
---|---|
WIL - map undefined/null cases (0) | 2023.07.24 |
WIL - firebase를 사용한 뉴스피드 프로젝트 (0) | 2023.07.03 |
WIL - 투두 리스트 리덕스 이해하기/고유값 패키지 라이브러리 UUID, shortID/Redux DevTools (0) | 2023.06.28 |
WIL - useState 사용해서 To do list 만들기 (0) | 2023.06.20 |