以正确的方式启动你的应用!包含 React、styled-system、styled components 和 Typescript

2025-06-09

以正确的方式启动你的应用!包含 React、styled-system、styled components 和 Typescript

作为一款应用的作业,我需要实现更一致的排版/主题。保持应用的一致性至关重要,还能节省大量时间。因此,与其重构那些按钮来设置字体系列、边距或主色,不如从这里入手,无需再为此操心。关于如何处理这个问题的文章有很多,这也不是什么新鲜事,但写下来似乎是将所学付诸实践的绝佳练习。在阅读这些文章时,我偶然发现了styled-system,这是一个令人印象深刻的库,它可以轻松地为组件提供 props。这个库将成为本文的基础。文章列表将在文章末尾。

希望您喜欢这篇关于如何为您的应用设置样式指南基础的分步教程。我们将从零开始,从创建您的应用开始,直到在 Storybook 中展示样式指南。让我们开始吧!

如果您不想阅读整个设置过程,因为您已经做过一千次了,那么本文是这样写的:

本文的写作方式:

设置

创建应用程序并安装额外的软件包

创建 React 应用程序对于 React 应用程序来说是一个很好的开始,我们将使用Typescript标志来获取 Typescript 提供的所有优秀功能(尤其是那些 Typescript 3.7 功能)

npx create-react-app react-styleguide --typescript

然后我们将安装我们需要的其他软件包:

yarn add styled-components @types/styled-components styled-system @types/styled-system react-router-dom

接下来我们可以添加我们的开发依赖项:

yarn add -D prettier eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-prettier eslint-plugin-prettier

我们最后要安装的是 Storybook。Storybook 将用于创建我们主题的参考展示(字体、颜色、空间、断点)。

npx -p @storybook/cli sb init --type react

Storybook 是一款便捷的工具,可以用来创建组件原型。不久前,我写了一篇关于 Storybook 驱动开发的文章,其中也介绍了 Storybook 的基础知识。您可以在这里阅读。

Prettier 和 Eslint 配置

我们希望代码简洁美观,因此我们将使用Prettiereslint来确保代码规范。这些工具在之前的步骤中已经安装好了,现在我们需要对它们进行配置。

我们的 eslint(.eslintrc)设置看起来像这样:

{
      "parser": "@typescript-eslint/parser",
      "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended",
        "prettier/@typescript-eslint",
        "plugin:prettier/recommended"
      ],
      "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module",
        "ecmaFeatures": {
          "jsx": true
        }
      },
      "rules": {
        "prettier/prettier": [
          "error",
          {
            "singleQuote": true
          }
        ],
        "react/prop-types": "off",
        "@typescript-eslint/no-explicit-any": "error"
      },
      "settings": {
        "react": {
          "version": "detect"
        }
      }
    }
Enter fullscreen mode Exit fullscreen mode

更漂亮的(.prettierrc)是这样的:

{
  "trailingComma": "es5",
  "tabWidth": 2,
  "singleQuote": true,
  "printWidth": 140
}
Enter fullscreen mode Exit fullscreen mode

这两个文件都需要在项目的根目录中创建。

故事书设置

幸运的是,Storybook 现在可以直接使用 Typescript。我们只需要设置一些内容以备将来使用。我们将设置 ThemeProvider 和 Router(用于 Links)。您可以将下面的代码复制粘贴到 .storybook 文件夹下的 config.js 中。

import { addDecorator, configure } from '@storybook/react';
import { ThemeProvider } from 'styled-components';
import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';

const history = createBrowserHistory();

addDecorator(story => (
  <ThemeProvider theme={{}}>
    <Router history={history}>{story()}</Router>
  </ThemeProvider>
));

configure(require.context('../src', true, /\.stories\.tsx$/), module);
Enter fullscreen mode Exit fullscreen mode

我们在这个文件中做了几件事:

  • 现在用一个空对象进行设置ThemeProvider,因为我们还没有主题
  • 设置我们的Router,这样我们的Link组件就不会在故事书中中断
  • 改变context功能,以便它能拾取我们的.tsx文件。
  • 更改我们的功能搜索故事的文件夹context(我喜欢将我的故事与组件放在一起)

