Styled-Components 丝滑实现 React 主题切换
是什么
CSS-in-JS
是一种技术(technique
),而不是一个具体的库实现(library
)。简单来说CSS-in-JS
就是将应用的CSS
样式写在JavaScript
文件里面,而不是独立为一些.css
,.scss
或者less
之类的文件,这样你就可以在CSS
中使用一些属于JavaScript
的诸如模块声明,变量定义,函数调用和条件判断等语言特性来提供灵活的可扩展的样式定义。
本文利用styled-components
丝滑实现主题切换,相比于scss
、less
可读性更高。在React
项目中非常推荐使用。
项目目录
- src
- store
- user
- index.ts
- index.ts
- user
- styles
- theme
- dark.ts
- light.ts
- index.ts
- GlobalStyle.ts
- types.ts
- theme
- views
- home.tsx
- App.tsx
- styled.d.ts
- store
@reduxjs/toolkit 实现 Store
选用@reduxjs/toolkit
状态管理来开发store
,Redux
官方推荐使用该插件,它简化了大多数Redux
任务,防止了常见错误,并使编写Redux
应用程序更加容易。
首先创建user
模块,模块内容主要是主题切换的方法。
// store/user/index.ts 文件
import { createSlice } from "@reduxjs/toolkit";
import { Theme } from "src/styles/types";
export interface UserState {
theme: Theme;
}
const localTheme = localStorage.getItem("theme") as Theme;
const initialState: UserState = {
theme: localTheme ?? Theme.Light,
};
export const userSlice = createSlice({
name: "user",
initialState,
reducers: {
toggleTheme: (state: UserState) => {
state.theme = state.theme === Theme.Dark ? Theme.Light : Theme.Dark;
localStorage.setItem("theme", state.theme);
},
},
});
export const { toggleTheme } = userSlice.actions;
export default userSlice.reducer;
定义store
入口文件,将所有子store
导入进去,方便后续扩展。
// store/index.ts 文件
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counter";
export const store = configureStore({
reducer: {
user: userReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
定义 Styles 配置
创建styles
文件,分别定义白色和黑色主题相关配置,假设以后添加多个主题更方便,代码更直观。
// styles/theme/dark.ts 文件
import { DefaultTheme } from "styled-components";
const dark: DefaultTheme = {
colors: {
body: "#000", // body背景
},
};
export default dark;
// styles/theme/light.ts 文件
import { DefaultTheme } from "styled-components";
const light: DefaultTheme = {
colors: {
body: "#fff", // body背景
},
};
export default light;
styled-components
通过导出一个完整的主题支持<ThemeProvider>
包装组件。这个组件通过上下文API
为它自己下面的所有React
组件提供了一个主题。在渲染树中,所有样式组件都可以访问提供的主题,即使它们是多层次的。
// styles/index.tsx 文件
import { useSelector } from "react-redux";
import { ThemeProvider } from "styled-components";
import light from "./light";
import dark from "./dark";
import { Theme } from "../types";
import { RootState } from "src/store";
const ThemeProviderWrapper = (props: any) => {
const theme = useSelector((state: RootState) => state.user.theme);
return (
<ThemeProvider theme={theme === Theme.Dark ? dark : light} {...props} />
);
};
export default ThemeProviderWrapper;
createGlobalStyle
用于生成处理全局样式的特殊StyledComponent
的辅助函数。通常,样式化组件会自动限定为本地CSS
类,因此与其他组件隔离。 在createGlobalStyle
的情况下,这个限制被移除,并且可以应用诸如CSS
重置或基本样式表之类的东西。
// styles/GlobalStyle.ts 文件
import { createGlobalStyle } from "styled-components";
const GlobalStyle = createGlobalStyle`
body {
background: ${(props) => props.theme.colors.body}
}
`;
export default GlobalStyle;
// styles/types.ts 文件
export enum Theme {
Light = "light",
Dark = "dark",
}
如何使用
// views/home.tsx 文件
import { useState } from "react";
import { useDispatch } from "react-redux";
import { RootState } from "src/store";
import { toggleTheme } from "src/store/user";
const Home: React.FC<HomeProps> = () => {
const dispatch = useDispatch();
return (
<>
<h1>Home</h1>
<button onClick={() => dispatch(toggleTheme())}>主题切换</button>
</>
);
};
export default Home;
// App.tsx 文件
import { Routes, Route } from "react-router-dom";
import { Provider } from "react-redux";
import { store } from "./store";
import ThemeProviderWrapper from "./styles/theme";
import GlobalStyle from "./styles/GlobalStyle";
import Home from "./views/home";
const App = () => {
return (
<Provider store={store}>
<ThemeProviderWrapper>
<GlobalStyle />
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</ThemeProviderWrapper>
</Provider>
);
};
export default App;
styled.d.ts 全局配置
全局定义styled-components
配置类型
// styled.d.ts 文件
import "styled-components";
declare module "styled-components" {
export interface DefaultTheme {
colors: {
body: string;
};
}
}
转载自:https://juejin.cn/post/7102406632692252679