react
如果你对我们的代码以及演示有一些指导,可以在博客中为我们留言,我们将虚心采纳你的留言,同样的为你提供我们的联系方式wechat:wxid_i807by3pqdok22
开发环境的搭建
这里我们开始讲解react,这里我们用的版本是react18,而创建项目的脚手架为npx create-react-app react-basic,对于已经创建好的文件我们只保留src下的index.js 和App.js,这里我们看一下目录结构。
而index.js为我们文件的启动入口,这里我们可以看到,raect将文件以组建的方式引入,同时通过consta渲染到客户端。


我们目前所要展示的页面效果为App.js。这里我们可以先看一下这个页面的基础构成,可以看出我们的底部依旧运用的html,相信你对这个前端技术并不陌生,react拥有很强的包容性,在其盒子的内部,你可以运用html来搭建你的页面,而这个页面也被我们俗称为根组件,接下来我们的基础知识的讲解也会围绕这一页面展开
function App() {
return (
<div className="App">
<h1>My first React </h1>
</div>
);
}
export default App;

这里我们测试一下这个端口,以便你可以更好的理解它所实现的效果

识别js表达式
我们都知道,在一般情况下是无法在vscode中直接运行js文件的,以前有一种古老的手法就是创建html文件再导入,渲染出去。在代码逻辑上也会更加的凌乱,所以这里我们可以换一种更好用的方式,以便你可以轻松的上手。而在jsx中可以通过大括号{}识别javaScript中的表达式,比如常见的变量,函数调用,这里我们来列举几个高频度常用的场景
- 使用引号传递字符串
- 使用javaScript变量
- 函数的调用以及方法的调用
- 使用javaScripta对象
同样的我们可以看一下代码会发现,react是作为一个前端框架内部如同nextjs一样支持HTML语言,这也就以为这你可以通过HTMl实现一些你想要的效果,不过在此之前让我们看一下演示的代码,以及页面效果。这里我们实现了上述的几个场景,同时也可以知道,react作为一个前端框架有很强的包容性,我们可以通过const来定义一些函数,同时也可以通过style实现js的页面样式效果。
// App.js
const doubao ={
name:'豆包',
age:2,
imageUrl:'https://tse2.mm.bing.net/th/id/OIP.AJJgUYZVBHqKBhYlhAivmwHaNK?rs=1&pid=ImgDetMain',
imageSize: '200px',
}
// 函数的调用
function sayHello(){
return 'hello'
}
function App() {
return (
<div className="App">
<h1>My first React </h1>
{/* 1.使用引号传递字符串 */}
{'This is a string'}
{/*2. 使用javascript变量*/}
<br/>
{doubao.name}
<img src={doubao.imageUrl} alt="cat" style={{width:doubao.imageSize}}/>
<br/>
{/* 函数调用 */}
{sayHello()}
<br/>
{/* 方法调用 */}
{new Date().toLocaleTimeString()}
<br/>
{/* 4. 使用js对象属性 */}
<br/>
<div style={{backgroundColor:'lightblue',width:'100px',height:'100px'}}>
this is a box
</div>
</div>
);
}
export default App;
可能第一眼你会觉得代码比较凌乱,所以这里我们可以看一下页面的展示效果。以便你更好的理解结构

