样式化组件中的动态主题

Posted

技术标签:

【中文标题】样式化组件中的动态主题【英文标题】:Dynamic Theme in Styled Components 【发布时间】:2017-08-05 09:56:26 【问题描述】:

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

【问题讨论】:

【参考方案1】:

这正是ThemeProvider 组件的用途!

您的样式化组件在插入函数时可以访问特殊的 theme 属性:

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

这个<Button /> 组件现在将动态响应ThemeProvider 定义的主题。你如何定义一个主题?将任何对象传递给ThemeProvidertheme 属性:

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 中,并且您的所有样式组件都将获得该主题。您可以动态交换该属性以在浅色和深色主题之间切换!

您可以在您的应用中添加任意数量的ThemeProviders。大多数应用只需要一个来包装整个应用,但要让应用的一部分为浅色主题,另一部分为深色主题,您只需将它们包装在两个具有不同主题的ThemeProviders 中:

const darkTheme = 
  primary: 'black',
;

const lightTheme = 
  primary: 'white',
;

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

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

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

我鼓励您查看我们的 docs about theming,因为 styled-components 在构建时非常考虑到这一点。

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

【讨论】:

如果我有主题的 redux const initialState = theme: 'sky'; export default (state = initialState, action) => switch (action.type) default: return state; ;,如何注入主应用程序? @stackdave 如果您需要从应用程序中的任何位置访问它,您可以使用 redux 来存储主题(名称)。或者您可以将其用作***组件状态,如这个快速示例codesandbox.io/s/kl83ojzrr 如果您需要访问当前主题,您可以使用withTheme 函数styled-components.com/docs/api#withtheme【参考方案2】:

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

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

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

我们定义我们的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,我们在每个要使用主题的组件之前定义&lt;ThemeProvider&gt;

// 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);

【讨论】:

我想避免创建一个单独的问题,因为标题很好地抓住了答案。 您还可以创建一个包含 setLighTheme 和 setDarkTheme 函数的 Context,以便您可以在应用中的任何位置调用它们 在你上一个app.tsx 中,要获得主题道具的自动完成功能不应该使用从theme.ts 导出的styled 吗?所以在app.tsx 中,第三个导入将改为import styled, Theme, lightTheme, darkTheme from '../styles/theme';,然后应该删除第二个(import styled, ThemedStyledInterface from 'styled-components';)。至少这对我来说是这样的。【参考方案3】:

以下是对我有用的东西:

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);

【讨论】:

以上是关于样式化组件中的动态主题的主要内容,如果未能解决你的问题,请参考以下文章

使用酶和主题测试样式化组件

尝试使用主题测试样式化组件时出错

无法更改样式化组件主题提供程序

无法让 Jest 使用包含主题的样式化组件

在样式化组件中使用 Material-ui 主题

包括对带有 typescript 的样式化组件主题的媒体查询