React是什么?
React是由Meta公司研发,也就是Facebook的公司(马克扎克伯格这个见人)研发的构建Web和原生交互界面的库
不仅可以写网页,还可以写苹果和安卓上面的app
React的优势:

React也是前端里最流行的一个框架
搭建环境
使用cra快速搭建开发环境,create-react-app是一个快速 创建React开发环境的工具,底层由Webpack构建,封装了配置细节,开箱即用

首先安装node.jsNode.js安装及环境配置超详细教程【Windows系统】_windows 安装nodejs-CSDN博客




bash
# 安装 nvm(如果尚未安装)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
#输入这个命令记得翻墙
# 重新加载 shell 配置
source ~/.bashrc # 或者 source ~/.zshrc(如果你使用 zsh)
# 安装最新版本的 Node.js
nvm install --lts
# 或者安装特定版本(例如 v16)
nvm install 16
# 设置默认版本
nvm alias default 16

bash
node -v # 应该显示 v14 或更高版本
npm -v # 确保 npm 也更新到最新版本

安装完node.js创建一个react文件
bash
npx create-react-app react-basic

这就代表好了!




好了
启动npm:
bash
npm start
出现这个画面说明你的项目创建成功并且可以启动了


介绍项目

package.json是项目的包文件,里面包括了一些核心包
选中部分为本次项目它依赖的一些核心包

下面这俩是最最最核心的包
javascript
"react": "^19.0.0",
"react-dom": "^19.0.0",
下面这段包含的是可执行的命令,我们刚刚启动npm的命令就来自于这里
常用的命令有start和build,build负责打包

这是src,是我们的源码目录
打开index.js,对里面的文件做清理
javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
对App.js做清理
javascript
function App() {
return (
<div className="App">
this is App
</div>
);
}
export default App;
如果出现这个界面说明清理工作一切正常

index.js是整个项目的入口,下面框出来的是react依赖的两个核心包

这个id为root的节点在public里的index.html里

所以index.js做的事就是把这个项目的根组件以React的方式渲染到public文件里的这个位置上👆
App.js是一切项目的根基,组件是从这个地方往下发散的,它被引入到index.js里,然后渲染到了public里的index.html里叫root的div里
App->index.js->public/index.html(root)
后面的基础语法都在App.js里
JSX基础
概念和本质
JSX:JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中编写
UI模版的方式
下图框出来的地方就是JSX,他书写的地方在函数的内部
优势(经典既要又要):
- HTML的声明式模版写法 2. JS的可编程能力
JSX的本质
JSX并不是标准的JS语法,它是 JS的语法扩展 ,浏览器本身不能识别,需要通过 解析工具做解析 之后才能在浏览器中运行
JSX->babel解析->js函数
使用BABEL转化工具体验一下
高频场景
在JSX中可以通过 大括号语法{} 识别 JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等
javascript
const count = 100
function getName() {
return '荷叶饭'
}
function App() {
return (
<div className="App">
this is App
{//使用引号传递字符串
'this is message'
}
{//识别JavaScript变量
count
}
{//函数调用
getName()
}
{//方法调用
new Date().getDate()
}
{//使用js对象
<div style={{ color: 'red' }}>this is div</div>
}
</div>
);
}
export default App;
只能写表达式,不能写语句(if语句、switch语句、变量声明属于语句,不是表达式,不能出现在{}中)

