危险

为之则易,不为则难

0%

01_React 初识

今日目标

✔ 了解 React 的特点。

✔ 掌握使用脚手架创建项目。

✔ 掌握 JSX 的基本使用。

✔ 掌握 B 站评论列表案例。

React 介绍

学习目标

了解 React 的背景和基本概念。

React 背景

React 起源于 Facebook(Meta) 的内部项目(2011,News Feed),之后又被用来开发网站(2012,Instagram),并于 2013 年 5 月开源。

React 是什么

React 是一个用于构建用户界面的 JavaScript

  • 用户界面:HTML 界面。

  • Library(库)和 Framework(框架):库的特点是小而巧,针对特定问题的单一解决方案。框架的特点是大而全,提供了一整套的解决方案。

React 趋势

NPM 下载量 来看,React > Vue > Angular。

做个小结

  • React 是哪个公司开发的?

  • React 是用来干什么?

  • React 的定位是库还是框架?

React 特点

目标

了解 React 的特点。

内容

  • 声明式

只需要描述 UI(HTML)看起来是什么样子,就像写 HTML 一样简单,React 内部负责渲染 UI,并在数据变化时自动更新 UI。

1
2
3
4
5
6
const jsx = (
<div className='app'>
<h1>Hello World!</h1>
<p>动态数据:{count}</p>
</div>
)
  • 组件化

把复杂的页面拆分成一个一个的单元,这些组成页面的基本单元就是组件,通过组合、复用组件来编写复杂界面的方式,就是组件化。

  • 一次学习,随处使用

使用 React 除了可以开发 Web 应用,还可以使用 React Native 开发原生移动应用,甚至可以开发 VR(虚拟现实)应用(React 360)。

  • 相比较于 Vue,React 强调尽可能的利用 JS 语言自身的能力来编写 UI,而不是通过造轮子增强 HTML 的功能。

小结

  • React 的特点有哪几个?

  • 从个人角度看 React 特点,大厂必备(阿里、字节、美团…)

脚手架创建项目

目标

掌握使用 React 脚手架创建项目的方法。

意义

现代的 Web 应用要考虑的问题很多,除了编写业务代码之外,还要考虑代码规范、预编译、压缩合并、打包上线等工作,需要有一套完整的解决方案来辅助我们快速开发,而 React 脚手架 就是这么一套完整的解决方案,它零配置,开箱即用,让我们从繁杂的 Webpack 配置当中解脱出来,更关注于业务本身。

使用

使用 create-react-app 这个命令行工具,它是 React 官方团队出的一个构建 React 应用的脚手架工具。

方法一

  1. 全局安装npm i -g create-react-app或者yarn global add create-react-app

  2. 初始化项目create-react-app my-app,my-app 表示项目名称,可以修改。

  3. 启动项目:yarn startor npm start

🙁 缺点:全局安装命令无法保证命令一直是最新版本。

方法二(推荐)

  1. 命令:npx create-react-app react-basic

  2. 启动项目:yarn start or npm start

  3. npx 是 npm@v5.2 版本新添加的命令,用来简化 npm 工具包的使用流程。

😄 优点:npx 会调用最新的 create-react-app 直接创建 React 项目。

小结

使用脚手架创建项目的命令是什么?

渲染自己的界面

目标

掌握通过 React 脚手架渲染页面的基本步骤。

步骤

  1. 删除 srcpublic 目录中的所有内容。

  2. 新建 public/index.html

  3. 新建 src/index.js 文件。

  4. 引入 React 核心库和涉及 DOM 操作的包。

  5. 调用 React.createElement() 方法创建 React 元素。

  6. 调用 ReactDOM.render() 方法渲染 React 元素到页面。

代码

  1. 删除 srcpublic 目录中的所有内容。

  2. 新建 public/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>

<body>
<div id="root"></div>
</body>
</html>
  1. 新建 src/index.js 文件。

  2. 引入 React 核心库和涉及 DOM 操作的包。

src/index.js

1
2
3
// create-react-app 脚手架生成的项目中已经下载好了 react 和 react-dom,无需重复下载,直接使用即可
import React from 'react'
import ReactDOM from 'react-dom'
  1. 调用 React.createElement() 方法创建 React 元素。