这里我们对代码进行了一些优化,同时引入了一些react的基础知识,以便你可以通过该代码简单实现一些页面的效果,这里我们将一些部分引入到div盒子当中,利用javaScript修改盒子样式,同时通过const来定义一些函数
点击查看代码
// App.js
const doubao ={
name:'豆包',
age:2,
imageUrl:'https://tse2.mm.bing.net/th/id/OIP.AJJgUYZVBHqKBhYlhAivmwHaNK?rs=1&pid=ImgDetMain',
imageSize: '200px',
}
// 函数的调用
function sayHello(){
return 'hello'
}
function App() {
return (
<div className="App">
{'This is a string'}
{/* 方法调用 */}
<br/>
{/* 4. 使用js对象属性 */}
<br/>
<div style={{backgroundColor:'lightblue',width:'750px',height:'750px'}}>
<br/>
<h1 style={{textAlign:'center'}}>个人档案</h1>
<br/>
{/* 修改后的蓝色盒子容器 */}
<div style={{
position: 'fixed',
bottom: 20,
left: 20,
backgroundColor: '#e6f7ff',
padding: '16px',
borderRadius: '8px',
width: '300px',
position: 'relative', // 父容器需设置相对定位
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}}>
{/* 文字内容容器 */}
<div>
<p style={{ margin: '8px 0', color: '#333' ,width: '200px'}}>
Hello, I am {doubao.name} and I am {doubao.age} years old.
</p >
<p style={{ fontWeight: 'bold' }}>this is a box</p >
{new Date().toLocaleTimeString()}
</div>
{/* 图片容器(右侧) */}
< img
src="https://tse2.mm.bing.net/th/id/OIP.AJJgUYZVBHqKBhYlhAivmwHaNK?rs=1&pid=ImgDetMain"
alt="silver-haired woman"
style={{
width: '60px',
borderRadius: '50%',
position: 'absolute', // 绝对定位
right: '16px', // 右侧间距
top: '50%', // 垂直居中
transform: 'translateY(-50%)' // 垂直居中修正
}}
/>
</div>
</div>
</div>
);
}
export default App;

