4.2.3.4 框架与库 ——React
入门
请阅读并完成页面中的练习:
顺便提一下,关于副作用的介绍也需要阅读,避免写出 bug: React: 保持组件纯粹
以及我们需要使用树形组织方式: React: 将 UI 视为树
TypeScript in React
在 React 中使用 TypeScript 可以参考 React: 使用 TypeScript 中提到的写法。
当然也还有一个邪门的用法:在 VSCode 中是自带错误检查支持的,在标红的地方加上 Type 标注消去红色标出的 Error 即可,这种方法也挺好用。
Hooks
钩子让开发者能够在函数组件中使用状态和生命周期相关的功能。React 当中会提供一些钩子 用来处理不同的需求。最常用的就是 useState
useEffect
和 useRef
。
我们在这里给出简单的介绍,确保大家在有需求的时候可以快速找到自己需要的用法,详细的可以查阅各部分的 React 官方文档。
useState
useState
是最常用的 Hook,它允许你在函数组件中引入状态。 useState
返回一个数组,包含当前状态值和用于更新状态的函数。
注意 useState
和 React 的渲染机制绑定。你需要阅读下面的文章来确保理解 state 的工作方式以及使用规范:
另外我们在 state 中使用数组和对象的时候,如果需要更新 state, 我们会用新的对象的复制,而不是在原来的对象上更改。
对于数组和对象,更新时记住用拷贝而不是修改原来的对象,否则不会触发更新。我们更推荐用 Immer 处理这样的情况,因为修改嵌套对象很麻烦。我们已经使用 npm 安装过了,所以这里给出示例:
import { useState } from 'react';
import { produce } from 'immer'; // 用于处理嵌套对象和数组的更新
function ShoppingList() {
// 初始化一个对象状态,包含名字和商品数组
const [list, setList] = useState({
name: 'My Shopping List',
items: ['Apples', 'Bananas'],
});
// 添加商品到购物列表
const addItem = (item) => {
setList((prevList) => produce(prevList, (draft) => {
draft.items.push(item); // 使用 Immer 进行不可变更新
}));
};
// 更新购物列表的名字
const updateListName = (newName) => {
setList((prevList) => produce(prevList, (draft) => {
draft.name = newName;
}));
};
return (
<div>
<h1>{list.name}</h1>
<ul>
{list.items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<button onClick={() => addItem('Oranges')}>Add Oranges</button>
<button onClick={() => updateListName('Grocery List')}>Rename List</button>
</div>
);
}
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
如果你还是没有理解,可以在遇到问题的时候查阅 React: 添加交互 章节中的其他教程,确保理解并掌握。
useEffect
useEffect
允许你在组件渲染后执行副作用,例如数据获取、订阅或手动修改 DOM。具体的使用需要阅读: React: 使用 Effect 进行同步
示例:
import { useEffect, useState } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
// 模拟数据获取,很多人会这么写,但是非常不推荐这样用:(想一想,为什么?提示:fetch 两次, 再提示:加上加载显示?错误处理?)
fetch('https://api.example.com/data')
.then(response => response.json())
.then(result => setData(result));
// 清理副作用(可选)
return () => {
console.log('Cleanup'); // 这段代码会被执行两次,如果你还不清楚原因,请重新阅读 [React: 使用 Effect 进行同步]
};
}, []); // 空数组作为依赖项意味着该副作用只在组件挂载和卸载时执行,你也可以添加变量来监听更改触发
return (
<div>
<p>Fetched data: {JSON.stringify(data)}</p>
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
对于示例中提到的获取数据,我们更推荐使用 React Query 中的 useQuery
钩子,因为这样能更好地实现 query 逻辑的解耦,也因为它不会 fetch 两次。
useRef
useRef
允许你访问 DOM 元素或存储任意可变值,而无需重新渲染组件。它通常用于访问 DOM 元素或保持某些跨渲染周期不变的值。一般来说以下用法完全够用。
示例:
import { useRef, useEffect } from 'react';
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// 在组件挂载后让输入框自动获得焦点
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}
2
3
4
5
6
7
8
9
10
11
12
useMemo
和 useCallback
useMemo
和 useCallback
主要用于性能优化。 useMemo
返回一个计算值的缓存,而 useCallback
则缓存一个回调函数,避免不必要的重新计算和渲染。
useMemo
示例:
import { useMemo } from 'react';
function ExpensiveComputationComponent({ number }) {
const computedValue = useMemo(() => {
return number * 2; // 假设这是一个昂贵的计算
}, [number]);
return <div>Computed Value: {computedValue}</div>;
}
2
3
4
5
6
7
8
9
useCallback
示例:
import { useCallback } from 'react';
function Button({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}
function ParentComponent() {
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return <Button onClick={handleClick} />;
}
2
3
4
5
6
7
8
9
10
11
12
13