学习资源

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
    4
    return(
    // 里面的标签必须是闭合的
    // 只能有一个父div,如果要有两个父div,那么使用空标签<></>把他俩进行包裹
    )

插值语法与数据渲染

  • JSX的插值语法:标签内容,标签属性

数据渲染—条件渲染

  1. 三元运算符
    {isLoading ? <Spinner /> : <Content />
  2. 逻辑与运算符(适用于存在则显示)
    {error && <p className="error">{error}</p>}
  3. if判断
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function 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
2
3
4
5
6
7
8
9
10
11
12
13
14
import {Fragment} from "react"
function App(){
const list = [{id:1,name:'松瑰'},{id=2,name:'雾刃'},{id=3,name:'枫糖'}]
const listContent = list.map(item=>{
<Fragment>
<li key={item.id}>{item.name}</li>
<li>----分隔符-----</li>
</Fragment>
})
return(
<ul>llistContent</ul>
)
}
export default App

Fragment

Fragment(相当于template)
轻量级容器,当需要返回多个同级元素而不想引入额外DOM节点时,可以使用<></>或 <Fragment/>,不渲染真实的dom结构

事件绑定

事件的基本用法

事件名使用驼峰命名(onClick、onChange)
传递函数引用,而非函数调用
传参的话可以使用箭头函数,例如()=>handleClick(1)

1
2
3
4
5
6
7
8
// 正确
<button onClick={handleClick}>点击</button>

// 错误:会立即执行
<button onClick={handleClick()}>点击</button>

// 需要传参时,使用箭头函数
<button onClick={() => handleClick(id)}>删除</button>
1
2
3
4
5
6
7
8
9
function App(){
function handleClick(e){
console.log('点击了按钮',e)
}
return(
<button onClick={handleClick}>按钮</button>
)
}
export default App

useState 状态处理

理解函数式组件

React 应用的基本构建块,本质是返回 JSX 的函数

vue可以声明响应式数据,可以驱动页面内容进行更新,也是减少操作DOM的手段,但是在react默认没有这个机制,所以这个数据也就是一个普通变量,把它改了页面内容不会更新
函数式组件默认是没有状态的,理解:
比如说点击按钮改变文本内容,如果按照下面这样写是不能改变的

1
2
3
4
5
6
7
8
9
10
11
12
13
function App(){
let divContent = '原本的内容'
function handleClick(e){
divContent = '内容改变啦'
}
return(
<>
<div>divContent</div>
<button onClick={handleClick}>按钮</button>
</>
)
}
export default App

useState基本使用

在react中使用useState函数进行状态变更
const [本次渲染的内容, 修改内容的函数] = useState(‘初始值’)

为什么react里这样做
如果每个变量都默认有状态更新机制的话,那页面的负担会很大
每个组件中通常只会有两三个变量是状态,不会每个变量都是状态
仔细看下面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {useState} from "react"
function App(){
//写法如下,用解构,content可以理解成本次渲染的内容,**setContent是一个函数**,用来修改这个content
const [ content , setContent ] = useState('我是数据的初始值')
function handleClick(){
setContent('我是修改后的新内容')
}
return(
<>
<div>{content}</div>
<button onClick={handleClick}>按钮</button>
</>
)
}
export default App

状态的类型—字符串类型(如上面的代码)

状态类型—对象类型(关联数据通常使用)

比如对title和contet进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function App(){
content [ data , setData ] = useState({
title: '默认标题',
content: '默认内容'
})
function handleClick(){
setData({
title: '修改后的标题',
content: '修改后的内容'
})
}
return(
<>
<div title={data.title}>{data.content}</div>
<button onClick={handleClick}>按钮</button>
</>
)
}
export default App

状态类型—数组

比如添加 ,删除,排序,修改等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

functIon App(){
const [data, setData]= useState([
{id:1name:'小吴'},
{id:2name:'小李'},
{id:3name:'小花'}
])
const listData =data.map(item =>(<li key={item.id}>{item.name}</li>))
let id = 3
function handleclick(){
setData([
{id:++id,name:'小花猫'}
...data
])
}
return(
<ul>{listData}</ul>
<button onClick={handleClick}>按钮</button>
)
}
export default App

状态更新注意事项

useState结构的第二项(修改内容的函数)是进行覆盖操作

什么意思呢,比如说使用对象类型,原本有title和content两个属性,但我现在只想修改title属性,我想这样写行不行

1
2
3
4
setData(
{
title = '修改后的tite'
})

这样不行的,覆盖后原来的content属性和属性值会丢失

正确写法是先通过展开运算符进行展开操作,把原来的属性都放进来,修改同名属性

1
2
3
4
5
6
7
8
9
setData(
{
...data,
title = '修改后的tite'
})
setArrdata({
...data,
{id:id++,name:'新增数组项'}
})

常见误区

状态更新是异步的,复杂场景使用函数式更新
对象 / 数组状态更新时,必须创建新对象 / 数组(不可变数据原则)
避免直接修改状态,始终通过 setter 函数更新

1
2
3
4
5
6
7
8
9
10
11
// 错误:直接修改状态
setData({ title: "New Title" }); // 会丢失其他属性

// 正确:合并更新
setData(prev => ({ ...prev, title: "New Title" }));

// 错误:使用索引作为key
users.map((user, index) => <li key={index}>{user.name}</li>);

// 正确:使用唯一ID作为key
users.map(user => <li key={user.id}>{user.name}</li>);

React DOM组件的Props设置

基本属性映射规则

React中的DOM属性与HTML有写差异,需注意以下几点:

  1. class变为className
    <div className="container">内容</div>
  2. 短横线属性用驼峰命名法
    <div style={{ backgroundColor: 'red', fontSize: 16 }}></div>
  3. 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>
  4. 带有alt属性的img标签,alt要留在img标签内不能提取走
    1
    2
    3
    const imgProps = { src: 'image.jpg', width: 200 };
    // 显式添加 alt
    <img {...imgProps} alt="示例图片" />

自定义组件的 Props(父传子)

JSX的展开语法不是ES6的展开运算符

ES6的展开运算发无法单独使用,不能在没有容器的地方单独使用
常用于展开,合并和修改操作

  1. JSX 的展开语法(常用于组件传参)
    1
    2
    3
    4
    5
    6
    7
    function MyComponent({ name, age }) {
    return <p>{name} is {age} years old.</p>
    }
    const user = { name: 'Alice', age: 25 }
    <MyComponent {...user} />
    //以上的写法等价于
    <MyComponent name="Alice" age={25} />

组件复用:复用逻辑与样式,不复用内容

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
// 可复用的按钮组件(只定义样式和逻辑)
const Button = ({ label, onClick, variant = 'primary' }) => {
const className = variant === 'primary'
? 'bg-blue-500 text-white'
: 'bg-gray-500 text-white';

return (
<button
className={`px-4 py-2 rounded ${className}`}
onClick={onClick}
>
{label} // 内容由外部传入
</button>
);
};

// 复用示例
const App = () => {
return (
<div>
<Button label="提交" onClick={handleSubmit} />
<Button label="取消" onClick={handleCancel} variant="secondary" />
</div>
);
};

children属性:用于传递组件内部的子元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 卡片组件
const Card = ({ title, children }) => {
return (
<div className="border rounded p-4">
<h3 className="font-bold">{title}</h3>
<div className="mt-2">{children}</div> // 子内容
</div>
);
};

// 使用示例
const App = () => {
return (
<Card title="产品详情">
<p>这是一款高品质产品</p>
<button>购买</button>
</Card>
);
};

深拷贝与浅拷贝(React实例)

浅拷贝:

创建一个新对象或数组,但仅复制一层数据,对于引用类型(对象 / 数组),只复制其内存地址,新旧数据共享同一引用。
特点:修改新数据的引用类型属性时,原数据会同步变化
基本数据类型(如 number, string, boolean 等)会被复制一份新的值
引用类型(如 object, array)仅复制其引用地址,而不是深层的对象或数组本身
常见实现方式:展开运算符(…);Object.assign();Array.prototype.slice()/concat()

1
2
3
4
5
6
7
8
9
10
const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };

// 修改原对象中基本类型的属性
original.a = 10;
console.log(shallowCopy.a); // 输出: 1 (未受影响)

// 修改原对象中嵌套对象的属性
original.b.c = 20;
console.log(shallowCopy.b.c); // 输出: 20 (受到影响)

深拷贝

定义:创建一个新对象或数组,递归复制所有层级的数据,新旧数据完全独立,互不影响。
特点:所有层次的数据都被复制,包括基本数据类型和引用类型
新对象或数组不会受到对原始对象或数组修改的影响
常见实现方式:JSON.parse(JSON.stringify())(简易版);递归函数手动复制;第三方库(如lodash.cloneDeep)

1
2
3
4
5
6
const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));

// 修改原对象中嵌套对象的属性
original.b.c = 30;
console.log(deepCopy.b.c); // 输出: 2 (未受影响)

react代码示例

浅拷贝示例

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
// 场景:React中状态为对象时的浅拷贝问题
import { useState } from 'react';

function ShallowCopyDemo() {
const [user, setUser] = useState({
name: '张三',
age: 20,
address: { city: '北京', district: '朝阳区' }
});

// 错误做法:直接修改对象属性(未拷贝)
const handleWrongUpdate = () => {
user.address.city = '上海'; // 直接修改原对象,React无法检测到更新
setUser(user); // 状态更新无效,视图不刷新
};

// 正确做法:浅拷贝(仅复制一层)
const handleShallowCopy = () => {
// 用展开运算符浅拷贝user对象
const newUser = { ...user };
// 浅拷贝后修改第一层属性(基本类型)
newUser.age = 21;
// 但修改引用类型属性时,仍会影响原数据
newUser.address.city = '上海'; // 原user.address.city也会变为上海
setUser(newUser);
};

return (
<div>
<p>姓名:{user.name}</p>
<p>年龄:{user.age}</p>
<p>地址:{user.address.city}-{user.address.district}</p>
<button onClick={handleWrongUpdate}>错误更新</button>
<button onClick={handleShallowCopy}>浅拷贝更新</button>
</div>
);
}

