项目实战
使用CSS
尽量不要使用内联CSS
- 内联style代码多,性能差,扩展性差
- 外链css文件可复用代码,可单独缓存文件
元素内联style
-
和HTMl元素的style相似
-
必须用JS写法,不能是字符串,里面必须是对象
<span style={{ color: "green" }}>已发布
-
驼峰写法
使用css文件
-
引入css文件
-
JSX中使用className
let itemClassName = "list-item"; if (isPublished) itemClassName += " published"; <div key={id} className={itemClassName}>
可使用clsx或classnames做条件判断,两个功能相近,都是判断class条件的集合,比如当判断的条件多了,使用上面的if无法满足的时候就需要借用到工具。在这个项目中用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;