接下来,我们可以将 Storybook 提供的演示故事的扩展名 (0-Welcome.stories.js) 从js更改为tsx,并将其移动到我们的src文件夹中,看看一切是否正常。尝试运行yarn storybook,如果看到下面的屏幕,则表示 Storybook 设置已完成。干得好!🔥

故事书运行

如果你没看到这个,别担心,我懂了。你可以直接克隆我创建的这个分支作为检查点分支,这样你就可以继续学习本教程了。

图像的替代文本

现在我们已经准备好开始行动了,让我们从主题结构开始吧!

制作我们的主题结构

主题是应用的骨架,我们在这里定义我们的标准(大部分标准)以及如何使用它们。我们在很大程度上依赖于 styled-system 来实现这一点。我们需要在主题中定义以下几点:

  • 空间
  • 断点(用于响应式字体)
  • 颜色

首先,在 src 文件夹中创建一个名为 styleguide 的文件夹。在这个文件夹中,我们可以创建一个defaulTheme.ts。在这个文件中,我们将定义主题。该主题将采用 提供的类型的结构。您可以在此处styled-system阅读主题的规范。

在本文中,我们不会用到所有这些属性,因为这样工作量太大,而且读起来可能有点枯燥。因此,我们只设置空间、断点和颜色,以保持简洁。

空间

定义空间的目的是为了避免应用中像素分布不均。使用预定义的空间值,可以获得更可预测的结果。您可以随意设置,但我更喜欢本文中解释的几何级数T 恤尺码方法的结合。它看起来像这样:

export interface Space {
  NONE: number;
  XS: number;
  S: number;
  M: number;
  L: number;
  XL: number;
  XXL: number;
}

export const space: Space = {
  NONE: 0,
  XS: 2,
  S: 4,
  M: 8,
  L: 16,
  XL: 32,
  XXL: 64,
};

Enter fullscreen mode Exit fullscreen mode

断点

接下来是我们的断点。这些断点主要用于响应式字体,稍后我会演示它的工作原理。

export const breakpoints: string[] = ['319px', '424px', '767px', '1023px'];
Enter fullscreen mode Exit fullscreen mode

颜色

这些颜色都是高度自定义的,您可以根据自己的喜好设置颜色。我只会告诉您如何设置它们。请注意,我使用的是 styled-system 使用的 csstype。

import * as CSS from 'csstype';

export interface ThemeColors {
  primary: CSS.ColorProperty;
  link: CSS.ColorProperty;
  success: CSS.ColorProperty;
  warning: CSS.ColorProperty;
  error: CSS.ColorProperty;
  heading: CSS.ColorProperty;
  text: CSS.ColorProperty;
  disabled: CSS.ColorProperty;
  border: CSS.ColorProperty;
}

export const colors: ThemeColors = {
  primary: '#423EA2',
  link: '#1890ff',
  success: '#52c41a',
  warning: '#faad14',
  error: '#e84118',
  heading: '#423EA2',
  text: '#000',
  disabled: '#f5222d',
  border: '#423EA2',
};

Enter fullscreen mode Exit fullscreen mode

最后,我们的 defaultTheme.ts:

import { Theme } from 'styled-system';
import { colors } from './colors';
import { space } from './space';

export const breakpoints: string[] = ['319px', '424px', '767px', '1023px'];

export const defaultTheme: Theme = {
  space: {
    ...space,
  },
  breakpoints,
  colors: {
    ...colors,
  },
};

Enter fullscreen mode Exit fullscreen mode

一旦定义了这些,您就可以在这样的组件中使用它们:


const Button = styled.button`
  background-color: ${({ theme }) => theme.colors.primary};
  padding: ${({ theme }) => theme.space.M}px;
  margin: ${({ theme }) => theme.space.M}px;
  width: 200px;
  border: none;
  color: white;
  font-size: 14px;
  font-weight: 700;
  border-radius: 15px;
  letter-spacing: 2px;
  text-transform: uppercase;
`;
Enter fullscreen mode Exit fullscreen mode

结果是:

