今日目标
✔ 了解 React 的特点。
✔ 掌握使用脚手架创建项目。
✔ 掌握 JSX 的基本使用。
✔ 掌握 B 站评论列表案例。
React 介绍
学习目标
了解 React 的背景和基本概念。
React 背景
React 起源于 Facebook(Meta) 的内部项目(2011,News Feed),之后又被用来开发网站(2012,Instagram),并于 2013 年 5 月开源。
React 是什么
React 是一个用于构建用户界面的 JavaScript 库。
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 脚手架创建项目的方法。
意义
现代的 Web 应用要考虑的问题很多,除了编写业务代码之外,还要考虑代码规范、预编译、压缩合并、打包上线等工作,需要有一套完整的解决方案来辅助我们快速开发,而 React 脚手架 就是这么一套完整的解决方案,它零配置,开箱即用,让我们从繁杂的 Webpack 配置当中解脱出来,更关注于业务本身。
使用
使用 create-react-app 这个命令行工具,它是 React 官方团队出的一个构建 React 应用的脚手架工具。
方法一
全局安装npm i -g create-react-app
或者yarn global add create-react-app
。
初始化项目create-react-app my-app
,my-app 表示项目名称,可以修改。
启动项目:yarn start
or npm start
。
🙁 缺点:全局安装命令无法保证命令一直是最新版本。
方法二(推荐)
命令:npx create-react-app react-basic
。
启动项目:yarn start
or npm start
。
npx 是 npm@v5.2
版本新添加的命令,用来简化 npm 工具包的使用流程。
😄 优点:npx
会调用最新的 create-react-app
直接创建 React 项目。
小结
使用脚手架创建项目的命令是什么?
渲染自己的界面
目标
掌握通过 React 脚手架渲染页面的基本步骤。
步骤
删除 src
和 public
目录中的所有内容。
新建 public/index.html
。
新建 src/index.js
文件。
引入 React 核心库和涉及 DOM 操作的包。
调用 React.createElement()
方法创建 React 元素。
调用 ReactDOM.render()
方法渲染 React 元素到页面。
代码
删除 src
和 public
目录中的所有内容。
新建 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>
|
新建 src/index.js
文件。
引入 React 核心库和涉及 DOM 操作的包。
src/index.js
1 2 3
| import React from 'react' import ReactDOM from 'react-dom'
|
- 调用
React.createElement()
方法创建 React 元素。
1 2
| const title = React.createElement('h1', null, 'Hello World')
|
- 调用
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 基本使用
- 使用 JSX 创建 React 元素
1
| const title = <h1>Hello JSX</h1>
|
- 使用
ReactDOM.render()
方法渲染 React 元素到页面中
1
| ReactDOM.render(title, document.querySelector('#root'))
|
JSX 是如何工作的
🤔 换句话说,JSX 并不是标准的 ECMAScript 语法,为什么 React 脚手架中可以直接使用 JSX 呢?
小结
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 个根节点,或者虚拟根节点 <></>
、<React.Fragment></React.Fragment>
。
属性名一般是驼峰的写法且不能是 JS 中的关键字,例如 class 改成 className,label 的 for 属性改为 htmlFor
,colspan 改为 colSpan
。
元素若没有子节点,可以使用单标签,但一定要闭合,例如 <span/>
。
React@16.14
之前需要先引入 React 才能使用 JSX(这个也好理解,因为 JSX 最后还是要被转成 React.createElement()
的形式)。
换行建议使用 ()
进行包裹,防止换行的时候自动插入分号的 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>
|
注意
1 2
| const span = <span>我是一个span</span> const title = <h1>盒子{span}</h1>
|
小结
条件渲染
目标
掌握条件渲染的写法。
内容
📝 需求: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> }
|
小结
条件渲染使用__ 和 __?
列表渲染
目标
需求
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
特点:key 值要保证唯一,尽量避免使用索引号,key 在最终的 HTML 结构中是看不见的。
加在哪里:map()
遍历谁,就把 key 加在谁上。
作用: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
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>
|
小结
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: 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 = { 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', 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', 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', 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 = { 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', 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', 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', 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 = { 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', 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', 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', 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 = { 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', 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', 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', 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'))
|
今日总结