样式设计是构建网络和移动应用程序的一个重要部分。为了使一个应用程序看起来对你的用户有吸引力,它需要进行造型。CSS是我们最常使用的美化应用程序的方法。
但是,尽管我们很喜欢CSS,但也有一些恼人的问题需要处理,如类名重复、类名拼写错误和未知、不确定类名是否合适等等。在有大量代码库的项目中,使用普通的CSS可能会变得费时又费力。这就是风格化组件的用武之地。
在本教程中,我们将向你展示如何使用styled-components构建和设计一个TypeScript应用程序。
为了用一个实际的例子来证明,我们将使用styled-components和TypeScript来创建一个类似于Amazon的电子商务页面。完成后的产品将看起来像这样。

你可以在我的GitHub资源库中获取这个演示所需的图片。在本教程结束时,你应该能够使用styled-components设计你自己的TypeScript项目。
什么是styled-components?
styled-components是一个CSS-in-JS库,它使你能够编写常规的CSS并将其附加到JavaScript组件上。有了styled-components,你可以使用你已经熟悉的CSS,而不必学习新的样式结构。
styled-components让你可以对你的工作进行样式设计,而不必担心类名是否冲突,或者它们是否适合于特定的使用情况。它还有助于调试,因为一旦你发现哪个组件有问题,你就知道在哪里可以找到它的样式。
设置一个TypeScript项目
我们将使用create-react-app ,为我们的项目生成一个模板。由于这是一个TypeScript项目,我们将在我们的命令中加入--template typescript 。
打开你的终端,输入以下内容,然后按回车。
npx create-react-app --template typescript
创建你的项目后,通过键入以下内容进入项目目录。
cd amazon-clone
在你的终端键入code . ,在你的代码编辑器中打开该项目。另外,你也可以从文件管理器中打开它,方法是右键单击文件夹,选择用代码打开。
我们需要安装一些依赖项,因为styled-components并没有随create-react-app一起出现。
运行下面的命令:
yarn add styled-components
当它完成安装后,运行下面的命令:
yarn add -D @types/styled-components
这将为TypeScript安装styled-components类型,作为一个开发依赖。
我们还将使用Material Icons,因此让我们安装 [material-ui package](https://www.npmjs.com/package/@material-ui/core).输入以下内容并按回车键。
yarn add @material-ui/icons @material-ui/core
VS Code有一个styled-components扩展,它使我们看起来像在输入实际的CSS,尽管它是一个TypeScript文件。它被称为 [vs-code-styled-components](https://marketplace.visualstudio.com/items?itemName=jpoissonnier.vscode-styled-components)如果你还没有安装,我建议你在继续之前安装它。
你的文件夹现在应该是这样的:
现在是时候清理我们的项目了。我们不需要由Create React App生成的所有文件,所以让我们删除多余的文件:
- 在public文件夹中,删除logo文件
- 在
index.html文件中,把标题改成你想要的样子,并删除从元描述到标题标签开始的所有信息 - 在
src文件夹中,删除所有文件,除了App.tsx和index.tsx - 在
index.tsx,删除导入index.css和reportWebVitals的行。同时删除所有与webVitals有关的行 - 删除
App.tsx中的所有内容,除了我们导入React和导出应用的地方。
说完这些,让我们开始构建。
创建项目结构
首先,让我们计划一下我们页面的布局。
我们的页面由三个主要部分组成:导航栏、菜单栏和主体。我们将从顶部到底部开始。
在App.tsx ,创建一个React功能组件。它应该看起来像这样:
import React from 'react';
const App:React.FC = ()=> (
<> </>
)
export default App;
在你的src 文件夹中,创建一个style文件夹。你可以随心所欲地命名它;我的名字是styles 。
在style文件夹中,创建一个文件并命名为global.ts 。这个文件将包含我们的全局样式。全局样式是一个页面中所有元素都使用的样式。
在global.ts ,我们导入createGlobalStyle ,并创建全局样式。你的global.ts 文件应该看起来像这样。
import {createGlobalStyle} from "styled-components"
export default createGlobalStyle`
*{
margin: 0;
padding: 0;
outline:0;
box-sizing:border-box;
font-family: 'Open Sans', sans-serif;
}
#root{
margin:0 auto;
}
`
这些样式确保页面上没有边距或填充,除非在明确说明的地方。
在src 文件夹中,我们将为组件创建一个文件夹。在组件文件夹中,我们将再创建三个文件夹,为页面的每个部分创建一个。我们将为我们的资产再创建一个文件夹。所有的图片都应该存放在这个文件夹里。
在导航条文件夹中,创建两个文件:index.tsx 和styles.tsx 。在index.tsx ,导入以下项目。
import React from 'react'
import {
Container,
Logo,
Flag,
Text,
Wrapper,
Searchbox,
Select,
SearchIconWrapper
} from "./styles"
import { ShoppingCartOutlined, SearchOutlined, ArrowDropDown, RoomOutlined } from '@material-ui/icons';
import logo from "../assets/logo.png"
import flag from "../assets/flag.png"
导入React是很重要的,因为我们需要它来创建我们的函数。
在第二行,我们从样式文件中导入了一些组件(我们很快会看到它们的作用)。然后,我们从Material Icons导入我们的图标。最后,我们从assets文件夹中导入了我们的图片。
在index文件夹中,让我们创建一个功能组件,并把我们的容器组件放在其中。
import React from 'react';
const App:React.FC = ()=> (
<>
<Container></Container>
</>
)
export default App;
如果你不习惯使用风格化的组件,这可能看起来很奇怪。
容器组件代表了我们将使用的其他组件的整体包装。在不使用styled-components时,它的使用方式与div 。当我们开始进行样式设计时,你会了解到容器组件实际上是一个div 。
让我们看看样式文件是什么样子的。
import styled from "styled-components"
export const Container = styled.div`
display: flex;
justify-content:space-evenly;
align-items: center;
color: white;
background-color: #131A22;
`
在顶部,我们从styled-components 中导入了styled 。styled 让我们可以选择使用任何HTML元素,如div 、section 、span 、inputs 等。我们可以访问它们全部。
对于我们的容器组件,我们使用了一个div 。在这个元素的名字后面,我们添加了template literals ,作为组件样式的包装。然后我们在模板字面上写下我们的CSS样式。
注意到这看起来像是在写普通的CSS吗?这是因为我前面提到的扩展。它对颜色和自动完成有帮助。
这些组件是可重复使用的,它们使每个组件与它的样式相关联变得更容易。这是因为每个组件在其主体中已经有了它的样式,所以当你需要调试时,你马上就知道该在哪里检查。
帮助性组件
让我们再创建一个文件夹:一个帮助者文件夹。在helper文件夹中,我们将创建两个文件:PageText.tsx 和ItemWrapper.tsx 。
这个页面上有很多文字,有不同的大小和颜色。为了避免重复,我们需要创建TypeScript组件,并将一些CSS属性作为props传递。这将使我们能够重复使用这些组件,并根据我们的需要修改它们,而不需要在我们的样式文件中写新的代码行。
PageText 组件将用于页面上的所有文本。ItemWrapper 组件将作为一个容器来包装页面上的各种元素。
在PageText.tsx ,插入以下内容。
import React from 'react'
interface Props{
className?: string;
fontSize?: any;
color?: string;
}
export const PageText:React.FC <Props> = ({className, children}) => {
return (
<span className={className}>{children}</span>
)
}
注意到组件中传递的道具了吗?这些是使用该组件时将被接受的道具。如果你不包括className 道具,无论你试图在组件主体中声明什么样式,都不会生效。在使用它的时候,你可以删除className 道具,看看它抛出的错误。子项道具代表我们将在组件内编写或使用的任何项目。
在ItemWrapper.tsx ,插入以下几行代码。
import React from 'react'
interface Props{
className?: string;
display?: string;
flexDirection?: string;
alignItems?: string;
maxWidth?: string;
margin?: string;
}
export const PageItemWrapper:React.FC <Props> = ({className, children}) => {
return (
<div className={className}>{children}</div>
)
}
设计导航条的样式
现在我们已经完成了辅助工具,让我们来创建我们的导航条。示例页面的导航条以亚马逊的标志开始。我们已经在索引文件中导入了它,所以让我们为它创建一个组件,并对它进行样式设计。
import{ PageText as NavText} from "../helpers/PageText"
import{ PageItemWrapper as NavItemWrapper} from "../helpers/ItemWrapper"
export const Logo = styled.img`
width: 6em;
border:1px solid #131A22;
padding: .2em .1em;
cursor:pointer;
&:hover{
border:1px solid #ffffff;
border-radius: .2em;
}
`
我们为宽度、边框、填充和光标行为创建了样式。我们还为标志被悬停时添加了额外的样式。不像在普通的CSS中,我们会使用像a:hover ,这里我们使用& 符号来表示我们正在为元素被悬停时创建样式。
这个过程在为::before 、::after 和其他伪元素创建样式时也是如此。样式化组件最好的一点是,你可以在一个地方编写所有的样式。
现在我们已经完成了标志的设计,让我们继续讨论导航条的其他部分。
在styles.tsx ,添加以下内容:
import{ PageText as NavText} from "../helpers/PageText"
export const Text = styled(NavText)`
color:${(props)=>props.color ? props.color :"#ffffff" };
font-size: ${(props)=>props.fontSize ? props.fontSize : ".9em"
};
`
这里,我们以另一种方式使用了styled-components。我们把PageText 作为NavText ,从帮助文件夹中导入。在元素名称的位置上,我们使用了NavText 。
注意,当使用一个现有的组件时,我们没有包括一个点。我们没有写styled.NavText ,而是写了styled(NavText) 。我们还为我们的组件创建了默认样式。
组件内的这几行代码意味着,如果在使用该组件时指定了一个color 或font-size 的道具,那么color 和font-size 就等于指定的道具,否则,文本组件就使用其中的默认值。
import{ PageItemWrapper as NavItemWrapper} from "../helpers/ItemWrapper"
export const Wrapper = styled(NavItemWrapper)`
display: flex;
flex-direction: ${(props)=>props.flexDirection ? props.flexDirection : "column"};
align-items: ${(props)=>props.alignItems ? props.alignItems : "flex-start"};
padding: .1em;
cursor:pointer;
border:1px solid #131A22;
&:hover{
border:1px solid #ffffff;
border-radius: .2em;
}
@media(max-width:850px){
display: none;
}
`
现在让我们在我们的页面中使用它们。在index.tsx ,添加以下几行代码:
const Navbar:React.FC = () => {
return (
<>
<Container>
<Logo src={logo}/>
<Wrapper flexDirection="row"
alignItems="center">
<RoomOutlined/>
<Wrapper >
<Text fontSize=".7em">Deliver in
</Text>
<Text >Nigeria</Text>
</Wrapper>
</Wrapper>
{/* the search button */}
<Select>
<option value="All">All</option>
</Select>
<Searchbox/>
<SearchIconWrapper>
<SearchOutlined/>
</SearchIconWrapper>
{/* flag image */}
<Wrapper flexDirection="row"
alignItems="flex-start"
>
<Flag src={flag}/>
<ArrowDropDown/>
</Wrapper>
<Wrapper>
<Text fontSize=".7em">Hello, Sign in</Text>
<Wrapper flexDirection="row" alignItems="center">
<Text>Account & Lists </Text>
<ArrowDropDown/>
</Wrapper>
</Wrapper>
<Wrapper>
<Text fontSize=".7em">Returns</Text>
<Text >& Orders</Text>
</Wrapper>
<Wrapper flexDirection="row"
alignItems="center">
<Wrapper alignItems="center">
<Text color="#ff9900">0</Text>
<ShoppingCartOutlined/>
</Wrapper>
<Text>Cart</Text>
</Wrapper>
</Container>
</>
)
}
export default Navbar
这是很多代码,但不要被吓倒。让我们一起看一下。
我们使用了我们的Wrapper 和NavText 组件,你可以看到如何根据需要修改它们。我们还使用了我们在本教程开始时导入的图标。这里唯一的新东西是Select,Searchbox,SearchIconWrapper, 和Flag 组件。让我们解释一下它们是什么,以及我们如何创建它们。
在页面的导航栏中,有一个搜索框和它两边的两个元素。让我们来创建它们。
在styles.tsx :
export const Searchbox = styled.input`
background-color: #ffffff;
padding: .78em;
width: 47%;
border: none;
@media(max-width:850px){
border-radius: .2em;
margin: .3em 0;
}
`
export const Select = styled.select`
background-color:#ddd;
margin-right: -1.2em;
padding: .72em .5em;
border-radius: .2em 0em 0em .2em;
border: none;
cursor: pointer;
@media(max-width:850px){
display: none;
}
`
export const SearchIconWrapper = styled.span`
background-color:#fabd60;
color: #131A22;
margin-left: -1em;
border-radius: 0em .2em .2em 0em ;
padding: .32em .5em;
cursor: pointer;
transition: all 250ms ease;
&:hover{
background-color:#ff9900;
}
@media(max-width:850px){
display: none;
}
`
export const Flag = styled.img`
width:2em;
`
我们为Searchbox 组件使用了一个input 标签,为Select 组件使用了一个select 标签,为Flag 组件使用了一个img 标签,为SearchIconWrapper 使用了一个span 。我们还为SearchIconWrapper 添加了一个悬停效果,并为新组件添加了媒体查询。
这就是导航条的全部内容。现在你的页面应该看起来像这样。
现在,让我们来建立菜单栏。
构建菜单栏
在我们之前创建的Menubar 文件夹中,创建一个index.tsx 文件和一个styles.tsx 文件。
在index.tsx ,创建一个功能组件,并在其中添加以下一行:
import React from 'react'
import {Container, Wrapper, Text, LeftText} from "./styles"
import {Menu} from '@material-ui/icons';
const Menubar:React.FC = () => {
return (
<></>
)
}
export default Menubar
我们从styles.tsx 中导入了一些组件,从Material Icons中导入了菜单图标。就像在我们的Navbar 文件夹中一样,我们将从帮助文件夹中导入包装器和文本组件,并在我们的 styles.tsx 文件中使用它。
import styled from "styled-components"
import{ PageText as MenuText} from "../helpers/PageText"
import{ PageItemWrapper as MenuItemWrapper} from "../helpers/ItemWrapper"
export const Container = styled.div`
display: flex;
justify-content:space-between;
color: white;
padding: .3em;
background-color: #232f3e;
`
export const Text = styled(MenuText)`
color:${(props)=>props.color ? props.color :"#ffffff" };
font-size: ${(props)=>props.fontSize ? props.fontSize : ".9em"};
margin-right: 1em;
border:1px solid #232f3e;
padding: .5em .1em;
cursor:pointer;
&:hover{
border:1px solid #ffffff;
border-radius: .2em;
}
@media(max-width:850px){
display: none;
}
`
export const LeftText = styled(Text)`
@media(max-width:850px){
display: block;
}
`
export const Wrapper = styled(MenuItemWrapper)`
display: flex;
flex-direction: row;
align-items: center;
margin-right: 1em;
`
我们对我们的Container,Wrapper, 和Text 组件进行了样式设计。我们还使Text 组件的颜色和字体大小变得可定制。让我们在我们的index.tsx 文件中使用它们。
const Menubar:React.FC = () => {
return (
<Container>
<Wrapper>
<Wrapper>
<Menu/>
<Text>All</Text>
</Wrapper>
<Wrapper>
<Text>Today's Deals</Text>
<Text>Customer Service</Text>
<Text>Gift Cards</Text>
<Text>Sell</Text>
<Text>Registry</Text>
</Wrapper>
</Wrapper>
<Wrapper>
<LeftText>Amazon's response to COVID-19</LeftText>
</Wrapper>
</Container>
)
}
export default Menubar
现在让我们建立我们的页面主体。
铺设页面主体
在页面的左侧,有一个显示各种部门名称的下拉菜单。我们在页面上也有产品的详细信息。让我们在我们的PageBody 文件夹中创建一个新文件来存储这些信息。
在PageInfo.ts
import battery from "../assets/cell-battery.jpg"
import headset from "../assets/headset.jpg"
import screen from "../assets/screen-protector.jpg"
export const departmentList = [
"Electronics",
"Cell Phones & Accessories",
"Industrial & Scientific",
" Office Products",
" Video Games",
" Health, Household & Baby Care",
" Automotive Parts & Accessories",
" Clothing, Shoes & Jewelry",
"Sports & Outdoors",
" Tools & Home Improvement",
"Home & Kitchen",
"Musical Instruments",
"Pet Supplies",
"Appliances",
"Arts, Crafts & Sewing",
" Toys & Games",
" CDs & Vinyl"
]
export const productDetails=[
{
name:"Amazon Basics 4 Pack CR2032 3 Volt Lithium Coin Cell Battery",
by: "by Amazon Basics",
starcount: "59,531",
price: "",
shipping: "",
available: "Currently unavailable.",
src: battery
},
{
name: "Arae Screen Protector for iPhone 12 Pro Max, HD Tempered Glass Anti Scratch Work with Most Case, 6.7 inch, 3 Pack",
by: "by Arae",
starcount: "321",
price: "$6.99",
shipping: " + $17.27 shipping",
available: "Arrives: Thursday, May 20",
src: screen
},
{
name: "SENZER SG500 Surround Sound Pro Gaming Headset with Noise Cancelling Microphone - Detachable Memory Foam Ear Pads - Portable Foldable",
by: "by SENZER",
starcount: "1,613",
price: "$23.99 ",
shipping: " + $23.39 shipping",
available: "Arrives: Thursday, May 20",
src: headset
}
]
在PageInfo 文件中,我们有departmentList ,它是一个部门名称的数组,和productDetails ,一个包含产品信息的对象数组。在顶部,我们从assets文件夹中导入了产品图片。
让我们在PageBody 文件夹中创建一些新文件。创建一个index.tsx 文件和一个styles.tsx 文件。在index.tsx ,添加以下内容。
import React from 'react'
import {
Container,
LeftContainer,
RightContainer,
Wrapper,
Image,
Text,
Paragraph,
ProductContainer,
ImageContainer,
SearchResultDiv,
IconWrapper,
BoldText
} from "./styles"
import {KeyboardArrowLeft, Star, StarHalf, KeyboardArrowDown} from '@material-ui/icons';
import {departmentList, productDetails} from "./PageInfo"
我们从样式文件中导入我们的组件,从PageInfo.ts 中导入我们的图标和页面的信息。
现在让我们来创建我们的组件并为它们设计样式。
在你的 styles.tsx 文件中,添加以下几行代码。
import styled from "styled-components"
import {PageText} from "./PageText"
import {PageItemWrapper} from "./PageItemWrapper"
export const Container = styled.div`
display: flex;
padding: 1em;
`
export const LeftContainer = styled.aside`
height:80vh;
width: 18vw;
border-right: 2px solid #ddd;
@media(max-width:650px){
display: none
}
`
export const RightContainer = styled.section`
height:80vh;
width: 82vw;
display: flex;
flex-direction: column;
margin-left: 1.5em;
`
export const Image = styled.img`
width: 13em;
`
export const Text = styled(PageText)`
color:${(props)=>props.color ? props.color :"#131A22" };
font-size:${(props)=>props.fontSize ? props.fontSize :".9em" };
`
export const BoldText = styled(Text)`
font-weight: bold;
padding: .4em;
`
export const Paragraph = styled.p`
font-size:.9em;
display: flex;
align-items: center;
padding-bottom: .1em;
`
export const SearchResultDiv = styled.div`
border: 1px solid #ddd;
padding: .6em;
width: 95%;
border-radius: 4px;
`
export const ProductContainer = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, 22em);
margin-top: 2em;
@media(max-width:915px){
grid-template-columns: repeat(auto-fit, 15em);
align-items: center;
justify-content: center;
}
`
export const ImageContainer = styled.div`
height: 14em;
display: flex;
align-items: center;
`
export const Wrapper = styled(PageItemWrapper)`
display: flex;
margin-right: 1em;
flex-direction: ${(props)=>props.flexDirection ? props.flexDirection : "row"};
align-items: ${(props)=>props.alignItems ? props.alignItems : "left"};
margin: ${(props)=>props.margin ? props.margin : ""};
`
export const IconWrapper = styled.div`
color: #ff9900;
`
在上面的代码中,我们为我们的页面主体创建了一个容器,然后将页面分为两个部分。左边的部分包含部门列表,右边的部分包含产品及其信息。
然后我们为我们的产品图片创建了一个组件,也使用了我们导入的辅助组件。
如果你看一下BoldText 组件,你会发现它是PageText 组件的扩展,里面有一些新的样式。我们用它来创建比页面上的文字更粗的文本。通过样式化组件,你可以扩展另一个组件。这就像复制带有所有样式的组件,然后给它添加额外的样式。
SearchResultDiv 组件是产品图片上方的长条,包含搜索结果的细节。
我们还创建了一个ProductContainer 组件,包含我们的产品和它们的信息,然后是产品图片的图像容器。
我们的样式文件中的最后一个组件是我们的明星图标的包装器。
如果你愿意的话,你可以花点时间研究一下这些样式,了解每一行的作用。
现在,让我们在我们的索引文件中使用这些组件。
const Menubar:React.FC = () => {
return (
<Container>
<LeftContainer>
<BoldText>Department</BoldText >
{departmentList.map(item =>(
<Paragraph> <KeyboardArrowLeft/>{item}</Paragraph>
))}
</LeftContainer>
<RightContainer>
<SearchResultDiv>
<BoldText>1-12 of over 2,000 results for
</BoldText>
<BoldText color="#c45500"> All Departments
</BoldText>
</SearchResultDiv>
<ProductContainer>
{productDetails.map(item =>(
<Wrapper
flexDirection="column"
>
<ImageContainer>
<Image src={item.src}/>
</ImageContainer>
<Text>{item.name}</Text>
<Text fontSize=".8em" color="grey" >{item.by}</Text>
{/* stars */}
<Wrapper
margin=".3em 0 0 0"
alignItems="center"
>
<IconWrapper>
<Star/>
<Star/>
<Star/>
<Star/>
<StarHalf/>
</IconWrapper>
<KeyboardArrowDown/>
<Text color="blue">{item.starcount}</Text>
</Wrapper>
<Wrapper
alignItems="center"
margin=".3em 0 .3em 0"
>
<Text fontSize="1.3em">{item.price }</Text>
<Text color="grey">{ item.shipping}</Text>
</Wrapper>
<Text>{item.available}</Text>
</Wrapper>
))}
</ProductContainer>
</RightContainer>
</Container>
)
}
export default Menubar
上面,我们使用了我们创建的组件来建立我们的页面主体,并定制了它们以适应它们应该出现的地方。
我们利用了我们的产品细节,这些细节是从pageInfo 文件中导入的。在LeftContainer 组件中,我们通过departmentList 数组进行了映射。而在ProductContainer 组件中,我们通过productDetails 数组进行映射。通过数组映射而不是一个接一个地写细节,使我们能够写出更干净、更少重复的代码。
这就是页面主体的全部内容。我们的页面现在看起来应该是这样的:

结语
恭喜你!你现在已经掌握了所有的基础知识。你现在已经掌握了所有的基础知识和技能,可以用styled-components为你自己的TypeScript项目设计样式。你可以在这里查看完成的示例页面。