본문 바로가기

What I Learnd/WIL

WIL - Styled Components와 ThemeProvider 사용해서 리액트에서 Darkmode 구현하기

기존 진행했던 투두리스트 프로젝트를 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 컴포넌트 만들기

  1. React.createContext: Context를 생성하는 함수 → 전역적으로 데이터를 공유하고 전달하는 메커니즘으로, 컴포넌트간의 명시적인 props 전달없이도 데이터를 공유, 컴포넌트 계층 구조의 깊은 곳에 있는 컴포넌트에서도 데이터에 접근할 수 있음!
    • 기본 값을 가진 컨텍스트 객체 생성, 현재 다크모드와 다크모드를 토글하는 함수를 기본 제공하고 있음
    • toggleDarkMode: () => {} : 빈 함수라는 뜻!아직은 아무 작업도 수행하지 않지만, 다크 모드를 토글하는 기능에 사용! 현재 다크모드 상태를 확인하여 상태를 반대로 변경하는 역할! → ThemeProvider 컴포넌트에서 적절하게 구현 필요
  2. ThemeProvider 정의, children prop을 받는 함수형 컴포넌트(FC, Functional Component)
    → 여기서 children은 ThemeProvider로 감싼 모든 컴포넌트 트리
    • ReactNode? → React에서 제공하는 타입, React 컴포넌트의 자식(children)으로 들어갈 수 있는 다양한 타입들을 포함하는 유니온(Union) 타입
  3. React.FC에서 children 속성은 이러한 ReactNode 타입을 사용하여 자식 컴포넌트를 받아올 수 있도록 선언됨
  4. useState로 상태 설정 & useCallback 메모이제이션으로 불필요한 렌더링 방지
    • 기본값 false 즉 light mode
    • useCallback 훅 사용하여 toggleDarkMode함수 memoization → darkMode의 상태가 변경되지 않는 한 해당함수는 다시 렌더링 되지 않는다!
  5. useEffect 훅은 localStorage에서 사용자의 선호 모드를 읽어와 darkMode 상태를 초기화하는 데 사용
    • localStorage.getItem('darkMode')를 사용하여 다크 모드 환경 설정을 검색하고, 이 값이 'true'로 설정되어 있으면, darkMode 상태를 해당 값으로 업데이트
    • 다른 클라우드 스토리지에서 적용 해보기
  6. 추가 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)하는 메서드
  7. 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