实现列表渲染
原生的jsx是支持js中的map方法来遍历渲染列表,这里我们同样在头部定义一个数组名为label,内部通过label实现map方法从而遍历我们的数组。同时key为数组的唯一值,当你在实现的时候key在内部是作为一个索引在堆中存放。
const label =[
{id: 1, name: "猫条"},
{id: 2, name: "鱼"},
{id: 3, name: "猫薄荷"},
]
function list() {
return(
<div className="list">
<h1>爱好</h1>
<ul>
{label.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
)
}
export default list;
这里我们可以看一下效果,在页面布局中。同时这个页面你可以单独创建或则是引入为组建,而同样的你也可以用来渲染一些列表信息

条件渲染
含义是根据不同的条件渲染不同的模板,这里我们能举个例子,为了方便你能够快速理解。场景:当你使用的身份不符合条件的时候为false,而符合条件的时候为true

而在react中可以通过逻辑运算符&&,三元表达式 (?:),这里我们先实现一个小的案例以便你可以更好的理解其效果。
const username ="豆包"
const isLogin1 = true;
const isLogin2 = false;
function App() {
return (
<div className="App">
{/* 逻辑与 && 运算符 */}
{isLogin1 && <div style={{border: "1px solid red", padding: "10px"}}>欢迎回来,{username}!</div>}
{/* 三元运算 */}
{isLogin2 ? <div style={{border: "1px solid red", padding: "10px"}}>欢迎回来,{username}!</div> : <div style={{border: "1px solid blue", padding: "10px"}}>请登录</div>}
</div>
);
}
export default App;
同样的我们看一下页面效果,你会发现当你通过修改逻辑islogin的值就可以控制页面的显示效果,三元运算符是在逻辑运算符的基础上,增加一个条件,当条件不满足时,则执行后者。

复杂条件进行渲染
我们可以平时看到很多推送的信息有时候是没有带图片,又或者是一些相关的链接。对于这种列表有着不同的控制效果,这个时候要是再用传统的条件渲染使我们的工作量也会增加。因此这里react为我们封装了复杂的条件渲染。** 模拟场景:**我们要实现一个列表,包含三种适配情况 单图,三图,无图。
// 定义文章类型
const articleType = 3; // 0 无图 1 一个图片 3 三个图片
function getArticleType() {
if(articleType === 0) {
return '无图'
}
else if(articleType === 1) {
return '一个图片'
}
else if(articleType === 3) {
return '三个图片'
}
}
function App() {
return (
<div>
{/* 函数调用渲染不同文章类型 */}
<div>{getArticleType()}</div>
</div>
);
}
export default App;
事件绑定
我们知道前端的所有交互效果都是通过事件来实现的,这也是我们ract的常用的一种手法。我们可以通过定义事件从而达到多种效果,而react中常用的事件语法是 ON+事件名字={事件处理程序},这里我会做一个大的案例,能够让你可以快速的实现一些自己想要的效果,同样的我们会为你提供源码。场景:当我们点击增加宽度的时候会改变盒子的宽度,出现盒子变宽的消息输出 当点击重置宽度的时候会让页面宽度回到改变前。
同样的我们需要在顶部通过const来去定义事件,这样才能实现我们的事件绑定,同样的react也可以实现带参构造。能够满足你对于事件的完整性的一个补充。

APP.js
import { useState } from 'react';
function App() {
// 状态定义
// 盒子宽度
const [boxWidth, setBoxWidth] = useState(500);
const [message, setMessage] = useState('');
// 点击处理(纯React方式)
const handleClick = () => {
setBoxWidth(prev => prev + 100);
setMessage('盒子变宽啦! (+100px)');
setTimeout(() => setMessage(''), 2000);
};
// 重置处理
const handleReset = () => {
setBoxWidth(500);
setMessage('宽度已重置!');
// 设置时间为2秒后清空消息提示
setTimeout(() => setMessage(''), 2000);
};
return (
<div>
<h1>Hello React</h1>
{/* 动态宽度盒子 */}
<div style={{
width: `${boxWidth}px`,
height: "500px",
backgroundColor: "lightblue",
transition: "width 0.3s ease"
}}>
{/* 消息提示层 */}
{message && (
<div className="message-popup">
{message}
</div>
)}
</div>
{/* 操作按钮 */}
<button onClick={handleClick}>点击增加宽度</button>
<button onClick={handleReset}>重置宽度</button>
</div>
);
}
export default App;
这里我们来看一下页面效果,当你点击增加宽度的时候就会增加100px大小,再点击重置就会回到最初大小,我们之所以以这个案例是因为考虑到目前很多网站会用这样的效果来美化页面。

组件的基本使用
首先我们可以先对组件作为一个了解,稍后我们会做一个案例进行演示。
一个组件就是用户界面的一部分,它可以有自己的逻辑和外观,而组件之间可以相互嵌套多次使用。倘若你有一些vue前端的知识,会很常用以组件的方式来构建我们的页面,就如同我们下方的演示图片。同样的我们可以把组件理解为模板。

而在react中组件的使用,可以理解为一个首字母大写的函数,内部存放了一些视图以及逻辑关系,而对于组件的使用,你只需要把它当成标签用即可,这里让我们做一个小案例来实现组件的效果。
这里我们用组件来模拟一个计算器,你可以控制数字的加减。 首先我们在头部定义一个计算器,内部返回两个按钮 ,最后我们只需要在盒子中使用这个计算器即可。这里我们先看一下代码
import { useState } from 'react';
// 定义一个组件
// 组件的功能是显示一个计数器,并可以增加、减少计数器的值
function Counter() {
const [count, setCount] = useState(0); // useState 是一个Hook,用来在函数组件中存储状态
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count - 1)}> - </button>
<button onClick={() => setCount(count + 1)}> + </button>
</div>
)
}
// 定义一个名为App的函数
function App() {
// 返回一个div元素,包含一个h1元素和一个减号按钮,以及一个加号按钮
return (
<div className="App">
<Counter />
</div>
);
}
export default App;
同样的我们可以来看一下页面效果。当你点击按钮即可控制数字

useState基本使用
useState是一个ReactHook,它允许我们在组件中添加一个状态变量。从而影响渲染结果。这里我们举一个例子,当我们输出的结果为0时,盒子的颜色为红色。当为1时,盒子的颜色为蓝色。这给我们传递了一个信息。当我们改变状态变量的时候,视图也会同样的发生变化。
而对于状态变量的使用这里我们以一个图为例

