我们已学习了组件的创建,本文介绍组件的样式(CSS),您将了解到Vanilla CSS(明确手写的CSS)、CSS模块、CSS-in-JS、CCS库的使用等知识。
Vanilla CSS
现在已经有了非常好用的CSS库,比如Bootstrap、Material UI,所以手动编写Vanilla CSS的方法又浪费时间又乏味,已很少人使用了。我们可以了解下怎么做。
先在main.tsx文件中,删除从bootstratp导入css的语句,然后在components文件夹下新建ListGroup.css文件,再新建ListGroup文件夹,然后将ListGroup.css和ListGroup.tsx两个文件移至ListGroup文件夹中。再编写ListGroup.css文件:
.list-group{
list-style: none;
padding: 0;
}
在ListGroup.tsx文件顶部导入ListGrooup.css:
import "./ListGroup.css";
保存文件,发现列表框样式已变了。另外,在App.tsx文件中,由于ListGroup.tsx放在了ListGroup文件夹下,所以导入语句要加一层ListGroup.
import ListGroup from "./components/ListGroup/ListGroup";
顺便说一下,这个导入语句有点嵌套难看,我们可以在ListGroup文件夹下新建一个index.ts文件:
import ListGroup from "./ListGroup";
export default ListGroup;
然后再在App.tsx顶部导入ListGroup只需定位到文件夹。React会查找该文件夹的index.ts文件,如果index.ts文件收集并export了组件,就可以直接使用。
import ListGroup from "./components/ListGroup";
CSS模块
Vanilla CSS有个问题,就是容易被其他同名的CSS的覆盖。比如我们在App.css里添加同名 list-group:
.list-group{
background: red;
}
然后在App.tsx项部导入App.css:
import "./App.css";
查看网页,背景已变红了,说明与App.css的同名 list-group的css冲突了。我们可以用CSS模块来解决这个问题,CSS模块限定了CSS的范围,类似javascript的模块,这样就不必担心同名CSS的冲突问题了。下面就来介绍下具体怎么做。
先将ListGroup.css改名为ListGroup.module.css
再在ListGroup.tsx导入,导入语句稍有些不同,与javascript模块一样。另外,调用时由于有连字符 - ,不能使用点. 操作符styles.list-group,要用方括号。
import styles from "./ListGroup.module.css";
...
<ul className={styles["list-group"]}>
...
如果觉得这样比较丑,可以改一下class名,用驼峰命名法命名为listGroup,然后再用点.操作。
.listGroup{
list-style: none;
padding: 0;
}
<ul className={styles.ListGroup}>
如果某个元素需要应用多个css的class,比如我们还要应用 container的css类.
ListGroup.module.css:
.listGroup{
list-style: none;
padding: 0;
}
.container{
background: yellow;
}
再在ListGroup.tsx使用数组,并用join方法合并。
...
<ul className={[styles.ListGroup, styles.container].join(" ")}>
...
CSS-IN-JS
样式组件的另一种技术是CSS-IN-JS,该技术目前还有争议,有人喜欢有人讨厌,它是把CSS与组件放在一起。有一些优点,首先是范围限制不会与其他CSS发生冲突,再是CSS和JS或TS代码放在一起容易干净删除一个组件,还有就是更容易样式化基于Props/state的组件。
CSS-IN- JS
Scoped styles
All the CsS & JS/TS code in one place
Easier to delete a component
Easier to style based on props state
该技术有很多库可以使用,使用较多的是以下三种。
LIBRARIES
Styled components
Emotion
Polished
这一节就来了解下styled components。
先来安装styled-components,可能是版本问题,目前在Typescript中import该库,都有警告提示找不到,我们需要再安装一个库 @types/styled-components,希望以后的版本不再有这个问题。
npm i styled-components
npm i @types/styled-components
现在就可以使用styled了,它的属性包括了html各个元素。在ListGroup组件中,我们有ul和li两种html元素,可以用styled.ul`...`和styled.li`...`定义相应的css,并返回一个react组件,这里我们命名为List和ListItem。然后我们在ListGroup组件中就可以使用这两个组件了,分别代替ul和li,同时也去掉 className的css标记。
...
import styled from "styled-components";
const List = styled.ul`
list-style: none;
padding: 0;
`;
const ListItem = styled.li`
padding: 5px 0;
`;
...
function ListGroup({ items, heading, onSelectItem }: Props) {
...
<List>
...
<ListItem
...
>
...
</ListItem>
))}
</List>
</>
);
}
我们看上面的jsx语句,没有对外的css的依赖,全部是组件。一个文件包含了所有内容。后面如果需要删除该组件的话,直接删就好,不会对其他部分产生影响。
前面我们说过CSS-IN-JS的优点之一是方便使用基于props/state的组件,现在就来看看怎么用。
添加一个props,有一个类型为boolean的属性active,然后组件ListItem的styled.li传入该props,active为true时,背景为蓝色blue。最后ListGroup使用ListItem时,传入active={index===selectedIndex}。这样就能实现当点击某一个列表项时,背景高亮显示蓝色。
...
interface ListItemProps {
active: Boolean;
}
const ListItem = styled.li<ListItemProps>`
padding: 5px 0;
background: ${(props) => (props.active ? "blue" : "none")};
`;
...
function ListGroup...
<ListItem
active={index === selectedIndex}
...
</ListItem>
...
关注点分离
软件工程一个重要原则是关注点分离,该原则建议将各功能模块放在不同部分,这样会比将所有功能放在一起更好。使用关注点分离,我们的应用会变成模块化,更容易理解、维护、修改。
模块化带来一些好处,我们可以独立地构建、测试各模块,并可在其他应用中重用模块,每一个模块只负责一个关注点。
这有点像饭店的工作,厨师只负责烧菜,服务员只负责服务顾客,各司其职。
模块里的复杂实现隐藏在一个定义好的接口后面,类似电视机的遥控器有很多复杂的实现,但它的按钮也就这么几个。隐藏了细节,其他应用也就很容易使用。
有人说,CSS-IN-JS违反了关注点分离原则,因为我们把所有东西放在了一起。
实际上,关注点分离的原则不是说将所有文件分离或放在一起,而是强调应用里的每一个模块都应该完成一个特定的功能,而围绕这一功能的复杂细节应该隐藏在定义好的接口里。
在我们的ListGroup里,实现细节都被隐藏,而props的heading、items相当于该模块的接口。使用ListGroup的用户可不关心ListGroup的实现细节,不关心它是放在一个文件或多个文件里,所以CSS-IN-JS 并不违反关注点分离的原则。
当然,您可以有不同意见,哈~
内联样式
可以直接在html元素中使用 style 属性设置css。style用大括号包起来。与普通的css区别在于,css的class命名方法不一样,不用连字符 - ,而是用驼峰命名法,比如用 backgroundColor,而不是background-color,这是一个不好的实践,除非是没得选择,一般不使用内联样式。
<ul className="list-group" style={{ backgroundColor: "yellow" }}>
流行的UI库
目前有很多UI库用来创建现代漂亮的网站,比如Bootstrap, Material UI, Tailwind CSS等,前面我们已用过Bootstrap了,它提供给我们非常有用且易用的组件。
Material UI是一个开源的React组件库,它实现了google material design,后者是一种设计语言,用在Google各种产品中,我们可以使用它快速实现相关UI而不用手写CSS样式。
Tailwind稍有些不同,它提供了一些实用的小的CSS类,比如 list-none就表示是{list-style:none}; 而padding为0,则可以使用p-0的class. 使用Wailwind, 我们可以不用再手写css了而直接使用这些简单实用的class。
但随着UI越来越复杂,使用Tailwind最终可能需要标记很多 class,标记会很长。
<div class='flex w-full flex-col items -center gap-4' >
...
</div>
如果这不是你想要的,可以使用关联的css库,Daisy UI 或Chakra UI。
Daisy UI组合了标记,看上去更清晰更短一些,有点类似bootstrap.
<div class='alert alert-success'>
...
</div>
Chakra UI类似于Material UI,它是React组件,基于Tailwind构建。
添加图标
本节介绍react添加图标,用到一个react库 react-icons。先安装。我们使用版本4.7.1。
npm i react-icons@4.7.1
谷歌搜索 "react icons"找到官网,搜索某个图标比如"calendar",能看到有多种风格的图标,并以具体风格的前缀命名图标,我们点击某个图标就复制了该图标名称,比如BsFillCalendarFill,然后回到App.tsx编写。
在顶部导入 BsFillCalendarFill 图标组件,由于是bs开头,我们加上/bs, react-icons/bs。然后可以像使用普通组件一样使用图标,添加属性,这里添加了color, size等。
import { BsFillCalendarFill } from "react-icons/bs";
function App() {
return (
<div>
<BsFillCalendarFill color="red" size="40" />
</div>
);
}
export default App;
运行效果是这样的
练习一:使用CSS模块
前面我们创建了一个Button组件,后来我们去掉了引入bootstrap的css的语句,目前显示的Button非常丑,我们来练习下如何使用CSS模块来美化该按钮。
解答:
在components下新建文件夹Button,将Button.tsx移入其中,再添加文件Button.module.css,键入css,当然配色及设计是完全主观的。我们可以再增加比如 btn-secondary, btn-danger等class,由于是重复的,这里就不添加了。
.btn{
padding: 8px 12px;
border-radius: 3px;
border: 0;
}
.btn-primary{
background-color: #0d6efd;
color: white;
}
然后到Button.tsx中调用即可。
import styles from "./Button.module.css";
const Button = (...
className={[styles.btn, styles["btn-" + color]].join(" ")}
...
练习二:创建Like组件
创建一个可重用的Like组件,点击它就简单在控制台显示clicked信息。
解答:
在components文件夹下新建Like.tsx文件。导入react-icons的AiFillHeart和AiOutlineHeart组件,再设置state表示是否点中,然后创建一个接口onClick可以让调用它的父组件处理事件。
import { useState } from "react";
import { AiFillHeart, AiOutlineHeart } from "react-icons/ai";
interface Props {
onClick: () => void;
}
const Like = ({ onClick }: Props) => {
const [status, setStatus] = useState(false);
const toggle = () => {
setStatus(!status);
onClick();
};
if (status)
return (
<AiFillHeart color="#ff6b81" size={40} onClick={toggle}></AiFillHeart>
);
return <AiOutlineHeart size={40} onClick={toggle}></AiOutlineHeart>;
};
export default Like;
这里是App组件调用,App.tsx:
import Like from "./Like";
function App() {
return (
<div>
<Like onClick={() => console.log("clicked")} />
</div>
);
}
export default App;
点击Like组件的效果变化如下。
小结
本文介绍了React组件的样式,我们可以根据需要选择某种技术实现样式。
下一篇计划:管理组件状态。