Styled-Components 丝滑实现 React 主题切换

1,832 阅读3分钟

是什么

CSS-in-JS是一种技术(technique),而不是一个具体的库实现(library)。简单来说CSS-in-JS就是将应用的CSS样式写在JavaScript文件里面,而不是独立为一些.css.scss或者less之类的文件,这样你就可以在CSS中使用一些属于JavaScript的诸如模块声明,变量定义,函数调用和条件判断等语言特性来提供灵活的可扩展的样式定义。
本文利用styled-components丝滑实现主题切换,相比于scssless可读性更高。在React项目中非常推荐使用。

项目目录

  • src
    • store
      • user
        • index.ts
      • index.ts
    • styles
      • theme
        • dark.ts
        • light.ts
        • index.ts
      • GlobalStyle.ts
      • types.ts
    • views
      • home.tsx
    • App.tsx
    • styled.d.ts

@reduxjs/toolkit 实现 Store

选用@reduxjs/toolkit状态管理来开发storeRedux官方推荐使用该插件,它简化了大多数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;
    };
  }
}