这里我们来做一个小的案例,当你在网站点击下拉框的时候会出现子菜单。我们目前就是基于状态变量来实现这个功能。这里我们为你准备好了代码。
import { useState } from 'react';
function SettingsMenu() {
// 控制下拉展开状态
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
// 精准对应图片中的子菜单项
const subItems = [
"编辑器设置",
"使用TypeScript",
"React开发人员工具",
"React编译器"
];
return (
<div className="menu-item">
{/* 主菜单项 */}
<div
className="menu-header"
onClick={() => setIsSettingsOpen(!isSettingsOpen)}
>
<span>设置</span>
<span className={`arrow ${isSettingsOpen ? 'open' : ''}`}>▼</span>
</div>
{/* 动态渲染下拉列表 */}
{isSettingsOpen && (
<div className="submenu">
{subItems.map((item, index) => (
<div
key={index}
className="submenu-item"
onClick={() => console.log('Selected:', item)}
>
{item}
</div>
))}
</div>
)}
</div>
);
}
export default SettingsMenu;
useState 返回一个数组,第一个元素是状态值,第二个元素是更新状态的函数,我们通过subItems数组来渲染下拉列表的子项。这里我们为主菜单项添加了一个箭头图标,当点击主菜单项时,下拉列表的展开状态会切换。当下拉列表展开时,才渲染子菜单项, 这里我们使用了map函数来渲染子菜单项, key属性为子菜单项的索引值,onClick事件为点击子菜单项时触发的函数。
接下来让我们能看一下页面的效果,当我们点击箭头的时候,页面出现下拉子菜单。

useState的使用规则
在react中,状态被认为是只读,我们应该始终替换它而不是修改,直接修改状态并不会引发视图更新,这里我们以图为例,这里我们作为一个知识点的补充。先不做演示代码。同样的你也可以根据自己的想法,去做一个测试,来更详细的明白其中的内在逻辑

基础样式控制
react为我们准备了两种样式的控制。
* 行内样式控制 :类似于我们说的** style{{}}**
* 类名控制样式: .<类名>
这里也是我们平时最常用的方式,如果你有一些html的基础就会发现,react也会包含同样的一些样式方式,接下来我们做一个演示。依据我们之前的案例。这里我们为设置设计一个边框

同样的我们可以看一下样式,

当然这样看起来有些过于简略,这里我们利用类名控制来为下拉列表做一个样式的补充,同样的我们需要在头部引入样式文件。创建index.css样式。这里我们只设置了子菜单样式。目的是让我们的对比可以更明显
import './index.css';
这里我们来看一下样式文件,这里我们利用类名控制的方式来实现样式的改变。同样的我们会为你展示样式效果。通过不同的引用样式的方式,从而实现样式的多样性。