1
2
// 标签名、标签属性、标签内容,返回的是一个 React 元素(虚拟 DOM)
const title = React.createElement('h1', null, 'Hello World')
  1. 调用 ReactDOM.render() 方法渲染 React 元素到页面。
1
ReactDOM.render(title, document.querySelector('#root'))

小结

在脚手架中,使用 React 和 ReactDOM 渲染页面的步骤是?

React.createElement() 📝

目标

如何用 React.createElement() 创建出如下结构?

1
2
3
4
5
6
7
<div class="wrap">
<ul>
<li>React</li>
<li>Vue</li>
<li>Angular</li>
</ul>
</div>

要点

1
React.createElement('标签名', { 标签上的属性1:值1 }, 子元素1, 子元素2)

小结

假如有更加复杂的页面结构怎么办呢?

JSX 基本介绍

目标

了解 JSX 的定义和使用,了解它的工作过程。

为什么要有 JSX

React.createElement() 创建 React 元素的问题:繁琐/不简洁;不直观,无法一眼看出所描述的结构;代码不容易维护!

1
2
3
4
5
React.createElement(
'div',
{ className: 'wrap' },
React.createElement('ul', null, React.createElement('li', null, 'React'), React.createElement('li', null, 'Vue'), React.createElement('li', null, 'Angular'))
)

对比下面 JSX 的写法

1
2
3
4
5
6
7
<div class="wrap">
<ul>
<li>React</li>
<li>Vue</li>
<li>Angular</li>
</ul>
</div>

JSX 是什么

JSX 是 JavaScript XML 的简写,表示可以在 JavaScript 代码中写 XML(HTML) 格式的代码。

优势:声明式语法更加直观,与 HTML 结构相同,降低了学习成本,提高了开发效率,JSX 是 React 的核心之一。

JSX 基本使用

  1. 使用 JSX 创建 React 元素
1
const title = <h1>Hello JSX</h1>
  1. 使用 ReactDOM.render() 方法渲染 React 元素到页面中
1
ReactDOM.render(title, document.querySelector('#root'))

JSX 是如何工作的

🤔 换句话说,JSX 并不是标准的 ECMAScript 语法,为什么 React 脚手架中可以直接使用 JSX 呢?

  • JSX 需要使用 Babel 编译成 React.createElement() 的形式,然后配合 React 才能在浏览器中使用,而 create-react-app 脚手架中已经内置了 Babel 及相关配置。

  • 编译 JSX 语法的包为:@babel/preset-react在线体验

小结

1. JSX 是什么的简写?

2. JSX 相比较 React.createElement() 的优势是什么?

3. 为什么 React 脚手架中可以直接使用 JSX?

JSX 注意点

目标

掌握 JSX 使用的注意点。

内容

1
2
3
4
5
6
const r = (
<div className='wrap'>
<h1>Hello World</h1>
<p>React</p>
</div>
)
  1. 必须有 1 个根节点,或者虚拟根节点 <></><React.Fragment></React.Fragment>

  2. 属性名一般是驼峰的写法且不能是 JS 中的关键字,例如 class 改成 className,label 的 for 属性改为 htmlFor,colspan 改为 colSpan

  3. 元素若没有子节点,可以使用单标签,但一定要闭合,例如 <span/>

  4. React@16.14 之前需要先引入 React 才能使用 JSX(这个也好理解,因为 JSX 最后还是要被转成 React.createElement() 的形式)。

  5. 换行建议使用 () 进行包裹,防止换行的时候自动插入分号的 Bug。

总结

至少说出 JSX 的三个注意点?根节点、属性名、换行的时候外部要怎样。

使用表达式

目标

掌握在 JSX 中使用表达式。

内容

单大括号中可以使用任意的表达式(可以产生结果的式子)。

  • 普通的简单数据类型。
1
2
3
4
5
6
7
const name = 'zs'
const age = 18
const title = (
<h1>
姓名:{name}, 年龄:{age}
</h1>
)
  • 对象中的属性。
1
2
3
4
const car = {
brand: '玛莎拉蒂',
}
const title = <h1>汽车:{car.brand}</h1>
  • 数组中的某一项甚至整个数组。
1
2
const friends = ['张三', '李四']
const title = <h1>汽车:{friends}</h1>
  • 可以调用方法。
