iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0
Modern Web

React 學得動嗎系列 第 8

[Day 8] React Context:優雅地管理全局狀態

  • 分享至 

  • xImage
  •  

歡迎來到我們React學習之旅的第八天!今天,我們來認識一下React的Context API。這是一個強大的特性,允許我們在不同層級的組件之間共享資料,而無需顯式地通過props傳遞。

什麼是React Context?

Context就像是一個大家庭或公司裡的公共佈告欄。它讓我們可以在React應用的不同部分之間分享資訊,而不需要一層一層地傳遞。想像一下,如果沒有這個佈告欄,每次要通知所有人一件事,就得逐一告訴每個人,然後讓他們再告訴其他人。
Context的設計目的是為了分享那些在應用中很多地方都需要用到的「全域」資訊。
例如:

  • 現在登入的使用者是誰
  • 網站是使用深色還是淺色模式
  • 使用者選擇的語言是中文還是英文

有了Context,這些資訊就像貼在佈告欄上一樣,所有的組件都可以直接看到,不用再辛苦地一個一個傳遞了。這樣不僅讓程式碼更簡潔,也讓管理這些全域資訊變得更容易。

為什麼使用Context?

  1. 避免Prop Drilling: 當需要將props傳遞到多個層級的子組件時,Context可以簡化這個過程。
  2. 管理全局狀態: 對於需要在多個組件中共享的資訊,Context提供了一種集中管理的方式。
  3. 提高組件的覆用性: 通過Context,我們可以讓組件不依賴於特定的資料結構。

建立和使用Context

讓我們通過一個主題切換的範例來了解如何建立和使用Context。

首先,建立一個新文件 ThemeContext.tsx:

import React, { createContext, useState, useContext } from 'react';

type Theme = 'light' | 'dark';

interface ThemeContextType {
  theme: Theme;
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [theme, setTheme] = useState<Theme>('light');

  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
};

這個程式做了以下幾件事:

  1. 建立了一個ThemeContext
  2. 定義了一個ThemeProvider組件,它管理主題狀態並提供切換主題的方法。
  3. 建立了一個自定義Hook useTheme,用於在其他組件中方便地訪問主題狀態和切換方法。

現在,讓我們在專案中使用這個Context:

// App.tsx
import React from 'react';
import { ThemeProvider } from './ThemeContext';
import ThemedButton from './ThemedButton';

const App: React.FC = () => {
  return (
    <ThemeProvider>
      <div className="p-4">
        <h1 className="text-2xl font-bold mb-4">主題切換示例</h1>
        <ThemedButton />
      </div>
    </ThemeProvider>
  );
};

export default App;
// ThemedButton.tsx
import React from 'react';
import { useTheme } from './ThemeContext';
import { Button } from "@/components/ui/button"

const ThemedButton: React.FC = () => {
  const { theme, toggleTheme } = useTheme();

  return (
    <Button 
      onClick={toggleTheme}
      className={`${theme === 'dark' ? 'bg-gray-800 text-white' : 'bg-gray-200 text-black'}`}
    >
      切換主題 (當前: {theme})
    </Button>
  );
};

export default ThemedButton;

https://ithelp.ithome.com.tw/upload/images/20240922/20140358wbvJP2NaSC.png

https://ithelp.ithome.com.tw/upload/images/20240922/20140358tTYBQ3Qf2Z.png

在這個例子中:

  1. App組件使用ThemeProvider包裝了整個應用。
  2. ThemedButton組件使用useTheme Hook來訪問當前主題和切換主題的方法。
  3. 按鈕的樣式根據當前主題動態變化。

Context的最佳實踐

  1. 適度使用: Context主要用於共享全局數據。對於只需要在少數組件間共享的資料,props可能是更好的選擇。
  2. 分離關注點: 為不同類型的全局狀態建立不同的Context。
  3. 結合useReducer: 對於複雜的狀態邏輯,可以將Context與useReducer結合使用。
  4. 性能考慮: 過度使用Context可能導致不必要的重渲染。確保只在需要的組件中使用Context。

結合自定義Hooks和Context

我們可以進一步優化我們的程式,建立一個更強大的自定義Hook來管理主題:

// useThemeManager.ts
import { useState, useCallback, useEffect } from 'react';

export const useThemeManager = () => {
  const [theme, setTheme] = useState<'light' | 'dark'>(() => {
    // 從本地存儲中獲取主題,如果沒有則默認為'light'
    return (localStorage.getItem('theme') as 'light' | 'dark') || 'light';
  });

  const toggleTheme = useCallback(() => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  }, []);

  useEffect(() => {
    // 當主題變化時,更新本地存儲和文檔類
    localStorage.setItem('theme', theme);
    document.documentElement.classList.toggle('dark', theme === 'dark');
  }, [theme]);

  return { theme, toggleTheme };
};

然後,我們可以在我們的ThemeProvider中使用這個Hook:

export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const themeManager = useThemeManager();

  return (
    <ThemeContext.Provider value={themeManager}>
      {children}
    </ThemeContext.Provider>
  );
};

https://ithelp.ithome.com.tw/upload/images/20240922/20140358XBsPJyTov1.png

https://ithelp.ithome.com.tw/upload/images/20240922/201403584IIHeAmxxO.png

這種方法將主題管理邏輯封裝在一個自定義Hook中,使得我們的Context Provider更加簡潔,同時也使得主題管理邏輯可以在其他地方覆用。

小結

今天,我們認識了React的Context API,了解了如何建立和使用Context,以及如何將其與自定義Hooks結合使用。Context是一個強大的工具,可以幫助我們管理全局狀態並避免prop drilling

然而Context並不是用來取代所有的props傳遞。對於只需要在少數組件間共享的資料,props通常是更簡單和更直接的解決方案。Context最適合用於那些需要在專案的多個部分共享的全域資料。

如果想深入了解Context,可以查看React官方文件中關於Context的章節

明天,我們將學習React的錯誤邊界(Error Boundaries)。


上一篇
[Day 7] React自定義Hooks:解鎖組件邏輯的重用之門
下一篇
[Day 9] React 錯誤邊界:讓你的應用程式不再整個掛掉
系列文
React 學得動嗎13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言