按钮

创建我们的动态样式组件

接下来,我们将使用 styled-system 内置的所有 props 来创建动态样式组件。这正是 styled-system 的亮点所在,因为我们只需要使用它们提供的样式函数。

import React from 'react';
import styled from 'styled-components';
import {
  borderRadius,
  BorderRadiusProps,
  color,
  fontFamily,
  FontFamilyProps,
  fontSize,
  FontSizeProps,
  fontStyle,
  FontStyleProps,
  fontWeight,
  FontWeightProps,
  letterSpacing,
  LetterSpacingProps,
  lineHeight,
  LineHeightProps,
  size,
  SizeProps,
  space,
  SpaceProps,
  textAlign,
  TextAlignProps,
  textStyle,
  TextStyleProps,
} from 'styled-system';

export type StyledSystemProps =
  | SpaceProps
  | FontSizeProps
  | FontStyleProps
  | SizeProps
  | TextStyleProps
  | LetterSpacingProps
  | FontFamilyProps
  | FontWeightProps
  | BorderRadiusProps
  | FontFamilyProps
  | LineHeightProps
  | TextAlignProps
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  | { color: string; as?: keyof JSX.IntrinsicElements | React.ComponentType<any> };

export default styled.div`
  ${space}
  ${fontSize}
  ${fontStyle}
  ${size}
  ${color}
  ${textStyle}
  ${letterSpacing}
  ${fontFamily}
  ${fontWeight}
  ${borderRadius}
  ${lineHeight}
  ${textAlign}
`;
Enter fullscreen mode Exit fullscreen mode

这意味着该组件现在拥有一系列可用于轻松设置样式的 props。这个动态组件将成为我们设置所有排版类型的基础。

设置所有排版样式和组件

现在我们可以开始创建文本样式了。我并非排版专家,但BBC的这些指南可以很好地帮助你了解排版的和谐。我们的文本样式如下所示:

import { colors } from './colors';
import { StyledSystemProps } from './DynamicStyledSystemComponent';

const fontFamilies: { heading: string; body: string } = {
  heading: 'Montserrat, serif',
  body: 'Raleway, sans-serif',
};

interface TypographyStyles {
  H1: StyledSystemProps;
  H2: StyledSystemProps;
  H3: StyledSystemProps;
  H4: StyledSystemProps;
  H5: StyledSystemProps;
  LargeLead: StyledSystemProps;
  SmallLead: StyledSystemProps;
  Paragraph: StyledSystemProps;
  SmallParagraph: StyledSystemProps;
  Link: StyledSystemProps;
}
export const typographyStyles: TypographyStyles = {
  H1: {
    fontSize: [50, 51, 52, 57],
    fontWeight: 700,
    fontFamily: fontFamilies.heading,
    as: 'h1',
  },
  H2: {
    fontSize: [37, 39, 41, 43],
    fontWeight: 700,
    color: colors.primary,
    fontFamily: fontFamilies.heading,
    as: 'h2',
  },
  H3: {
    fontSize: [27, 28, 30, 32],
    fontWeight: 700,
    fontFamily: fontFamilies.heading,
    as: 'h3',
  },
  H4: {
    fontSize: [18, 20, 22, 24],
    fontWeight: 700,
    color: colors.primary,
    fontFamily: fontFamilies.heading,
    as: 'h4',
  },
  H5: {
    fontWeight: 700,
    fontSize: [16, 17, 19, 21],
    fontFamily: fontFamilies.heading,
    as: 'h5',
  },
  LargeLead: {
    fontWeight: 500,
    fontSize: [18, 20, 22, 24],
    fontFamily: fontFamilies.heading,
    as: 'p',
  },
  SmallLead: {
    fontWeight: 500,
    fontSize: [17, 18, 19, 21],
    fontFamily: fontFamilies.heading,
    as: 'p',
  },
  Paragraph: {
    fontSize: [14, 15, 15, 16],
    fontWeight: 300,
    fontFamily: fontFamilies.body,
    as: 'p',
  },
  SmallParagraph: {
    fontSize: [13, 14, 14, 15],
    fontWeight: 300,
    fontFamily: fontFamilies.body,
    as: 'p',
  },
  Link: {
    fontWeight: 700,
    color: colors.primary,
    fontSize: [14, 15, 15, 16],
    fontFamily: fontFamilies.body,
  },
};
Enter fullscreen mode Exit fullscreen mode