1
2
3
4
function sayHi() {
return '你好'
}
const title = <h1>姓名:{sayHi()}</h1>

注意

  • JS 对象虽然也是表达式,但是不能直接嵌套在 {} 中,一般只会出现在 style 属性中。

  • JSX 本身也是表达式。

1
2
const span = <span>我是一个span</span>
const title = <h1>盒子{span}</h1>

小结

  • JSX 中可以包含任意的表达式(除了对象)。

  • JSX 中不能放语句,例如 ifswitchforwhile 等。

条件渲染

目标

掌握条件渲染的写法。

内容

📝 需求:isLoading 是 true,显示“加载中…”,否则显示“加载完毕!”。

1
2
3
4
5
6
7
8
9
10
11
12
import ReactDOM from 'react-dom'

const isLoading = true

const loadData = () => {
if (isLoading) {
return <h2>数据加载中,请稍后...</h2>
}
return <h2>数据加载完成,此处显示了加载后的数据</h2>
}

ReactDOM.render(loadData(), document.querySelector('#root'))

三元表达式的写法如下。

1
2
3
const loadData = () => {
return <h2>{isLoading ? '数据加载中,请稍后...' : '数据加载完成,此处显示了加载后的数据'}</h2>
}

小结

条件渲染使用__ 和 __?

列表渲染

目标

  • 能在 JSX 中使用数组的 map 方法来生成列表结构。

  • 了解 key 的作用。

需求

  • 后端返回的数据。
1
2
3
4
5
const arr = [
{ id: 1, name: 'Vue' },
{ id: 2, name: 'React' },
{ id: 3, name: 'Angular' },
]
  • 期望实现的效果。

代码实现

手动拼接如下。

1
2
3
4
5
<ul>
<li>{list[0].name}</li>
<li>{list[1].name}</li>
<li>{list[2].name}</li>
</ul>

简化上面的代码:可以使用 map() 方法渲染一组数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import ReactDOM from 'react-dom'

const list = [
{ id: 1, name: 'Vue' },
{ id: 2, name: 'React' },
{ id: 3, name: 'Angular' },
]

const arrJsx = list.map((item) => <li key={item.id}>{item.name}</li>)

const loadData = () => {
return <ul>{arrJsx}</ul>
}

ReactDOM.render(loadData(), document.querySelector('#root'))

关于 key

  1. 特点:key 值要保证唯一,尽量避免使用索引号,key 在最终的 HTML 结构中是看不见的。

  2. 加在哪里:map() 遍历谁,就把 key 加在谁上。

  3. 作用:React 内部用来进行性能优化时使用的,key 在最终的 HTML 结构中是看不见的。

小结

  • 生成列表使用数组循环的哪个方法?

  • key 的作用是什么?

  • key 一定要写在哪个位置?

渲染数据 📝

目标

掌握使用 map 方法来进行列表渲染。

内容

1
2
3
4
5
const list = [
{ id: 1, name: '武汉黑马前端64期', salary: 11000 },
{ id: 2, name: '武汉黑马前端66期', salary: 13000 },
{ id: 3, name: '武汉黑马前端68期', salary: 15000 },
]

需求:根据上面数据生成下面结果。

1
2
3
4
5
6
7
8
9
10
<ul>
<li>
<h3>班级:武汉黑马前端64期</h3>
<p>工资:11000</p>
</li>
<li>
<h3>班级:武汉黑马前端66期</h3>
<p>工资:15000</p>
</li>
</ul>

样式处理

目标

掌握 React 中使用样式的两种方式。

行内样式

  • 语法
1
<元素 style={ {css属性1:值1,css属性2:值2} }></元素>
  • 需求
  • 代码
1
<div style={{ width: 200, height: 200, lineHeight: '200px', backgroundColor: 'black', color: 'white', textAlign: 'center', fontSize: 30 }}>React</div>
  • 注意点

    a,为啥有两个{{ }},外层的 {} 表示要开始写 JS 表达式了,内层的 {} 表示是一个对象。

    b,属性名是小驼峰格式,例如 background-color 需要写成 backgroundColor

    c,属性值是字符串,如果单位是 px,可以简写成数值。