深拷贝实例

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

// 场景:React中需要完全隔离新旧数据时的深拷贝
import { useState } from 'react';

function DeepCopyDemo() {
const [data, setData] = useState({
list: [1, 2, 3],
config: { mode: 'production', env: 'dev' }
});

// 方法1:JSON转换(简易深拷贝,不支持函数/Date等类型)
const handleJsonCopy = () => {
const newData = JSON.parse(JSON.stringify(data));
newData.list.push(4);
newData.config.mode = 'test';
setData(newData);
};

// 方法2:递归深拷贝函数(支持复杂类型)
const deepClone = (obj) => {
if (typeof obj !== 'object' || obj === null) {
return obj; // 基本类型直接返回
}
if (obj instanceof Array) {
return obj.map(item => deepClone(item)); // 数组递归拷贝
}
if (obj instanceof Object) {
const clone = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]); // 对象递归拷贝
}
}
return clone;
}
return obj;
};

const handleDeepClone = () => {
const newData = deepClone(data);
newData.list.push(4);
newData.config.mode = 'test';
setData(newData);
};

return (
<div>
<p>列表:{data.list.join(',')}</p>
<p>配置:{data.config.mode}-{data.config.env}</p>
<button onClick={handleJsonCopy}>JSON深拷贝</button>
<button onClick={handleDeepClone}>递归深拷贝</button>
</div>
);
}

小结

在 React 应用中,当涉及到状态管理时,通常需要注意浅拷贝的行为,特别是在更新包含嵌套对象或数组的状态时,可能需要手动实现深拷贝或者使用 Redux Toolkit 这样的工具提供的便捷方法来帮助管理状态

使用场景

  1. 浅拷贝适用场景:
    数据层级单一(如只有一层对象 / 数组)
    引用类型属性不需要完全隔离(如仅修改基本类型属性)
    React 中常用的…prevState展开运算符即浅拷贝
  2. 深拷贝适用场景:
    需要处理深层结构的数据并且希望这些数据之间互不影响
    数据多层嵌套(如obj.a.b.c)
    需要完全隔离新旧数据(如撤销 / 重做功能、多版本数据对比)

性能注意事项

深拷贝比浅拷贝性能消耗更高,尤其是大数据量时
优先使用浅拷贝,仅在必要时使用深拷贝(如嵌套数据修改)

ES6展开运算符与JSX展开语法

ES6 展开运算符 和 JSX 展开语法 都是基于浅拷贝机制,它们只复制对象或数组的第一层数据

ES6 展开运算符主要用于创建对象或数组的浅拷贝。这意味着如果你有一个包含嵌套对象或数组的对象,使用展开运算符只会复制最外层的对象或数组,而内部的引用类型仍保持原有的引用关系
JSX 的展开语法 {…props} 在传递 props 到组件时也是基于浅拷贝原理。它只是将对象的所有键值对作为单独的 prop 传递给组件,而不涉及对象内部引用类型的深层次复制