项目实战
使用CSS
尽量不要使用内联CSS
- 内联style代码多,性能差,扩展性差
- 外链css文件可复用代码,可单独缓存文件
元素内联style
- 和HTMl元素的style相似
- 必须用JS写法,不能是字符串,里面必须是对象
<span style={{ color: "green" }}>已发布</span>
- 驼峰写法
使用css文件
- 引入css文件
- JSX中使用className
<div key={id} className="list-item">
let itemClassName = "list-item";
if (isPublished) itemClassName += " published";
<div key={id} className={itemClassName}>
- 可使用clsx或classnames做条件判断,两个功能相近,都是判断class条件的集合,比如当判断的条件多了,使用上面的if无法满足的时候就需要借用到工具。在这个项目中用classnames做例子,通过下列步骤使用
- classnames仓库地址
下载指令
npm install classnames
import classnames from "classnames";
const itemClassName = classnames("list-item", { published: isPublished });
// 上下两种含义一致,只是不同的写法
const itemClassName = classnames({
"list-item": true,
published: isPublished,
});
CSS Module
普通CSS的问题,React使用组件化开发,多个组件就需要多个CSS文件,多个CSS文件很容易造成className重复。在没有相应的工具前使用的是BEM,一种软性规范。主观性过强而不推荐。以下是CSS module的特点
- 每个CSS文件都当作单独的模块,命令xxx.module.css
- 为每个className增加后缀名,不重复
- Create- React-App原生支持CSS Module
将文件名更改为xxx.module.css的格式,样式的格式不变
// QuestionCard.module.css 文件中
.list-item{
border: 1px solid black;
padding: 10px;
margin-bottom: 16px;
}
.published{
border: 1px solid greenyellow;
}
引入和使用,这一部分和原来的差别比较大
// import classnames from "classnames";
// 上面是原来的,下面是module的引入
import styles from "./QuestionCard.module.css";
// 使用格式,其中list-item因为有-符号,如果直接styles.list-item会报错,下面这个js写法就可以
<div key={id} className={styles["list-item"]}>
Sass
CSS语法比较原始,不能嵌套。现代开发一般使用less sass等预处理语言。CRA原生支持Sass Module,后缀直接改为.scss即可
下载指令
npm install sass --save
接下来将想要使用的文件格式改为xxx.scss,记得后缀为scss。
import styles from "./QuestionCard.module.scss";
const itemClassName = classnames({
[styles["list-item"]]: true,
[styles["published"]]: isPublished,
});
<div key={id} className={itemClassName}>
其中对于itemClassName的中括号的解释如下,在这段代码中,中括号([]
)用于在JavaScript对象字面量中动态地设置属性名。这种语法是ES6)中引入的计算属性名的一个特性,其键为变量y而不是固定字符比如a时,这个写法实际上是将这个变量的引用值传递进去
CSS-in-JS
- 一种解决方案(而非工具名称),有好几个工具
- 在JS中写CSS,带来极大的灵活性
- 它和内联style完全不一样,也不会有内联style的问题和className的问题(会自行生成class)
Style-components
官网 可能是外网的链接,打开的时候速度有点慢
下载指令
npm install styled-components
引入代码如下,视频中老师的引入爆红线,需要额外下载东西,我这边没有,不过也顺便下载了。下面这个组件可以测试引入是否成功
下载指令
npm i --save-dev @types/styled-components
import React, { FC } from "react";
import styled, { css } from "styled-components";
const Button = styled.button<{ $primary?: boolean }>`
background: transparent;
border-radius: 3px;
border: 2px solid #bf4f74;
color: "#BF4F74";
margin: 0 1em;
padding: 0.25em 1em;
${(props) =>
props.$primary &&
css`
background: blue;
color: white;
`};
`;
const Container = styled.div`
text-align: center;
`;
const Demo: FC = () => {
return (
<div>
<p>styled-components demo</p>
<Container>
<Button>normal button</Button>
<Button $primary>primary 按钮</Button>
</Container>
</div>
);
};
export default Demo;
大概解释一下上述代码中的逻辑,其中styled可以理解为一个类,而styled.button和styled.div小数点后的两个属性 都可以理解为方法,包括css后面加字符串,css也是个函数,反引号可以理解为传参如下图所示,多一层括号显得麻烦,所以不用括号的形式
styled-jsx
仓库地址
项目中不使用这个,因为ts环境中对标签的属性比较敏感,而这个工具插入了一些非标准的属性,导致需要额外扩展比较多的功能,用于js没问题,可以选择性的使用。
emotion
官网地址
使用起来的形式和前面的components比较类似,但是同样有个问题,就是这个工具在标签中添加了css属性,在ts中这么设置会报错,所以ts环境中同样不进行使用.
重构列表页,增加css样式
选择CSS-Module
- 简单易用,学习成本较低
- 性能更好,使用CSS-in-JS会增加编译时间
- 不需要灵活变换样式
新建React项目,具体过程参考这一章,原来有很多个Demo的也进行保留,两个都会讲。以下几个文件直接复制粘贴可以用
App.tsx
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import List from "./pages/list";
function App() {
return (
<div>
<h1>问卷F1</h1>
<List />
</div>
);
}
export default App;
list.tsx
import React, { FC, useState } from "react";
import QuestionCard from "../components/QuestionCard";
import styled from "./list.module.scss";
const rawQuestionList = [
{
_id: "q1",
title: "问卷1",
isPublished: true,
isStar: false,
answerCount: 5,
createAt: "3月10日 13:23",
},
{
_id: "q2",
title: "问卷2",
isPublished: false,
isStar: true,
answerCount: 15,
createAt: "3月22日 13:23",
},
{
_id: "q3",
title: "问卷3",
isPublished: true,
isStar: true,
answerCount: 100,
createAt: "4月10日 13:23",
},
{
_id: "q4",
title: "问卷4",
isPublished: false,
isStar: false,
answerCount: 98,
createAt: "3月23日 13:23",
},
];
const List: FC = () => {
const [questionList, setQuestionList] = useState(rawQuestionList);
return (
<>
<div className={styled.header}>
<div className={styled.left}>
<h3>我的问卷</h3>
</div>
<div className={styled.right}>搜索</div>
</div>
<div className={styled.content}>
{questionList.map((q) => {
const { _id } = q;
return <QuestionCard key={_id} {...q} />;
})}
</div>
<div className={styled.footer}>footer</div>
</>
);
};
export default List;
list.module.scss
.header{
display: flex;
.left{
flex: 1;
}
.right{
flex: 1;
text-align: right;
}
}
.content{
margin-bottom: 20px;
}
.footer{
text-align: center;
}
body{
background-color: #f1f1f1;
}
QuestionCard.module,scss
.container{
margin-bottom: 20px;
padding: 12px;
border-radius: 3px;
background-color: white;
&:hover{
box-shadow: 0 4px 10px lightgray;
}
}
.title{
display: flex;
.left{
flex: 1;
}
.right{
flex: 1;
text-align: right;
}
}
.button-container{
display: flex;
.left{
flex: 1;
}
.right{
flex: 1;
text-align: right;
button{
color: #999;
}
}
}
QuestionCard.tsx
import React, { FC, useEffect } from "react";
// import "./QuestionCard.css";
import styled from "./QuestionCard.module.scss";
import classnames from "classnames";
type PropsType = {
_id: string;
title: string;
isPublished: boolean;
isStar: boolean;
answerCount: number;
createAt: string;
// 问号是可写可不写,跟flutter语法相似
deletQuestion?: (id: string) => void;
pubQuestion?: (id: string) => void;
};
const QuestionCard: FC<PropsType> = (props: PropsType) => {
const { _id, title, createAt, answerCount, isPublished } = props;
return (
<div className={styled.container}>
<div className={styled.title}>
<div className={styled.left}>
<a href="#">{title}</a>
</div>
<div className={styled.right}>
{isPublished ? (
<span style={{ color: "green" }}>已发布</span>
) : (
<span>未发布</span>
)}
<span>答卷:{answerCount}</span>
<span>{createAt}</span>
</div>
</div>
<div className={styled["button-container"]}>
<div className={styled.left}>
<button>编辑问卷</button>
<button>数据统计</button>
</div>
<div className={styled.right}>
<button>标星</button>
<button>复制</button>
<button>删除</button>
</div>
</div>
</div>
);
};
export default QuestionCard;