className

  • className 定义类名。

  • src 目录中准备 index.css 文件,然后在 index.js 文件中通过 import './index.css' 引入文件。

index.css

1
2
3
4
5
6
.title {
width: 200px;
height: 200px;
color: white;
background-color: black;
}

index.js

1
2
import './index.css'
;<div className='title'>Hello React</div>

小结

  • 类名使用 className推荐

  • 行内样式,<div style={{ color: 'red' }}>Hello</div>

B 站评论列表 📝

案例目标

综合使用 JSX 的知识,结合数据、结构和样式渲染成如下效果。

资源准备

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./index.css" />
</head>

<body>
<div class="App">
<div class="comment-container">
<div class="comment-head"><span>1 评论</span></div>
<div class="tabs-order">
<ul class="sort-container">
<li class="">按热度排序</li>
<li class="on">按时间排序</li>
</ul>
</div>
<div class="comment-send">
<div class="user-face"><img class="user-head" src="avatar.png" alt="" /></div>
<div class="textarea-container"><textarea cols="80" rows="5" placeholder="发条友善的评论" class="ipt-txt"></textarea><button class="comment-submit">发表评论</button></div>
<div class="comment-emoji"><i class="face"></i><span class="text">表情</span></div>
</div>
<div class="comment-list">
<div class="list-item">
<div class="user-face"><img class="user-head" src="https://y.qq.com/music/photo_new/T001R300x300M000003aQYLo2x8izP.jpg?max_age=2592000" alt="" /></div>
<div class="comment">
<div class="user">刘德华</div>
<p class="text">给我一杯忘情水</p>
<div class="info">
<span class="time">2021-10-10 09:09:00</span><span class="like liked"><i class="icon"></i></span><span class="hate"><i class="icon"></i></span
><span class="reply btn-hover">删除</span>
</div>
</div>
</div>
<div class="list-item">
<div class="user-face"><img class="user-head" src="https://y.qq.com/music/photo_new/T001R500x500M0000025NhlN2yWrP4.jpg?max_age=2592000" alt="" /></div>
<div class="comment">
<div class="user">周杰伦</div>
<p class="text">听妈妈的话</p>
<div class="info">
<span class="time">2021-10-11 09:09:00</span><span class="like"><i class="icon"></i></span><span class="hate"><i class="icon"></i></span
><span class="reply btn-hover">删除</span>
</div>
</div>
</div>
<div class="list-item">
<div class="user-face"><img class="user-head" src="https://y.qq.com/music/photo_new/T001R500x500M000003Nz2So3XXYek.jpg?max_age=2592000" alt="" /></div>
<div class="comment">
<div class="user">陈奕迅</div>
<p class="text">十年</p>
<div class="info">
<span class="time">2021-10-11 10:09:00</span><span class="like"><i class="icon"></i></span><span class="hate hated"><i class="icon"></i></span
><span class="reply btn-hover">删除</span>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

index.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
* {
margin: 0;
padding: 0;
list-style: none;
}
.App {
/* width: 1090px; */

width: 80%;
margin: 50px auto;
}
.comment-head {
margin: 0 0 20px;
font-size: 18px;
line-height: 24px;
color: #222;
}

.comment-send {
margin: 10px 0;
}

.user-face {
float: left;
margin: 7px 0 0 5px;
position: relative;
}

.user-head {
width: 48px;
height: 48px;
border-radius: 50%;
}

.textarea-container {
position: relative;
margin-left: 85px;
margin-right: 80px;
}
.textarea-container:hover .ipt-txt {
background-color: #fff;
border-color: #00a1d6;
}

.ipt-txt {
font-size: 12px;
display: inline-block;
box-sizing: border-box;
background-color: #f4f5f7;
border: 1px solid #e5e9ef;
overflow: auto;
border-radius: 4px;
color: #555;
width: 100% !important;
height: 65px;
transition: 0s;
padding: 5px 10px;
line-height: normal;
resize: none;
outline: none;
}

.comment-submit {
width: 70px;
height: 64px;
position: absolute;
right: -80px;
top: 0;
padding: 4px 15px;
font-size: 14px;
color: #fff;
border-radius: 4px;
text-align: center;
min-width: 60px;
vertical-align: top;
cursor: pointer;
background-color: #00a1d6;
border: 1px solid #00a1d6;
transition: 0.1s;
user-select: none;
outline: none;
}
.comment-submit:hover {
background-color: #00b5e5;
border-color: #00b5e5;
}

