React+Scss+Redux-tookit 实现优质的全局深色模式功能
一:下载相关依赖
下载RTK相关依赖
npm i react-redux @reduxjs/toolkit
下载SCSS相关依赖
npm i sass -D
二:创建一个 themeSlice 来管理主题
在 src/store/themeSlice.ts 文件中创建一个 slice,管理深色模式的状态。
// src/store/themeSlice.ts
// src/features/theme/themeSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// 定义初始状态
interface ThemeState {
isDarkMode: boolean;
}
const initialState: ThemeState = {
isDarkMode: false,
};
// 创建切片
const themeSlice = createSlice({
name: 'theme',
initialState,
reducers: {
toggleTheme(state) {
state.isDarkMode = !state.isDarkMode;
},
}
});
// 导出 reducer 和 actions
export const { toggleTheme } = themeSlice.actions;
export default themeSlice.reducer;
三:配置 Redux Store
然后,我们将 themeSlice 加入到 Redux store 中。在 src/store/index.ts 文件中配置 store。
// src/store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import themeReducer from './themeSlice';
const store = configureStore({
reducer: {
theme: themeReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
四:更新 index.tsx 文件以提供 Redux Store
确保在 src/index.tsx 中提供了 Redux store:
typescript
复制代码
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
五:在应用中使用 Redux 管理深色模式
在主应用文件(例如 src/App.tsx)中,使用 useSelector 和 useDispatch 来访问和更新主题状态,
// src/App.tsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { RootState, AppDispatch } from './store';
import { toggleTheme } from './store/themeSlice';
import './App.scss';
const App: React.FC = () => {
const dispatch: AppDispatch = useDispatch();
const isDarkMode = useSelector((state: RootState) => state.theme.isDarkMode);
// 切换模式的处理函数
const handleToggleTheme = () => {
dispatch(toggleTheme());
};
return (
<div className={isDarkMode ? 'dark-mode' : 'light-mode'}>
<h1>{isDarkMode ? 'Dark Mode' : 'Light Mode'}</h1>
<button onClick={handleToggleTheme}>
{isDarkMode ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
</button>
</div>
);
};
export default App;
六:应用深色模式样式
在 App.scss 文件中,使用 dark-mode 和 light-mode 类来切换样式。
// App.scss
@import './setting/variables.scss';
// 默认样式(浅色模式)
body {
background-color: $global-background-color;
color: $global-font-color;
}
// 深色模式样式
.dark-mode {
background-color: $background-color-dark;
color: $text-color-dark;
}
// 给button也设置深色模式,如有其他需求还可以自己去弄更多样式
.dark-mode button {
background-color: $primary-dark-dark;
color: $white-dark;
}
// 浅色模式样式
.light-mode {
background-color: $global-background-color;
color: $global-font-color;
}
.light-mode button {
background-color: $primary-color;
color: $white;
}
这时,已经实现基本的深色模式切换了!
七:深色模式持久化和跟随系统功能
如果你希望应用在页面刷新后保留深色模式的设置,可以使用 localStorage 或 sessionStorage 来持久化状态。在 themeSlice.ts 中添加持久化功能:
-
系统初始设置: 在应用首次加载时监听读取
window.matchMedia的值,设置初始主题,跟随系统切换。 -
手动切换: 只有用户手动点击切换按钮时才将状态存入
localStorage,并覆盖系统默认的设置。且跟随系统切换功能不生效
// src/store/themeSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// 读取localStorage中的主题设置
const savedTheme = localStorage.getItem('isDarkMode');
interface ThemeState {
isDarkMode: boolean;
isSystemPreference: boolean;
}
const initialState: ThemeState = {
isDarkMode:savedTheme === 'true',
isSystemPreference: !savedTheme, // 没有用户设置时,跟随系统
};
const themeSlice = createSlice({
name: 'theme',
initialState,
reducers: {
toggleTheme(state) {
state.isDarkMode = !state.isDarkMode;
state.isSystemPreference = false;
localStorage.setItem('isDarkMode', state.isDarkMode.toString());
},
setBySystemTheme(state,actions:PayloadAction<boolean>) {
if (state.isSystemPreference) {
const systemPrefersDark = actions.payload;
state.isDarkMode = systemPrefersDark;
}
}
}
});
export const { toggleTheme, setBySystemTheme } = themeSlice.actions;
export default themeSlice.reducer;
// src/App.ts
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { RootState, AppDispatch } from './store';
import { setBySystemTheme, toggleTheme } from './store/themeSlice';
import './App.scss';
import ThemeDiv from './themeComponent/themeDiv';
const App: React.FC = () => {
const dispatch: AppDispatch = useDispatch();
const isDarkMode = useSelector((state: RootState) => state.theme.isDarkMode);
// 监听系统的颜色方案,设置初始模式
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleSystemThemeChange = () => {
dispatch(setBySystemTheme(mediaQuery.matches));
};
// 初始检查和监听系统主题变化
dispatch(setBySystemTheme(mediaQuery.matches));
mediaQuery.addEventListener('change', handleSystemThemeChange);
return () => {
mediaQuery.removeEventListener('change', handleSystemThemeChange);
};
}, [dispatch]);
useEffect(() => {
if (isDarkMode) {
document.body.classList.add('dark-mode');
document.body.classList.remove('light-mode');
} else {
document.body.classList.add('light-mode');
document.body.classList.remove('dark-mode');
}
}, [isDarkMode]);
// 切换模式的处理函数
const handleToggleTheme = () => {
dispatch(toggleTheme());
};
return (
<ThemeDiv>
<h1>{isDarkMode ? 'Dark Mode' : 'Light Mode'}</h1>
<button onClick={handleToggleTheme}>
{isDarkMode ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
</button>
</ThemeDiv>
);
};
export default App;
八:添加过渡效果
如果你希望增加一个优雅的过渡效果,那只需要在App.scss中 .dark-mode .light-mode 绑定 transition
.dark-mode , .light-mode{
transition: background-color 0.3s ease, color 0.3s ease;
}
九:封装成 themeDiv 组件,再次降低代码耦合
1.在src目录下创建themeComponent文件夹,用于放置会被深色模式影响的组件
我们这里示范一个themeView组件,创建themeView.tsx文件,原理就是从redux中取到是否为深色模式,然后children获取父组件传来的ReactDom
// src/themeComponent/themeView.tsx
import React, { CSSProperties, ReactNode } from 'react';
import { useSelector } from 'react-redux';
import { RootState } from '../store';
interface ThemeViewProps {
children: ReactNode;
style?:CSSProperties
}
const ThemeView: React.FC<ThemeViewProps> = ({ style,children }) => {
const isDarkMode = useSelector((state: RootState) => state.theme.isDarkMode);
return (
<div className={isDarkMode ? 'dark-mode' : 'light-mode'} style={style}>
{children}
</div>
);
};
export default ThemeView;
十:封装成为hook,降低App.tsx维护成本
// src/hooks/useTheme.ts
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { RootState, AppDispatch } from '../store';
import { setBySystemTheme, toggleTheme } from '../store/themeSlice';
const useTheme = () => {
const dispatch: AppDispatch = useDispatch();
const isDarkMode = useSelector((state: RootState) => state.theme.isDarkMode);
// 监听系统的颜色方案,设置初始模式
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleSystemThemeChange = () => {
dispatch(setBySystemTheme(mediaQuery.matches));
};
// 初始检查和监听系统主题变化
dispatch(setBySystemTheme(mediaQuery.matches));
mediaQuery.addEventListener('change', handleSystemThemeChange);
return () => {
mediaQuery.removeEventListener('change', handleSystemThemeChange);
};
}, [dispatch]);
// 设置页面背景色和字体颜色
useEffect(() => {
if (isDarkMode) {
document.body.classList.add('dark-mode');
document.body.classList.remove('light-mode');
} else {
document.body.classList.add('light-mode');
document.body.classList.remove('dark-mode');
}
}, [isDarkMode]);
// 切换模式的处理函数
const handleToggleTheme = () => {
dispatch(toggleTheme());
};
return { isDarkMode, handleToggleTheme };
};
export default useTheme;
// src/App.tsx
import React from 'react';
import ThemeDiv from './themeComponent/themeDiv';
import useTheme from './hook/useTheme';
import './App.scss';
const App: React.FC = () => {
const { isDarkMode, handleToggleTheme } = useTheme(); // 使用自定义 hook
return (
<ThemeDiv>
<h1>{isDarkMode ? 'Dark Mode' : 'Light Mode'}</h1>
<button onClick={handleToggleTheme}>
{isDarkMode ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
</button>
</ThemeDiv>
);
};
export default App;
总结
React 应用结合了 RTK 来管理深色模式的切换。通过 useDispatch 和 useSelector,你可以在应用中动态地切换并应用深色模式。在variables.scss定义全局变量,在 App.scss 中通过 dark-mode 和 light-mode 类来切换对应的样式。同时,使用 localStorage 保持主题状态跨页面刷新,以及通过监听 window.matchMedia('(prefers-color-scheme: dark)');实现跟随系统设置深色主题,最后封装成为hook降低App.tsx的复杂度