以正确的方式启动你的应用!包含 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 配置
我们希望代码简洁美观,因此我们将使用Prettier和eslint来确保代码规范。这些工具在之前的步骤中已经安装好了,现在我们需要对它们进行配置。
我们的 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"
}
}
}
更漂亮的(.prettierrc)是这样的:
{
"trailingComma": "es5",
"tabWidth": 2,
"singleQuote": true,
"printWidth": 140
}
这两个文件都需要在项目的根目录中创建。
故事书设置
幸运的是,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);
我们在这个文件中做了几件事:
- 现在用一个空对象进行设置
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,
};
断点
接下来是我们的断点。这些断点主要用于响应式字体,稍后我会演示它的工作原理。
export const breakpoints: string[] = ['319px', '424px', '767px', '1023px'];
颜色
这些颜色都是高度自定义的,您可以根据自己的喜好设置颜色。我只会告诉您如何设置它们。请注意,我使用的是 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',
};
最后,我们的 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,
},
};
一旦定义了这些,您就可以在这样的组件中使用它们:
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;
`;
结果是:
创建我们的动态样式组件
接下来,我们将使用 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}
`;
这意味着该组件现在拥有一系列可用于轻松设置样式的 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,
},
};
有了这些文本样式,我们就可以创建排版组件了。我们可以创建一个辅助函数 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,
};
现在我们可以开始使用 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;
`;
总结
这就是我为你的下一个 React 应用设置样式指南的一点心得。希望你喜欢这篇文章,期待下一篇!谢谢!
来源
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