有了这些文本样式,我们就可以创建排版组件了。我们可以创建一个辅助函数 createComponent,它包含两个参数:相应的样式属性及其显示名称。Link 组件不是使用 createComponent 函数创建的,因为它需要使用 react-dom Link 组件。

import React from 'react';
import { Link as RouterLink, LinkProps } from 'react-router-dom';
import DynamicStyledSystemComponent, { StyledSystemProps } from './DynamicStyledSystemComponent';
import { typographyStyles } from './typographyStyles';

type AnchorProps = StyledSystemProps & Pick<LinkProps, 'to'> & { onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void };

const Link: React.FC<AnchorProps> = ({ to, onClick, children, ...props }) => (
  <RouterLink to={to} onClick={onClick}>
    <DynamicStyledSystemComponent {...typographyStyles.Link} {...props}>
      {children}
    </DynamicStyledSystemComponent>
  </RouterLink>
);

interface TypographyComponentProps {
  H1: React.FC<StyledSystemProps>;
  H2: React.FC<StyledSystemProps>;
  H3: React.FC<StyledSystemProps>;
  H4: React.FC<StyledSystemProps>;
  H5: React.FC<StyledSystemProps>;
  LargeLead: React.FC<StyledSystemProps>;
  SmallLead: React.FC<StyledSystemProps>;
  Paragraph: React.FC<StyledSystemProps>;
  SmallParagraph: React.FC<StyledSystemProps>;
  Link: React.FC<AnchorProps>;
}

const createComponent: (textStyle: StyledSystemProps, displayName: string) => React.FC<StyledSystemProps> = (textStyle, displayName) => {
  const component: React.FC<StyledSystemProps> = props => (
    <DynamicStyledSystemComponent {...textStyle} {...props}>
      {props.children}
    </DynamicStyledSystemComponent>
  );
  component.displayName = displayName;
  return component;
};

export const Typography: TypographyComponentProps = {
  H1: createComponent(typographyStyles.H1, 'H1'),
  H2: createComponent(typographyStyles.H2, 'H2'),
  H3: createComponent(typographyStyles.H3, 'H3'),
  H4: createComponent(typographyStyles.H4, 'H4'),
  H5: createComponent(typographyStyles.H5, 'H5'),
  LargeLead: createComponent(typographyStyles.LargeLead, 'LargeLead'),
  SmallLead: createComponent(typographyStyles.SmallLead, 'SmallLead'),
  Paragraph: createComponent(typographyStyles.Paragraph, 'Paragraph'),
  SmallParagraph: createComponent(typographyStyles.SmallParagraph, 'SmallParagraph'),
  Link: Link,
};
Enter fullscreen mode Exit fullscreen mode

现在我们可以开始使用 Typography 组件了。接下来我会在 Storybook 中展示它。

参考故事书中的故事

我们可以通过制作一个故事书来展示我们的样式指南是如何设置的。如果你想知道要使用哪种字体、颜色和空间,这可以作为参考。

import React from 'react';
import { storiesOf } from '@storybook/react';
import { Typography } from './Typography';
import styled from 'styled-components';
import { colors } from './colors';
import { breakpoints } from './theme';
import { space } from './space';

const { H1, H2, H3, H4, H5, LargeLead, Link, Paragraph, SmallLead, SmallParagraph } = Typography;

