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

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

【问题讨论】:

    标签: reactjs themes styled-components


    【解决方案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);
    

    【讨论】:

      猜你喜欢
      • 2019-09-12
      • 1970-01-01
      • 2019-12-12
      • 2018-05-28
      • 2010-10-17
      • 2021-07-17
      • 1970-01-01
      • 2019-02-05
      相关资源
      最近更新 更多