eg:实现列表渲染
循环哪个结构就在App方法里return哪个结构:
key是React里提升框架性能的,独一无二,赋值为字符串或number id
javascript
const list = [
{ id: 1001, name: 'Vue' },
{ id: 1002, name: 'React' },
{ id: 1003, name: 'Angular' }
]
function App() {
return (
<div className="App">
this is App
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
export default App;
愤怒
又好了嘻嘻
实现基础条件渲染
条件渲染就是根据条件进行渲染,有名字渲染名字,没名字渲染请登录
多种条件渲染的语法:在React中,可以通过逻辑与运算符&&(两种情况)、三元表达式(?:)(多种情况)实现基础的条件渲染
逻辑与&&,短路运算符
如果左侧的值为
true
,则返回右侧的值。如果左侧的值为
false
,则直接返回false
,不会计算右侧的值。
javascript
const isLogin=true
function App() {
return (
<div className="App">
this is App
{/* 逻辑与&&,短路运算符 */}
{isLogin&&<span>this is span</span>}
</div>
);
}
export default App;
三元表达式不用多说
javascript
const isLogin=false
function App() {
return (
<div className="App">
this is App
{/* 逻辑与&&,短路运算符 */}
{isLogin && <span>this is span</span>}
{/* {三元表达式} */}
{isLogin?<span>荷叶饭</span>:<span>loading...</span>}
</div>
);
}
export default App;

一想到这个玩意在后盾人里的课可能两句话就带过去了我就想笑
实现复杂的条件渲染
复杂的就是当有多种情况需要渲染时,&&和三元表达式不能满足要求
比如新闻渲染的时候有的没有图片,有的一张图片,有的三张图片,单独写一个函数来处理
javascript
const articleType = 1//0,1,3
//定义核心函数:根据不同文章类型返回不同JSX模板
function getArticleTem() {
if (articleType === 0) {
return <div>我是无图文章</div>
} else if (articleType === 1) {
return <div>我是单图文章</div>
} else {
return <div>我是多图文章</div>
}
}
function App() {
return (
<div className="App">
this is App
{getArticleTem()}
</div>
);
}
export default App;

React中的事件绑定
语法: on + 事件名称 = { 事件处理程序 } ,整体上遵循驼峰命名法
javascript
function App() {
const handleClick = () => console.log('button被点击了')
return (
<div className="App">
<button onClick={handleClick}>click me</button>
</div>
);
}
export default App;

当事件绑定需要获取事件对象e时, 语法:在事件回调函数中 设置形参e,和原生js是一样的
javascript
function App() {
const handleClick = (e) => console.log('button被点击了e:',e)
return (
<div className="App">
<button onClick={handleClick}>click me</button>
</div>
);
}
export default App;
可以获取事件对象捏

传递自定义参数:事件绑定的位置改造成箭头函数的写法,在执行clickHandler实际处理业务函数的时候传递实参
需要传递额外的参数,才需要使用箭头函数(如 onClick={() => handleClick('荷叶饭')}
)。
javascript
function App() {
const handleClick = (name) => console.log('button被点击了name:',name)
return (
<div className="App">
<button onClick={()=>handleClick('荷叶饭')}>click me</button> </div>
);
}
export default App;

这个箭头函数是必须的,如果写成onClick={handleClick('荷叶饭')}
,handleClick
函数会在渲染时立即执行,而不是在点击时执行,箭头函数可以延迟函数的执行,直到点击事件发生。
箭头函数的等价写法
javascript
<button onClick={function() { handleClick('荷叶饭'); }}>click me</button>
但是不简洁,更推荐箭头函数
如果我们既要有要,既要事件对象e,也要额外参数怎么解决?
箭头函数传参数给普通函数 函数再去接收参数
javascript
function App() {
const handleClick = (name,e) => console.log('button被点击了name:',name,'e:',e)
return (
<div className="App">
<button onClick={(e)=>handleClick('荷叶饭',e)}>click me</button>
</div>
);
}
export default App;

组件
概念:一个组件就是用户界面的一部分,它可以有自己的逻辑和外观,组件之间 可以互相嵌套,也可以复用多次

root是根组件,header是左边的灰块的上部分,main是左侧在主体区域,aside是右边的
main里重复的两块就是article*2,aside里重复的就是item*3
组件化开发可以让开发者像搭积木一样构建一个完整的庞大的应用
流程:定义组件->使用组件
在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI, 渲染组件只需要把组件当成标签书写
即可
两种使用方法
javascript
//定义组件
function Button() {
//组件内部逻辑
return <button>click me!</button>
}
function App() {
// const handleClick = (name,e) => console.log('button被点击了name:',name,'e:',e)
return (
<div className="App">
{/*使用组件(渲染组件)*/}
{/*自闭和使用*/}
<Button />
{/*成对标签使用*/}
<Button></Button>
</div>
);
}
export default App;
组件其实就是个函数,比较灵活,首字母大写,有两种使用方法:自闭和和成对标签,像标签一样灵活
useState基础使用
useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量(State), 从而控制影响组件的渲染结果
状态变量是 React 组件内部的一个特殊变量,用于存储组件的数据,当状态变量的值发生变化时,React 会自动重新渲染组件,以反映最新的数据
状态变量 和普通JS变量不同的是,状态变量一旦发生变化组件的视图UI 也会跟着变化 (数据驱动视图)
useState是React里的方法,返回的是个数组,数组里有两项:[状态变量,修改状态变量的方法]
只有用这个专门的修改状态变量的方法修改变量,才会在ui渲染里起效
javascript
//useState实现一个计数器按钮
import { useState } from 'react'
function App() {
// const handleClick = (name,e) => console.log('button被点击了name:',name,'e:',e)
//1、调用useState添加一个状态变量
const [count, setCount] = useState(0)
//2、点击事件回调
const handleClick = () => { setCount(count+1) }
return (
<div className="App">
<button onClick={handleClick}>{count}</button>
</div>
);
}
export default App;
count++
会直接修改 count
的值,而 React 的状态变量是不可变的(Immutable),不能直接修改。

那我就要问了:const handleClick = () => { setCount(count++) }这么写不也是通过setState 函数更新状态吗?为什么不行?

count++
会先返回当前的值,再进行自增操作,而 React 的 setState
需要传入新的值,而不是修改原值。
那++count呢?++count
会先对 count
进行自增操作,然后返回自增后的值。这个操作会直接修改当前 count
的值,而 React 需要的是通过 setCount
来触发状态的更新,这就相当于状态变量的修改是你自己改的,不是通过对应的方法改的
正确的写法是应该直接传入 count + 1
,而不是 count++
,因为 count++
只是修改了本地变量
对于对象来说也是这样,应该把新的对象写进对应方法,而不是直接修改
javascript
import { useState } from 'react'
function App () {
let [count, setCount] = useState(0)
const handleClick = () => {
// 直接修改 无法引发视图更新
// count++
// console.log(count)
setCount(count + 1)
}
// 修改对象状态
const [form, setForm] = useState({ name: 'jack' })
const changeForm = () => {
// 错误写法:直接修改
// form.name = 'john'
// 正确写法:setFrom 传入一个全新的对象
setForm({
...form,
name: 'john'
})
}
return (
<div>
<button onClick={handleClick}>{count}</button>
<button onClick={changeForm}>修改form{form.name}</button>
</div>
)
}
export default App

组件基础样式方案

javascript
//useState实现一个计数器按钮
import { useState } from 'react'
function App() {
// const handleClick = (name,e) => console.log('button被点击了name:',name,'e:',e)
//1、调用useState添加一个状态变量
const [count, setCount] = useState(0)
//2、点击事件回调
const handleClick = () => { setCount(count+1) }
return (
<div className="App">
{/* //行内控制样式 */}
<button onClick={handleClick} style={{color:'red',fontSize:'50px'}}>{count}</button>
</div>
);
}
export default App;
行内控制样式
也可以封装出来:
javascript
const style = {
color: 'red',
fontSize:'50px'
}
//useState实现一个计数器按钮
import { useState } from 'react'
function App() {
// const handleClick = (name,e) => console.log('button被点击了name:',name,'e:',e)
//1、调用useState添加一个状态变量
const [count, setCount] = useState(0)
//2、点击事件回调
const handleClick = () => { setCount(count+1) }
return (
<div className="App">
{/* //行内控制样式 */}
<button onClick={handleClick} style={style}>{count}</button>
</div>
);
}
export default App;
也可以使用类名外部引入
javascript
//导入样式
import './index.css'
function App() {
// const handleClick = (name,e) => console.log('button被点击了name:',name,'e:',e)
//1、调用useState添加一个状态变量
const [count, setCount] = useState(0)
//2、点击事件回调
const handleClick = () => { setCount(count + 1) }
return (
<div className="App">
<button onClick={handleClick} className='foo'>{count}</button>
</div>
);
}
export default App;
//index.css
.foo{
color:blue
}

案例:B站评论
javascript
import { useEffect, useRef, useState } from 'react'
import './App.scss'
import avatar from './images/bozai.png'
import _ from 'lodash'
import classNames from 'classnames'
import { v4 as uuidV4 } from 'uuid'
import dayjs from 'dayjs'
import axios from 'axios'
// 当前登录用户信息
const user = {
// 用户id
uid: '30009257',
// 用户头像
avatar,
// 用户昵称
uname: '黑马前端',
}
// 导航 Tab 数组
const tabs = [
{ type: 'hot', text: '最热' },
{ type: 'time', text: '最新' },
]
// 封装请求数据的Hook
function useGetList () {
// 获取接口数据渲染
const [commentList, setCommentList] = useState([])
useEffect(() => {
// 请求数据
async function getList () {
// axios请求数据
const res = await axios.get(' http://localhost:3004/list')
setCommentList(res.data)
}
getList()
}, [])
return {
commentList,
setCommentList
}
}
// 封装Item组件
function Item ({ item, onDel }) {
return (
<div className="reply-item">
{/* 头像 */}
<div className="root-reply-avatar">
<div className="bili-avatar">
<img
className="bili-avatar-img"
alt=""
src={item.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>
{/* 条件:user.id === item.user.id 的情况,才显示删除按键*/}
{user.uid === item.user.uid &&
<span className="delete-btn" onClick={() => onDel(item.rpid)}>
删除
</span>}
</div>
</div>
</div>
</div>
)
}
const App = () => {
// 渲染评论列表
// 1. 使用useState维护list
// const [commentList, setCommentList] = useState(_.orderBy(list, 'like', 'desc'))
const { commentList, setCommentList } = useGetList()
// 删除功能
const handleDel = (id) => {
console.log(id)
// 对commentList做过滤处理
setCommentList(commentList.filter(item => item.rpid !== id))
}
// tab切换功能
// 1. 点击谁就把谁的type记录下来
// 2. 通过记录的type和每一项遍历时的type做匹配 控制激活类名的显示
const [type, setType] = useState('hot')
const handleTabChange = (type) => {
console.log(type)
setType(type)
// 基于列表的排序
if (type === 'hot') {
// 根据点赞数量排序
// lodash
setCommentList(_.orderBy(commentList, 'like', 'desc'))
} else {
// 根据创建时间排序
setCommentList(_.orderBy(commentList, 'ctime', 'desc'))
}
}
// 发表评论
const [content, setContent] = useState('')
const inputRef = useRef(null)
const handlPublish = () => {
setCommentList([
...commentList,
{
rpid: uuidV4(), // 随机id
user: {
uid: '30009257',
avatar,
uname: '黑马前端',
},
content: content,
ctime: dayjs(new Date()).format('MM-DD hh:mm'), // 格式化 月-日 时:分
like: 66,
}
])
// 1. 清空输入框的内容
setContent('')
// 2. 重新聚焦 dom(useRef) - focus
inputRef.current.focus()
}
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 */}
{tabs.map(item =>
<span
key={item.type}
onClick={() => handleTabChange(item.type)}
className={classNames('nav-item', { active: type === item.type })}>
{item.text}
</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="发一条友善的评论"
ref={inputRef}
value={content}
onChange={(e) => setContent(e.target.value)}
/>
{/* 发布按钮 */}
<div className="reply-box-send">
<div className="send-text" onClick={handlPublish}>发布</div>
</div>
</div>
</div>
{/* 评论列表 */}
<div className="reply-list">
{/* 评论项 */}
{commentList.map(item => <Item key={item.id} item={item} onDel={handleDel} />)}
</div>
</div>
</div>
)
}
export default App
classnames优化类名控制
一直存在的类名叫静态类名,根据不同情况肯存在肯不存在的类名叫动态类名
卧槽这个老师讲的也是依托