这里我们来开始做一B站评论功能的案例,这里的话帮助你以后更好的了解react的实现机制,这里我们会为你准备好代码
一:列表渲染
这里的代码会比较多,所以这里我们采用折叠的代码块
点击查看代码
import './App.scss'
import avatar from './images/bozai.png'
import React, { useState } from 'react'
/**
* 评论列表的渲染和操作
*
* 1. 根据状态渲染评论列表
*
* 2. 删除评论
*/
// 评论列表数据
const list = [
{
// 评论id
rpid: 3,
// 用户信息
user: {
uid: '13258165',
avatar: '',
uname: '白鹿为溪',
},
// 评论内容
content: '喵喵呜',
// 评论时间
ctime: '10-18 08:15',
like: 88,
},
{
rpid: 2,
user: {
uid: '36080105',
avatar: '',
uname: '豆包二号',
},
content: '这是一条测试评论',
ctime: '11-13 11:29',
like: 88,
},
{
rpid: 1,
user: {
uid: '30009257',
avatar,
uname: '黑马前端',
},
content: '月薪过万',
ctime: '10-19 09:00',
like: 66,
},
]
// 当前登录用户信息
const user = {
// 用户id
uid: '30009257',
// 用户头像
avatar,
// 用户昵称
uname: '黑马前端',
// 插入用户图片
}
/**
* 导航 Tab 的渲染和操作
*
* 1. 渲染导航 Tab 和高亮
* 2. 评论列表排序
* 最热 => 喜欢数量降序
* 最新 => 创建时间降序
*/
// 导航 Tab 数组
const tabs = [
{ type: 'hot', text: '最热' },
{ type: 'time', text: '最新' },
]
const App = () => {
const [commentList, setCommentList] = useState(list)
return (
<div className="app">
{/* 导航 Tab */}
<div className="reply-navigation">
<ul className="nav-bar">
<li className="nav-title">
<span className="nav-title-text">评论</span>
{/* 评论数量 */}
<span className="total-reply">{10}</span>
</li>
<li className="nav-sort">
{/* 高亮类名: active */}
<span className='nav-item'>最新</span>
<span className='nav-item'>最热</span>
</li>
</ul>
</div>
<div className="reply-wrap">
{/* 发表评论 */}
<div className="box-normal">
{/* 当前用户头像 */}
<div className="reply-box-avatar">
<div className="bili-avatar">
<img className="bili-avatar-img" src={avatar} alt="用户头像" />
</div>
</div>
<div className="reply-box-wrap">
{/* 评论框 */}
<textarea
className="reply-box-textarea"
placeholder="发一条友善的评论"
/>
{/* 发布按钮 */}
<div className="reply-box-send">
<div className="send-text">发布</div>
</div>
</div>
</div>
{/* 评论列表 */}
<div className="reply-list">
{/* 评论项 */}
{/* 遍历渲染评论项 */}
{commentList.map(item =>(
<div key={item.rpid} className="reply-item">
{/* 头像 */}
<div className="root-reply-avatar">
<div className="bili-avatar">
<img
className="bili-avatar-img"
alt=""
src={item.user.avatar || user.avatar} // 使用用户头像或默认头像
/>
</div>
</div>
<div className="content-wrap">
{/* 用户名 */}
<div className="user-info">
<div className="user-name">{item.user.uname}</div>
</div>
{/* 评论内容 */}
<div className="root-reply">
<span className="reply-content">{item.content}</span>
<div className="reply-info">
{/* 评论时间 */}
<span className="reply-time">{item.ctime}</span>
{/* 评论数量 */}
<span className="reply-time">点赞数:{item.like}</span>
<span className="delete-btn">
删除
</span>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
)
}
export default App
我们来对这段代码进行一个解释,以便你可以更好的理解,从而根据这个案例你可以实现,自己想要的效果。我们在顶部引入了所需要的模块,同时接着定义了两个数组,然后对于数组的控制显示我们采用的是状态控制。
并在列表中遍历出我们所要的数组,
这里你可能导入的时候会出现一些配置上的错误,其原因是缺少react-script的依赖包 ,你可以在你的vscode终端输入npm install react-script --save。同样的该文件还包含一个样式文件APP.scss
点击查看代码
.app {
width: 80%;
margin: 50px auto;
}
.reply-navigation {
margin-bottom: 22px;
.nav-bar {
display: flex;
align-items: center;
margin: 0;
padding: 0;
list-style: none;
.nav-title {
display: flex;
align-items: center;
width: 114px;
font-size: 20px;
.nav-title-text {
color: #18191c;
font-weight: 500;
}
.total-reply {
margin: 0 36px 0 6px;
color: #9499a0;
font-weight: normal;
font-size: 13px;
}
}
.nav-sort {
display: flex;
align-items: center;
color: #9499a0;
font-size: 13px;
.nav-item {
cursor: pointer;
&:hover {
color: #00aeec;
}
&:last-child::after {
display: none;
}
&::after {
content: ' ';
display: inline-block;
height: 10px;
width: 1px;
margin: -1px 12px;
background-color: #9499a0;
}
}
.nav-item.active {
color: #18191c;
}
}
}
}
.reply-wrap {
position: relative;
}
.box-normal {
display: flex;
transition: 0.2s;
.reply-box-avatar {
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 50px;
}
.reply-box-wrap {
display: flex;
position: relative;
flex: 1;
.reply-box-textarea {
width: 100%;
height: 50px;
padding: 5px 10px;
box-sizing: border-box;
color: #181931;
font-family: inherit;
line-height: 38px;
background-color: #f1f2f3;
border: 1px solid #f1f2f3;
border-radius: 6px;
outline: none;
resize: none;
transition: 0.2s;
&::placeholder {
color: #9499a0;
font-size: 12px;
}
&:focus {
height: 60px;
background-color: #fff;
border-color: #c9ccd0;
}
}
}
.reply-box-send {
position: relative;
display: flex;
flex-basis: 86px;
align-items: center;
justify-content: center;
margin-left: 10px;
border-radius: 4px;
cursor: pointer;
transition: 0.2s;
& .send-text {
position: absolute;
z-index: 1;
color: #fff;
font-size: 16px;
}
&::after {
position: absolute;
width: 100%;
height: 100%;
background-color: #00aeec;
border-radius: 4px;
opacity: 0.5;
content: '';
}
&:hover::after {
opacity: 1;
}
}
}
.bili-avatar {
position: relative;
display: block;
width: 48px;
height: 48px;
margin: 0;
padding: 0;
border-radius: 50%;
}
.bili-avatar-img {
position: absolute;
top: 50%;
left: 50%;
display: block;
width: 48px;
height: 48px;
object-fit: cover;
border: none;
border-radius: 50%;
image-rendering: -webkit-optimize-contrast;
transform: translate(-50%, -50%);
}
// 评论列表
.reply-list {
margin-top: 14px;
}
.reply-item {
padding: 22px 0 0 80px;
.root-reply-avatar {
position: absolute;
left: 0;
display: flex;
justify-content: center;
width: 80px;
cursor: pointer;
}
.content-wrap {
position: relative;
flex: 1;
&::after {
content: ' ';
display: block;
height: 1px;
width: 100%;
margin-top: 14px;
background-color: #e3e5e7;
}
.user-info {
display: flex;
align-items: center;
margin-bottom: 4px;
.user-name {
height: 30px;
margin-right: 5px;
color: #61666d;
font-size: 13px;
line-height: 30px;
cursor: pointer;
}
}
.root-reply {
position: relative;
padding: 2px 0;
color: #181931;
font-size: 15px;
line-height: 24px;
.reply-info {
position: relative;
display: flex;
align-items: center;
margin-top: 2px;
color: #9499a0;
font-size: 13px;
.reply-time {
width: 86px;
margin-right: 20px;
}
.reply-like {
display: flex;
align-items: center;
margin-right: 19px;
.like-icon {
width: 14px;
height: 14px;
margin-right: 5px;
color: #9499a0;
background-position: -153px -25px;
&:hover {
background-position: -218px -25px;
}
}
.like-icon.liked {
background-position: -154px -89px;
}
}
.reply-dislike {
display: flex;
align-items: center;
margin-right: 19px;
.dislike-icon {
width: 16px;
height: 16px;
background-position: -153px -153px;
&:hover {
background-position: -217px -153px;
}
}
.dislike-icon.disliked {
background-position: -154px -217px;
}
}
.delete-btn {
cursor: pointer;
&:hover {
color: #00aeec;
}
}
}
}
}
}
.reply-none {
height: 64px;
margin-bottom: 80px;
color: #99a2aa;
font-size: 13px;
line-height: 64px;
text-align: center;
}
这里我们来看一下页面的显示效果,希望你可以通过这个案例来实现一下自己想要的功能。

二:删除评论功能
这里我们在此基础上实现删除的功能,同时为了演示好我们的案例,这里我们会先了解一下我们的需求。
- 只有自己的评论才会显示删除的按钮
- 点击删除按钮,删除当前的评论,同时不会出现在列表中
这里同样的我们做每一个步骤都需要有一个整体的思路, 删除显示——条件渲染的一个控制,删除功能——拿到当前的id并对id为条件的列表进行一个filter过滤。
好的让我们开始演示代码,首先我们先实现第一个功能,删除按钮的显示,这里我们是根据我们的 当我们的用户id和评论用户id相等时,显示删除按钮 。同时为删除按钮设置一个绑定的事件

这里我们定义了一个 handleDel 函数,它接收一个参数 id,表示要删除的评论的 id。 在函数内部,我们使用 filter 方法来过滤掉要删除的评论,然后使用 setCommentList 函数来更新评论列表的状态。

同样的当我们打开查看页面效果的时候会发现只有我们自己发送的评论可以实现删除功能


浙公网安备 33010602011771号