storiesOf('Styleguide ', module)
  .addParameters({ viewport: { defaultViewport: 'default' } })
  .add('default', () => (
    <Container>
      <H2>Typography</H2>
      <Divider />
      <H1>H1: Animi aperiam, aspernatur culpa deserunt eaque, eius explicabo inventore ipsa laudantium</H1>
      <H2>H2: Consectetur consequuntur cum deserunt dignissimos esse fugiat inventore iusto, laboriosam maiores minima!.</H2>
      <H3>H3: Culpa dignissimos expedita facilis, fugiat minus odio reiciendis ut? Accusamus delectus dicta eius.</H3>
      <H4>H4: Accusamus ad adipisci alias aliquam aperiam autem, culpa dolorem enim error est eum.</H4>
      <H5>H5: Debitis distinctio dolorum fugiat impedit itaque necessitatibus, quo sunt? Atque consectetur, corporis.</H5>
      <LargeLead>LargeLead:Deleniti est facere id placeat provident sapiente totam vitae. Asperiores consequuntur eaque eum.</LargeLead>
      <SmallLead>SmallLead: At aut corporis culpa doloribus ea enim error est impedit, ipsum iure maxime molestiae omnis optio.</SmallLead>
      <Paragraph>
        Paragraph: Facilis hic iste perspiciatis qui quibusdam sint velit vero Animi doloremque esse ex iure perferendis.
      </Paragraph>
      <SmallParagraph>SmallParagraph: Ad animi at debitis eligendi explicabo facere illum inventore, ipsum minus obcaecati.</SmallParagraph>
      <Link to="/">Link: Lorem ipsum dolor sit amet, consectetur adipisicing elit.</Link>
      <Divider />
      <H2>Colors</H2>
      <Paragraph>These colors are defined in styleguide colors.ts.</Paragraph>
      <Divider />
      <GridContainer>
        <div>
          <SmallParagraph>Kind</SmallParagraph>
        </div>
        <div>
          <SmallParagraph>HEX</SmallParagraph>
        </div>
        <div>
          <SmallParagraph>Color</SmallParagraph>
        </div>
      </GridContainer>
      {Object.entries(colors).map(obj => (
        <GridContainer key={obj[0]}>
          <SmallParagraph>{obj[0]}</SmallParagraph>
          <SmallParagraph>{obj[1]}</SmallParagraph>
          <ColorCircle color={obj[1]} />
        </GridContainer>
      ))}
      <Divider />
      <H2>Breakpoints</H2>
      <Paragraph>These are the responsive breakpoints being used</Paragraph>
      <br />
      <FlexContainer>
        {breakpoints.map((key: string) => (
          <SmallParagraph key={key} m={4}>
            {key}
          </SmallParagraph>
        ))}
      </FlexContainer>
      <Divider />
      <H2>Space</H2>
      <FlexContainer>
        {Object.entries(space).map(obj => (
          <div key={obj[0]}>
            <SmallParagraph m={2}>
              <strong>{obj[1]}px</strong>
            </SmallParagraph>
            <SmallParagraph m={2}>{obj[0]}</SmallParagraph>
          </div>
        ))}
      </FlexContainer>
    </Container>
  ));

const Divider = styled.div`
  border: 1px solid #00000022;
  width: 100%;
  margin: ${({ theme }) => theme.space.M}px;
`;

const ColorCircle = styled.div<{ color: string }>`
  height: 20px;
  width: 20px;
  border-radius: 20px;
  background-color: ${({ color }) => color};
`;

const GridContainer = styled.div`
  display: grid;
  grid-template-columns: 150px 150px 150px;
  margin: ${({ theme }) => theme.space.M}px;
`;

const FlexContainer = styled.div`
  display: flex;
`;

const Container = styled.div`
  background-color: white;
  height: 100vh;
  padding: 16px;
`;
Enter fullscreen mode Exit fullscreen mode

总结

这就是我为你的下一个 React 应用设置样式指南的一点心得。希望你喜欢这篇文章,期待下一篇!谢谢!

Github 仓库

来源

https://levelup.gitconnected.com/building-a-react-typography-system-f9d1c8e16d55
https://www.bbc.co.uk/gel/guidelines/typography
https://sproutsocial.com/seeds/visual/typography/
https://medium.com/eightshapes-llc/space-in-design-systems-188bcbae0d62

链接:https://dev.to/jdcas89/start-your-app-the-right-way-featuring-react-styled-system-styled-components-and-typescript-7a4
PREV
Firestore 功能受限,那么... 2022 年的完美数据库是什么?
NEXT
使用 React 的无头 WordPress