React / 已完成
React 核心篇:Hooks 表单 请求 用户列表
用 Hooks 管搜索、表单、请求、loading、error,把用户列表变成真实业务页面。
返回文章积累一句话:React 核心能力就是用 Hooks 管状态、管副作用、管表单和接口请求,把静态组件变成真实业务页面。
第 3 篇 / 共 12 篇。
本篇学完你会:用 React 19 + Hooks + TypeScript 写用户列表的搜索、请求、loading、error、新增编辑表单。
1. Hooks 解决什么问题
Hooks 是 React 函数组件里使用“组件能力”的方式。
| Hook | 大白话 |
|---|---|
useState | 记住一份会变化的数据 |
useEffect | 在渲染之后做副作用,比如请求接口 |
useMemo | 缓存计算结果 |
useCallback | 缓存函数引用 |
useRef | 记住一个不会触发重渲染的值 |

2. useState 管表单
搜索条件就是一份 state:
type UserQuery = {
keyword: string;
status: 'all' | 'active' | 'disabled';
};
const [query, setQuery] = useState<UserQuery>({
keyword: '',
status: 'all'
});更新某一个字段时,不要丢掉其他字段:
setQuery((current) => ({
...current,
keyword: event.target.value
}));大白话:表单 state 像一张草稿纸,每次改一个字段,都要把整张草稿纸交回给 React。
3. useEffect 管接口请求
接口请求属于副作用。它不是单纯算 UI,而是去外部世界拿数据。
useEffect(() => {
async function loadUsers() {
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data);
}
loadUsers();
}, []);第二个参数 [] 表示这个 effect 只在组件挂载后跑一次。
如果搜索条件变化就重新请求:
useEffect(() => {
loadUsers(query);
}, [query]);4. 用户列表请求流程
真实页面不能只管成功,还要管 loading 和 error。
type UserState = {
data: User[];
loading: boolean;
error: string | null;
};
const [userState, setUserState] = useState<UserState>({
data: [],
loading: false,
error: null
});请求时:
async function loadUsers(query: UserQuery) {
setUserState((current) => ({ ...current, loading: true, error: null }));
try {
const search = new URLSearchParams({
keyword: query.keyword,
status: query.status
});
const response = await fetch(`/api/users?${search.toString()}`);
if (!response.ok) {
throw new Error('用户列表加载失败');
}
const data = (await response.json()) as User[];
setUserState({ data, loading: false, error: null });
} catch (error) {
setUserState({
data: [],
loading: false,
error: error instanceof Error ? error.message : '未知错误'
});
}
}页面渲染:
{userState.loading && <p>加载中...</p>}
{userState.error && <p>{userState.error}</p>}
{!userState.loading && !userState.error && <UserList users={userState.data} />}5. 新增和编辑表单
新增和编辑可以共用一个表单组件:
type UserFormValue = {
name: string;
email: string;
role: 'admin' | 'member';
status: 'active' | 'disabled';
};
type UserFormProps = {
initialValue: UserFormValue;
onSubmit: (value: UserFormValue) => Promise<void>;
};表单组件内部管理草稿:
function UserForm({ initialValue, onSubmit }: UserFormProps) {
const [value, setValue] = useState(initialValue);
const [submitting, setSubmitting] = useState(false);
async function handleSubmit(event: React.FormEvent) {
event.preventDefault();
setSubmitting(true);
await onSubmit(value);
setSubmitting(false);
}
return (
<form onSubmit={handleSubmit}>
<input value={value.name} onChange={(event) => setValue({ ...value, name: event.target.value })} />
<input value={value.email} onChange={(event) => setValue({ ...value, email: event.target.value })} />
<button disabled={submitting}>{submitting ? '保存中...' : '保存'}</button>
</form>
);
}6. useMemo 和 useCallback 什么时候用
不要一上来就乱用。
适合 useMemo 的情况:
const activeUsers = useMemo(() => users.filter((user) => user.status === 'active'), [users]);适合 useCallback 的情况:
const handleDelete = useCallback(async (id: number) => {
await deleteUser(id);
await loadUsers(query);
}, [query]);大白话:只有当计算比较贵、函数会传给子组件、或者依赖稳定性真的重要时再用。
7. 自定义 Hook
用户列表逻辑可以抽成 useUsers:
function useUsers() {
const [query, setQuery] = useState<UserQuery>({ keyword: '', status: 'all' });
const [state, setState] = useState<UserState>({ data: [], loading: false, error: null });
const reload = useCallback(() => loadUsers(query), [query]);
useEffect(() => {
reload();
}, [reload]);
return { query, setQuery, state, reload };
}这样页面组件就更像在描述布局:
const { query, setQuery, state, reload } = useUsers();8. 常见错误
| 错误 | 后果 | 修正 |
|---|---|---|
useEffect 依赖漏写 | 页面用旧数据 | 按规则补依赖 |
| 请求没有 loading | 用户不知道发生了什么 | 增加 loading 状态 |
| 请求失败不处理 | 页面静默坏掉 | 增加 error 状态 |
| 表单直接改 props | 数据来源混乱 | 用本地草稿 state |
| 过早 useMemo | 代码变复杂 | 先写清楚,再优化 |
9. 下一步怎么学
本篇重点是把用户管理页从“能显示”推进到“能交互”。
| 能力 | 对应任务 |
|---|---|
useState | 搜索条件、表单草稿 |
useEffect | 加载用户列表 |
| loading/error | 真实请求体验 |
| 自定义 Hook | 抽出业务逻辑 |
| 表单组件 | 新增和编辑用户 |