.comment-emoji {
padding: 0;
width: 66px;
height: 24px;
color: #99a2aa;
border: 1px solid #e5e9ef;
border-radius: 4px;
position: relative;
font-size: 12px;
text-align: center;
line-height: 23px;
margin-left: 86px;
margin-top: 3px;
cursor: pointer;
display: inline-block;
}
.comment-emoji:hover {
color: #6d757a;
}

.face {
display: inline-block;
vertical-align: middle;
line-height: 1;
width: 16px;
height: 16px;
margin-right: 5px;
background: url(data:image/png;base64,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)
no-repeat -408px -24px;
}
.comment-emoji:hover .face {
background-position: -472px -24px;
}

.comment-emoji .text {
display: inline-block;
vertical-align: middle;
line-height: 1;
font-size: 12px !important;
}

.tabs-order {
margin: 0 0 24px 0;
border-bottom: 1px solid #e5e9ef;
}

.sort-container {
display: flex;
}

.tabs-order li {
background-color: transparent;
border-radius: 0;
border: 0;
padding: 8px 0;
margin-right: 16px;
border-bottom: 1px solid transparent;
position: relative;
float: left;
cursor: pointer;
line-height: 20px;
height: 20px;
font-size: 14px;
font-weight: bold;
color: #222;
}

.tabs-order li:last-child {
margin: 0 16px;
}

.tabs-order li.on {
border-bottom: 1px solid #00a1d6;
color: #00a1d6;
}

.tabs-order li.on::after {
content: '';
width: 6px;
height: 3px;
background: transparent
url(data:image/png;base64,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) -669px -31px
no-repeat;
position: absolute;
bottom: 0;
left: 50%;
margin-left: -3px;
visibility: visible;
}

.list-item {
display: flex;
}
.list-item:first-child {
padding-top: 22px;
}

.comment {
flex: 1;
position: relative;
margin-left: 35px;
padding: 22px 0 14px 0;
border-top: 1px solid #e5e9ef;
}
.list-item:last-child .comment {
border-bottom: 1px solid #e5e9ef;
}

.comment .user {
color: #6d757a;
font-size: 12px;
font-weight: bold;
line-height: 18px;
padding-bottom: 4px;
display: block;
word-wrap: break-word;
position: relative;
}

.comment .text {
line-height: 20px;
padding: 2px 0;
font-size: 14px;
text-shadow: none;
overflow: hidden;
word-wrap: break-word;
word-break: break-word;
white-space: pre-wrap;
}

.info {
color: #99a2aa;
line-height: 26px;
font-size: 12px;
}

.icon {
cursor: pointer;
background: url(data:image/png;base64,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)
no-repeat;
}

.time {
margin-right: 20px;
}

.like {
cursor: pointer;
margin-right: 20px;
}

.like > i {
display: inline-block;
width: 14px;
height: 14px;
vertical-align: text-top;
margin-right: 5px;
background-position: -153px -25px;
}
.like:hover > i {
background-position: -218px -25px;
}
.info .liked > i {
background-position: -154px -89px;
}

.hate {
cursor: pointer;
margin-right: 15px;
}

.hate > i {
display: inline-block;
width: 14px;
height: 14px;
vertical-align: text-top;
margin-right: 5px;
background-position: -153px -153px;
}

.hate:hover > i {
background-position: -217px -153px;
}
.info .hated > i {
background-position: -154px -217px;
}

.btn-hover {
padding: 0 5px;
border-radius: 4px;
margin-right: 15px;
cursor: pointer;
display: inline-block;
}

.btn-hover:hover {
color: #00a1d6;
background: #e5e9ef;
}

实现步骤

渲染基本结构

解决 className 和图片失效的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

