样式组件中的动态主题

IT技术 reactjs themes styled-components
2021-05-22 01:43:05

我在我的 React 应用程序中使用 styled-components 并想使用动态主题。有些区域会使用我的深色主题,有些会使用浅色。因为样式组件必须在使用它们的组件之外声明,我们如何动态传递主题?

3个回答

这正是ThemeProvider组件的用途!

你的样式组件theme在插入一个函数时可以访问一个特殊的prop:

const Button = styled.button`
  background: ${props => props.theme.primary};
`

<Button />组件现在将动态响应由ThemeProvider. 你如何定义一个主题?将任何对象传递给 的themepropsThemeProvider

const theme = {
  primary: 'palevioletred',
};

<ThemeProvider theme={theme}>
  <Button>I'm now palevioletred!</Button>
</ThemeProvider>

我们通过 为您的样式组件提供主题context,这意味着无论组件和 ThemeProvider 之间有多少个组件或 DOM 节点,它仍将完全相同:

const theme = {
  primary: 'palevioletred',
};

<ThemeProvider theme={theme}>
  <div>
    <SidebarContainer>
      <Sidebar>
        <Button>I'm still palevioletred!</Button>
      </Sidebar>
    </SidebarContainer>
  </div>
</ThemeProvider>

这意味着您可以将整个应用程序包装在一个 . 中ThemeProvider,并且您的所有样式组件都将获得该主题。您可以动态交换该属性以在浅色和深色主题之间切换!

您可以根据需要ThemeProvider在您的应用程序中使用尽可能少或尽可能多的s。大多数应用程序只需要一个来包装整个应用程序,但是要让您的应用程序的一部分是浅色主题而其他部分是深色主题,您只需将它们包装在两个ThemeProvider具有不同主题的 s 中:

const darkTheme = {
  primary: 'black',
};

const lightTheme = {
  primary: 'white',
};

<div>
  <ThemeProvider theme={lightTheme}>
    <Main />
  </ThemeProvider>

  <ThemeProvider theme={darkTheme}>
    <Sidebar />
  </ThemeProvider>
</div>

内部任何位置的任何样式组件Main现在将是浅色主题,而内部任何位置的任何样式组件都Sidebar将是深色主题。它们会根据呈现它们的应用程序的哪个区域进行调整,您无需执行任何操作即可实现!🎉

我鼓励你查看我们关于主题化文档,因为 styled-components 在很大程度上考虑到了这一点。

在 styled-components 出现之前,JS 中样式的一大痛点是之前的库对样式的封装和托管做得很好,但都没有适当的主题支持。如果您想了解更多关于我们现有库的其他痛点,我鼓励您观看我在 ReactNL上发布的样式组件的演讲(注意:styled-components 的第一次出现是在大约 25 分钟后,不要感到惊讶!)

虽然这个问题最初是为了让多个主题同时运行,但我个人希望在运行时为整个应用程序动态切换一个主题

这是我实现它的方法:(我将在这里使用 TypeScript 和钩子。对于纯 JavaScript,只需删除types as、 和interface):

为了以防万一,我还在每个块代码的顶部包含了所有导入。

我们定义我们的theme.ts文件

//theme.ts
import baseStyled, { ThemedStyledInterface } from 'styled-components';

export const lightTheme = {
  all: {
    borderRadius: '0.5rem',
  },
  main: {
    color: '#FAFAFA',
    textColor: '#212121',
    bodyColor: '#FFF',
  },
  secondary: {
    color: '#757575',
  },
};

// Force both themes to be consistent!
export const darkTheme: Theme = {
  // Make properties the same on both!
  all: { ...lightTheme.all },
  main: {
    color: '#212121',
    textColor: '#FAFAFA',
    bodyColor: '#424242',
  },
  secondary: {
    color: '#616161',
  },
};

export type Theme = typeof lightTheme;
export const styled = baseStyled as ThemedStyledInterface<Theme>;

然后在我们的主条目中,在这种情况下,App.tsx我们定义了<ThemeProvider>要使用主题的每个组件之前。

// app.tsx
import React, { memo, Suspense, lazy, useState } from 'react';
import { Router } from '@reach/router';

// The header component that switches the styles.
import Header from './components/header';
// Personal component
import { Loading } from './components';

import { ThemeProvider } from 'styled-components';

// Bring either the lightTheme, or darkTheme, whichever you want to make the default
import { lightTheme } from './components/styles/theme';

// Own code.
const Home = lazy(() => import('./views/home'));
const BestSeller = lazy(() => import('./views/best-seller'));

/**
 * Where the React APP main layout resides:
 */
function App() {
// Here we set the default theme of the app. In this case,
// we are setting the lightTheme. If you want the dark, import the `darkTheme` object.
  const [theme, setTheme] = useState(lightTheme);
  return (
    <Suspense fallback={<Loading />}>
      <ThemeProvider theme={theme}>
        <React.Fragment>
         {/* We pass the setTheme function (lift state up) to the Header */}
          <Header setTheme={setTheme} />
          <Router>
            <Home path="/" />
            <BestSeller path="/:listNameEncoded" />
          </Router>
        </React.Fragment>
      </ThemeProvider>
    </Suspense>
  );
}

export default memo(App);

在 header.tsx 中,我们将 setTheme 传递给组件(提升状态):

// header.tsx
import React, { memo, useState } from 'react';
import styled, { ThemedStyledInterface } from 'styled-components';
import { Theme, lightTheme, darkTheme } from '../styles/theme';

// We have nice autocomplete functionality 
const Nav = styled.nav`
  background-color: ${props => props.theme.colors.primary};
`;

// We define the props that will receive the setTheme
type HeaderProps = {
  setTheme: React.Dispatch<React.SetStateAction<Theme>>;
};

function Header(props: 
  function setLightTheme() {
    props.setTheme(lightTheme);
  }

  function setDarkTheme() {
    props.setTheme(darkTheme);
  }
// We then set the light or dark theme according to what we want.
  return (
    <Nav>
      <h1>Book App</h1>
      <button onClick={setLightTheme}>Light </button>
      <button onClick={setDarkTheme}> Dark </button>
    </Nav>
  );
}

export default memo(Header);

这是对我有用的东西:

import * as React from 'react';
import { connect } from 'react-redux';
import { getStateField } from 'app/redux/reducers/recordings';

import { lightTheme, darkTheme, ThemeProvider as SCThemeProvider } from 'app/utils/theme';
import { GlobalStyle } from 'app/utils/globalStyles';

interface ThemeProviderProps {
  children: JSX.Element;
  isLightMode?: boolean;
}

const ThemeProvider = ({ children, isLightMode }: ThemeProviderProps) => {
  return (
    <SCThemeProvider theme={isLightMode ? lightTheme : darkTheme}>
      <React.Fragment>
        {children}
        <GlobalStyle />
      </React.Fragment>
    </SCThemeProvider>
  );
};

export const ConnectedThemeProvider = connect((state) => ({
  isLightMode: getStateField('isLightMode', state)
}))(ThemeProvider);