web-dev-qa-db-ja.com

スタイル付きコンポーネントの動的テーマ

Reactアプリでスタイル付きコンポーネントを使用していて、動的テーマを使用したいと考えています。暗いテーマを使用する領域もあれば、ライトを使用する領域もあります。スタイル付きコンポーネントはそれらが使用されているコンポーネントの外部で宣言されている場合、テーマを動的に渡すにはどうすればよいですか?

11
James Woodley

それがまさにThemeProviderコンポーネントの目的です!

スタイル付きコンポーネントは、関数を補間するときに特別なthemeプロパティにアクセスできます。

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

この <Button />コンポーネントは、ThemeProviderで定義されたテーマに動的に応答するようになりました。テーマをどのように定義しますか? themeThemeProviderプロパティにオブジェクトを渡します:

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でラップでき、スタイルを設定したすべてのコンポーネントがそのテーマを取得します。その1つのプロパティを動的に交換して、明るいテーマと暗いテーマを切り替えることができます。

アプリに含めることができるThemeProvidersは、必要に応じて少なくすることも、増やすこともできます。ほとんどのアプリは、アプリ全体をラップするために1つだけ必要ですが、アプリの一部を明るいテーマにして、他の一部を暗いテーマにするには、異なるテーマを持つ2つのThemeProvidersにラップするだけです。

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

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

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

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

Main内の任意のスタイル付きコンポーネントはライトテーマになり、Sidebar内の任意のスタイル付きコンポーネントはダークテーマになります。それらは、レンダリングされるアプリケーションの領域に応じて適応し、それを実現するために何もする必要はありません! ????

テーマに関するドキュメント を確認することをお勧めします。styled-componentsはそれを念頭に置いて非常に構築されているためです。

Styles-componentsが存在する前のJSのスタイルの大きな問題点の1つは、以前のライブラリがスタイルのカプセル化とコロケーションを非常にうまく行ったが、適切なテーマ設定サポートがなかったことです。既存のライブラリで発生したその他の問題点について詳しく知りたい場合は、スタイル付きコンポーネントをリリースした ReactNLでの私の講演 をご覧になることをお勧めします。 (注:styled-componentsの最初の表示は25分以内です。驚かないでください!)

38
mxstbr

この質問はもともと同時に複数のテーマを同時に実行するためのものでしたが、私は個人的にランタイムで動的に切り替える 1つsingle themeアプリ全体。

これが私がそれを達成した方法です:(ここでは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を渡します(状態を持ち上げます):

// app.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);
9
Jose A