const content = (
<div class='App'>
<div class='comment-container'>
<div class='comment-head'>
<span>1 评论</span>
</div>
<div class='tabs-order'>
<ul class='sort-container'>
<li class=''>按热度排序</li>
<li class='on'>按时间排序</li>
</ul>
</div>
<div class='comment-send'>
<div class='user-face'>
<img class='user-head' src='avatar.png' alt='' />
</div>
<div class='textarea-container'>
<textarea cols='80' rows='5' placeholder='发条友善的评论' class='ipt-txt'></textarea>
<button class='comment-submit'>发表评论</button>
</div>
<div class='comment-emoji'>
<i class='face'></i>
<span class='text'>表情</span>
</div>
</div>
<div class='comment-list'>
<div class='list-item'>
<div class='user-face'>
<img class='user-head' src='https://y.qq.com/music/photo_new/T001R300x300M000003aQYLo2x8izP.jpg?max_age=2592000' alt='' />
</div>
<div class='comment'>
<div class='user'>刘德华</div>
<p class='text'>给我一杯忘情水</p>
<div class='info'>
<span class='time'>2021-10-10 09:09:00</span>
<span class='like liked'>
<i class='icon'></i>
</span>
<span class='hate'>
<i class='icon'></i>
</span>
<span class='reply btn-hover'>删除</span>
</div>
</div>
</div>
<div class='list-item'>
<div class='user-face'>
<img class='user-head' src='https://y.qq.com/music/photo_new/T001R500x500M0000025NhlN2yWrP4.jpg?max_age=2592000' alt='' />
</div>
<div class='comment'>
<div class='user'>周杰伦</div>
<p class='text'>听妈妈的话</p>
<div class='info'>
<span class='time'>2021-10-11 09:09:00</span>
<span class='like'>
<i class='icon'></i>
</span>
<span class='hate'>
<i class='icon'></i>
</span>
<span class='reply btn-hover'>删除</span>
</div>
</div>
</div>
<div class='list-item'>
<div class='user-face'>
<img class='user-head' src='https://y.qq.com/music/photo_new/T001R500x500M000003Nz2So3XXYek.jpg?max_age=2592000' alt='' />
</div>
<div class='comment'>
<div class='user'>陈奕迅</div>
<p class='text'>十年</p>
<div class='info'>
<span class='time'>2021-10-11 10:09:00</span>
<span class='like'>
<i class='icon'></i>
</span>
<span class='hate hated'>
<i class='icon'></i>
</span>
<span class='reply btn-hover'>删除</span>
</div>
</div>
</div>
</div>
</div>
</div>
)

ReactDOM.render(content, document.querySelector('#root'))

