react--初学笔记
学习资源
- 官方文档:https://react.dev/
- React 教程中文版(快速入门好看):https://zh-hans.react.dev/learn
- 框架语法特性对比-中文版:https://component-party.lainbo.com/
- 大模型老师
- React Router:https://reactrouter.com/en/main
- Redux Toolkit:https://redux-toolkit.js.org/
- TypeScript + React:https://www.typescriptlang.org/docs/handbook/react.html
JSX语法
JSX是React的模板语法,属于语法糖,最终会被 Babel编译为React.createElement()
jsx是将html和javascript结合起来的模板语法,但它不是 HTML,而是 JavaScript 的扩展
比如说
<div className="title">Hello</div>
会被编译成React.createElement("div", { className: "title" }, "Hello")
基本语法规则
- html根在return()的返回中
- JSX只能返回一个根元素
- 所有标签必须闭合
1
2
3
4return(
// 里面的标签必须是闭合的
// 只能有一个父div,如果要有两个父div,那么使用空标签<></>把他俩进行包裹
)
插值语法与数据渲染
- JSX的插值语法:标签内容,标签属性
数据渲染—条件渲染
- 三元运算符
{isLoading ? <Spinner /> : <Content /> - 逻辑与运算符(适用于存在则显示)
{error && <p className="error">{error}</p>} - if判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function App(){
const divContent = '标签内容'
const divTitle = '标签标题'
const flag = true
let newContent=''
if(flag){
newContent = <span>JSP不用字符串包裹的写</span>
}else{
newContent = <p>这样就可以呈现啦</p>
}
return (
<div title = {divTitle}>{divContent}
{/* 注意这里的属性值{divTitle}不能加双引号哦 */}
{newContent}
</div>
)
}
export default App;
数据渲染—列表渲染(map+key)
使用Array.map()生成元素列表,必须添加 key 属性:
这里的Fragment其实等价于<></>,不渲染真实的dom结构
1 | import {Fragment} from "react" |
Fragment
Fragment(相当于template)
轻量级容器,当需要返回多个同级元素而不想引入额外DOM节点时,可以使用<></>或 <Fragment/>,不渲染真实的dom结构
事件绑定
事件的基本用法
事件名使用驼峰命名(onClick、onChange)
传递函数引用,而非函数调用
注意,onClick={handleClick} 的结尾没有小括号!不要 调用 事件处理函数:你只需 把函数传递给事件 即可。当用户点击按钮时 React 会调用你传递的事件处理函数。
传参的话可以使用箭头函数,例如()=>handleClick(1)
1 | // 正确 |
1 | function App(){ |
useState 状态处理
理解函数式组件
vue可以声明响应式数据,可以驱动页面内容进行更新,也是减少操作DOM的手段,但是在react默认没有这个机制,所以这个数据也就是一个普通变量,把它改了页面内容不会更新
函数式组件默认是没有状态的,理解:
比如说点击按钮改变文本内容,如果按照下面这样写是不能改变的
1 | function App(){ |
useState基本使用
React中,要学会在交互过程中提炼出来一个状态/数据,然后通过控制这个状态的方式,来改变 UI组件,数据驱动ui的开发思维
在react中使用useState函数进行状态变更
const [本次渲染的内容, 修改内容的函数] = useState(‘初始值’)
按照惯例会像 [something, setSomething] 这样命名
为什么react里这样做
如果每个变量都默认有状态更新机制的话,那页面的负担会很大
每个组件中通常只会有两三个变量是状态,不会每个变量都是状态
仔细看下面代码
1 | import {useState} from "react" |
useState状态类型
字符串类型(如上面的代码)
对象类型(关联数据通常使用)
比如对title和contet进行处理
1 | function App(){ |
数组类型
比如添加 ,删除,排序,修改等
1 |
|
状态更新注意事项
useState结构的第二项(修改内容的函数)是进行覆盖操作
什么意思呢,比如说使用对象类型,原本有title和content两个属性,但我现在只想修改title属性,我想这样写行不行
1 | setData( |
这样不行的,覆盖后原来的content属性和属性值会丢失
正确写法是先通过展开运算符进行展开操作,把原来的属性都放进来,再修改同名属性
1 | setData( |
常见误区
状态更新是异步的,复杂场景使用函数式更新
对象 / 数组状态更新时,必须创建新对象 / 数组(不可变数据原则)
避免直接修改状态,始终通过 setter 函数更新
1 | // 错误:直接修改状态 |
useState位置变化效果变化(是否共享数据)
组件各自拥有独立的数据
如果useState放在组件中,多次渲染同一个组件,每个组件都会拥有自己的state他们之间互不干扰
如果需要件共享数据并一起更新
将MyButton的state上移到 MyApp,再将将 MyApp 中的点击事件处理函数以及 state 一同向下传递到 每个 MyButton 中
React DOM组件和Props设置
React 组件必须以大写字母开头,而HTML标签则必须是小写字母
基本属性映射规则
React中的DOM属性与HTML有些差异:
- class变为className
<div className="container">内容</div> - 短横线属性用驼峰命名法
<div style={{ backgroundColor: 'red', fontSize: 16 }}></div> - style 属性的两种写法
1
2
3
4
5
6
7
8
9
10
11// 对象内联写法
<div style={{ color: 'blue', margin: '10px' }}>文本</div>
// 提取样式对象
const styles = {
color: 'blue',
margin: '10px',
borderRadius: '4px'
};
<div style={styles}>文本</div> - 带有alt属性的img标签,alt要留在img标签内不能提取走
1
2
3const imgProps = { src: 'image.jpg', width: 200 };
// 显式添加 alt
<img {...imgProps} alt="示例图片" />
自定义组件的 Props(父传子)
JSX的展开语法(常用于组件传参,不同于ES6)
JSX的展开语法不是ES6的展开运算符
ES6的展开运算发无法单独使用,不能在没有容器的地方单独使用,常用于展开,合并和修改操作
JSX 的展开语法(常用于组件传参)
1 | function MyComponent({ name, age }) { |
组件复用:复用逻辑与样式,不复用内容
1 | // 可复用的按钮组件(只定义样式和逻辑) |
children属性:用于传递组件内部的子元素
预定义一个值children接受传递的内容
1 | // 卡片组件 |
常用解构
内部结构传递使用props(单项数据流)
prop解构成{title,const,active},这样使用可以直接{title}而不是{prop.title}
1 | function Article({title,const,active}){ |
子传父:
父组件给子组件传递自定义事件,子组件触发传给父组件
(AI找例子)
纯函数与不纯函数
概念对比
| 特性 | 纯函数 | 非纯函数 |
|---|---|---|
| 输入决定输出 | 相同输入始终返回相同结果(确定性) | 相同输入可能返回不同结果(依赖外部状态) |
| 副作用 | 无副作用(不修改外部状态,比如不可以改变原操作的数组或对象) | 可能修改外部状态或依赖外部变量 |
| 可测试性 | 易于测试(仅需验证输入输出) | 难以测试(需模拟外部环境) |
| 缓存性 | 可缓存 | 无法缓存 |
| 线程安全 | 天然线程安全 | 可能引发竞态条件 |
纯函数场景:输入参数应使用const声明,确保不可变性,符合纯函数 “不修改外部状态” 的原则
不纯函数场景:若需要修改变量,使用let声明
1 | //看这两个add,add1是纯函数,add2不是纯函数 |
React中的纯函数实践
React 组件作为纯函数
- React 官方推荐组件是纯函数:组件的输出仅由
props和state决定,无副作用。 - 受控组件:表单元素的值由 React 状态控制,符合纯函数思想。
1 | // 纯函数组件(React Hook) |
React中不纯函数
不纯函数的副作用表现
- 修改外部变量:如上面impureIncrement修改全局count
- 依赖外部状态:
1
2
3
4
5// 依赖全局变量的不纯函数
const name = 'Alice';
function getGreeting() {
return `Hello, ${name}`; // 依赖外部变量name
} - 异步操作 / IO 操作
1
2
3
4// 发送网络请求(产生IO副作用)
function fetchData() {
return fetch('https://api.example.com');
} - DOM 操作:直接修改DOM元素
避免副作用的组件
- 禁止直接修改 props或state:应通过
useState更新状态,而非直接赋值。 - 避免在组件中执行DOM操作:应使用
useEffect或 React 的合成事件。
1 | // 错误:直接修改 props |
useEffect处理副作用
- 副作用场景:数据请求、订阅、手动 DOM 操作
- 规则:副作用应在
useEffect中声明,且依赖项需明确
深拷贝与浅拷贝(React示例)
浅拷贝:
创建一个新对象或数组,但仅复制一层数据,对于引用类型(对象 / 数组),只复制其内存地址,新旧数据共享同一引用。
特点:修改新数据的引用类型属性时,原数据会同步变化
基本数据类型(如 number, string, boolean 等)会被复制一份新的值
引用类型(如 object, array)仅复制其引用地址,而不是深层的对象或数组本身
常见实现方式:展开运算符(…);Object.assign();Array.prototype.slice()/concat()
1 | const original = { a: 1, b: { c: 2 } }; |
深拷贝
定义:创建一个新对象或数组,递归复制所有层级的数据,新旧数据完全独立,互不影响。
特点:所有层次的数据都被复制,包括基本数据类型和引用类型
新对象或数组不会受到对原始对象或数组修改的影响
常见实现方式:JSON.parse(JSON.stringify())(简易版);递归函数手动复制;第三方库(如lodash.cloneDeep)
1 | const original = { a: 1, b: { c: 2 } }; |
react代码示例
浅拷贝示例
1 | // 场景:React中状态为对象时的浅拷贝问题 |
深拷贝实例
1 |
|
小结
在 React 应用中,当涉及到状态管理时,通常需要注意浅拷贝的行为,特别是在更新包含嵌套对象或数组的状态时,可能需要手动实现深拷贝或者使用 Redux Toolkit 这样的工具提供的便捷方法来帮助管理状态
使用场景
- 浅拷贝适用场景:
数据层级单一(如只有一层对象 / 数组)
引用类型属性不需要完全隔离(如仅修改基本类型属性)
React 中常用的…prevState展开运算符即浅拷贝 - 深拷贝适用场景:
需要处理深层结构的数据并且希望这些数据之间互不影响
数据多层嵌套(如obj.a.b.c)
需要完全隔离新旧数据(如撤销 / 重做功能、多版本数据对比)
性能注意事项
深拷贝比浅拷贝性能消耗更高,尤其是大数据量时
优先使用浅拷贝,仅在必要时使用深拷贝(如嵌套数据修改)
ES6展开运算符与JSX展开语法
ES6 展开运算符 和 JSX 展开语法 都是基于浅拷贝机制,它们只复制对象或数组的第一层数据
ES6 展开运算符主要用于创建对象或数组的浅拷贝。这意味着如果你有一个包含嵌套对象或数组的对象,使用展开运算符只会复制最外层的对象或数组,而内部的引用类型仍保持原有的引用关系
JSX 的展开语法 {…props} 在传递 props 到组件时也是基于浅拷贝原理。它只是将对象的所有键值对作为单独的 prop 传递给组件,而不涉及对象内部引用类型的深层次复制