渲染评论数量和 Tab 栏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const state = {
// hot: 热度排序 time: 时间排序
tabs: [
{
id: 1,
name: '热度',
type: 'hot',
},
{
id: 2,
name: '时间',
type: 'time',
},
],
active: 'time',
list: [
{
id: 1,
author: '刘德华',
comment: '给我一杯忘情水',
time: '2021-11-10 09:09:00',
img: 'https://y.qq.com/music/photo_new/T001R300x300M000003aQYLo2x8izP.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: 1,
},
{
id: 2,
author: '周杰伦',
comment: '听妈妈的话',
time: '2021-12-11 09:09:00',
img: 'https://y.qq.com/music/photo_new/T001R500x500M0000025NhlN2yWrP4.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: 0,
},
{
id: 3,
author: '陈奕迅',
comment: '十年',
time: '2021-10-11 10:09:00',
img: 'https://y.qq.com/music/photo_new/T001R500x500M000003Nz2So3XXYek.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: -1,
},
],
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import React from 'react'
import ReactDOM from 'react-dom'
import avatar from './images/avatar.png'
import './index.css'

const state = {
// hot: 热度排序 time: 时间排序
tabs: [
{
id: 1,
name: '热度',
type: 'hot',
},
{
id: 2,
name: '时间',
type: 'time',
},
],
active: 'time',
list: [
{
id: 1,
author: '刘德华',
comment: '给我一杯忘情水',
time: '2021-11-10 09:09:00',
img: 'https://y.qq.com/music/photo_new/T001R300x300M000003aQYLo2x8izP.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: 1,
},
{
id: 2,
author: '周杰伦',
comment: '听妈妈的话',
time: '2021-12-11 09:09:00',
img: 'https://y.qq.com/music/photo_new/T001R500x500M0000025NhlN2yWrP4.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: 0,
},
{
id: 3,
author: '陈奕迅',
comment: '十年',
time: '2021-10-11 10:09:00',
img: 'https://y.qq.com/music/photo_new/T001R500x500M000003Nz2So3XXYek.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: -1,
},
],
}

const content = (
<div className='App'>
<div className='comment-container'>
<div className='comment-head'>
<span>1 评论</span>
</div>
<div className='tabs-order'>
<ul className='sort-container'>
{state.tabs.map((item) => (
<li key={item.id} className={item.type === state.active ? 'on' : ''}>
按{item.name}排序
</li>
))}
</ul>
</div>
<div className='comment-send'>
<div className='user-face'>
<img className='user-head' src={avatar} alt='' />
</div>
<div className='textarea-container'>
<textarea cols='80' rows='5' placeholder='发条友善的评论' className='ipt-txt'></textarea>
<button className='comment-submit'>发表评论</button>
</div>
<div className='comment-emoji'>
<i className='face'></i>
<span className='text'>表情</span>
</div>
</div>
<div className='comment-list'>
<div className='list-item'>
<div className='user-face'>
<img className='user-head' src='https://y.qq.com/music/photo_new/T001R300x300M000003aQYLo2x8izP.jpg?max_age=2592000' alt='' />
</div>
<div className='comment'>
<div className='user'>刘德华</div>
<p className='text'>给我一杯忘情水</p>
<div className='info'>
<span className='time'>2021-10-10 09:09:00</span>
<span className='like liked'>
<i className='icon'></i>
</span>
<span className='hate'>
<i className='icon'></i>
</span>
<span className='reply btn-hover'>删除</span>
</div>
</div>
</div>
<div className='list-item'>
<div className='user-face'>
<img className='user-head' src='https://y.qq.com/music/photo_new/T001R500x500M0000025NhlN2yWrP4.jpg?max_age=2592000' alt='' />
</div>
<div className='comment'>
<div className='user'>周杰伦</div>
<p className='text'>听妈妈的话</p>
<div className='info'>
<span className='time'>2021-10-11 09:09:00</span>
<span className='like'>
<i className='icon'></i>
</span>
<span className='hate'>
<i className='icon'></i>
</span>
<span className='reply btn-hover'>删除</span>
</div>
</div>
</div>
<div className='list-item'>
<div className='user-face'>
<img className='user-head' src='https://y.qq.com/music/photo_new/T001R500x500M000003Nz2So3XXYek.jpg?max_age=2592000' alt='' />
</div>
<div className='comment'>
<div className='user'>陈奕迅</div>
<p className='text'>十年</p>
<div className='info'>
<span className='time'>2021-10-11 10:09:00</span>
<span className='like'>
<i className='icon'></i>
</span>
<span className='hate hated'>
<i className='icon'></i>
</span>
<span className='reply btn-hover'>删除</span>
</div>
</div>
</div>
</div>
</div>
</div>
)

ReactDOM.render(content, document.querySelector('#root'))

循环渲染评论列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import React from 'react'
import ReactDOM from 'react-dom'
import avatar from './images/avatar.png'
import './index.css'

const state = {
// hot: 热度排序 time: 时间排序
tabs: [
{
id: 1,
name: '热度',
type: 'hot',
},
{
id: 2,
name: '时间',
type: 'time',
},
],
active: 'time',
list: [
{
id: 1,
author: '刘德华',
comment: '给我一杯忘情水',
time: '2021-11-10 09:09:00',
img: 'https://y.qq.com/music/photo_new/T001R300x300M000003aQYLo2x8izP.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: 1,
},
{
id: 2,
author: '周杰伦',
comment: '听妈妈的话',
time: '2021-12-11 09:09:00',
img: 'https://y.qq.com/music/photo_new/T001R500x500M0000025NhlN2yWrP4.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: 0,
},
{
id: 3,
author: '陈奕迅',
comment: '十年',
time: '2021-10-11 10:09:00',
img: 'https://y.qq.com/music/photo_new/T001R500x500M000003Nz2So3XXYek.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: -1,
},
],
}

const content = (
<div className='App'>
<div className='comment-container'>
<div className='comment-head'>
<span>1 评论</span>
</div>
<div className='tabs-order'>
<ul className='sort-container'>
{state.tabs.map((item) => (
<li key={item.id} className={item.type === state.active ? 'on' : ''}>
按{item.name}排序
</li>
))}
</ul>
</div>
<div className='comment-send'>
<div className='user-face'>
<img className='user-head' src={avatar} alt='' />
</div>
<div className='textarea-container'>
<textarea cols='80' rows='5' placeholder='发条友善的评论' className='ipt-txt'></textarea>
<button className='comment-submit'>发表评论</button>
</div>
<div className='comment-emoji'>
<i className='face'></i>
<span className='text'>表情</span>
</div>
</div>
<div className='comment-list'>
{state.list.map((item) => (
<div key={item.id} className='list-item'>
<div className='user-face'>
<img className='user-head' src={item.img} alt='' />
</div>
<div className='comment'>
<div className='user'>{item.name}</div>
<p className='text'>{item.author}</p>
<div className='info'>
<span className='time'>{item.time}</span>
<span className={`like ${item.attitude === 1 ? 'liked' : ''}`}>
<i className='icon'></i>
</span>
<span className={`hate ${item.attitude === -1 ? 'hated' : ''}`}>
<i className='icon'></i>
</span>
<span className='reply btn-hover'>删除</span>
</div>
</div>
</div>
))}
</div>
</div>
</div>
)

ReactDOM.render(content, document.querySelector('#root'))

class 的另外一种处理方式。

1
2
3
<span className={['like', item.attitude === 1 ? 'liked' : ''].join(' ')}>
<i className='icon'></i>
</span>

最终代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import ReactDOM from 'react-dom'
import './index.css'
import avatar from './images/avatar.png'

const state = {
// hot: 热度排序 time: 时间排序
tabs: [
{
id: 1,
name: '热度',
type: 'hot',
},
{
id: 2,
name: '时间',
type: 'time',
},
],
active: 'time',
list: [
{
id: 1,
author: '刘德华',
comment: '给我一杯忘情水',
time: '2021-11-10 09:09:00',
img: 'https://y.qq.com/music/photo_new/T001R300x300M000003aQYLo2x8izP.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: 1,
},
{
id: 2,
author: '周杰伦',
comment: '听妈妈的话',
time: '2021-12-11 09:09:00',
img: 'https://y.qq.com/music/photo_new/T001R500x500M0000025NhlN2yWrP4.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: 0,
},
{
id: 3,
author: '陈奕迅',
comment: '十年',
time: '2021-10-11 10:09:00',
img: 'https://y.qq.com/music/photo_new/T001R500x500M000003Nz2So3XXYek.jpg?max_age=2592000',
// 1: 点赞 0:无态度 -1:踩
attitude: -1,
},
],
}

const content = (
<div className='App'>
<div className='comment-container'>
<div className='comment-head'>
<span>{state.list.length} 评论</span>
</div>
<div className='tabs-order'>
<ul className='sort-container'>
{state.tabs.map((item) => (
<li className={item.type === state.active ? 'on' : ''} key={item.id}>
按{item.name}排序
</li>
))}
</ul>
</div>
<div className='comment-send'>
<div className='user-face'>
<img className='user-head' src={avatar} alt='' />
</div>
<div className='textarea-container'>
<textarea cols='80' rows='5' placeholder='发条友善的评论' className='ipt-txt'></textarea>
<button className='comment-submit'>发表评论</button>
</div>
<div className='comment-emoji'>
<i className='face'></i>
<span className='text'>表情</span>
</div>
</div>
<div className='comment-list'>
{state.list.map((item) => (
<div className='list-item' key={item.id}>
<div className='user-face'>
<img className='user-head' src={item.img} alt='' />
</div>
<div className='comment'>
<div className='user'>{item.author}</div>
<p className='text'>{item.comment}</p>
<div className='info'>
<span className='time'>{item.time}</span>
<span className={item.attitude === 1 ? 'like liked' : 'like'}>
<i className='icon'></i>
</span>
<span className={item.attitude === -1 ? 'hate hated' : 'hate'}>
<i className='icon'></i>
</span>
<span className='reply btn-hover'>删除</span>
</div>
</div>
</div>
))}
</div>
</div>
</div>
)

ReactDOM.render(content, document.querySelector('#root'))

今日总结