React
React 是一个由 Facebook 开发的流行的 JavaScript 库,用于构建交互式用户界面。它采用了一种基于组件化的开发模式,使得开发人员可以将 UI 拆分为独立的、可复用的组件,并由这些组件构建复杂的用户界面。
React 的主要特点包括:
组件化开发:React 将 UI 拆分为独立的、可复用的组件,开发人员可以将这些组件组合在一起构建复杂的用户界面;
虚拟 DOM:React 采用虚拟 DOM 技术来优化 UI 更新性能,通过比较前后状态的差异来最小化 DOM 操作;
单向数据流:React 中的数据流是单向的,数据由父组件传递给子组件,子组件不能直接修改父组件的数据;
JSX:React 支持使用 JSX 语法,将组件的结构和样式与 JavaScript 代码结合在一起,使得代码更加简洁和易于理解。
React 生态系统非常丰富,包括许多与 React 相关的库和工具,如 Redux、React Router、Webpack 等,可帮助开发人员更好地使用 React 构建应用程序。
React 在 Web 开发、移动应用开发和桌面应用开发等领域得到了广泛应用,并且在社区中有着非常活跃的开发者和贡献者。如果您想要学习构建交互式用户界面的技术,React 是一个非常不错的选择。

查看更多相关内容
useCallback 和 useMemo 有什么区别?什么场景下使用?## 问题背景
useCallback 和 useMemo 是 React 提供的两个性能优化 Hook,它们看起来很相似,但用途和返回值有本质区别。
## 核心区别
### 语法对比
```jsx
// useCallback:返回函数本身
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// useMemo:返回函数执行结果
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
```
### 本质区别
| 特性 | useCallback | useMemo |
|------|-------------|---------|
| 返回值 | 函数引用 | 任意值(计算结果) |
| 用途 | 缓存函数 | 缓存计算结果 |
| 类似于 | 函数记忆化 | 值记忆化 |
| 场景 | 避免函数重建 | 避免重复计算 |
## useCallback 详解
### 基本用法
```jsx
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b]
);
```
### 使用场景
#### 1. 传递给子组件避免不必要渲染
```jsx
function Parent({ items }) {
// ❌ 每次渲染都创建新函数,导致 Child 重新渲染
const handleClick = () => {
console.log("clicked");
};
// ✅ 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
return <Child onClick={handleClick} items={items} />;
}
// 配合 React.memo 使用
const Child = React.memo(({ onClick, items }) => {
console.log("Child render");
return <button onClick={onClick}>Click</button>;
});
```
#### 2. 作为 useEffect 依赖
```jsx
function UserProfile({ userId }) {
// ❌ fetchUser 每次都是新引用,useEffect 每次都执行
const fetchUser = () => {
api.getUser(userId);
};
// ✅ 使用 useCallback 稳定函数引用
const fetchUser = useCallback(() => {
api.getUser(userId);
}, [userId]);
useEffect(() => {
fetchUser();
}, [fetchUser]);
}
```
## useMemo 详解
### 基本用法
```jsx
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
```
### 使用场景
#### 1. 避免重复计算
```jsx
function ProductList({ products, filter }) {
// ❌ 每次渲染都重新过滤
const filteredProducts = products.filter(p =>
p.name.includes(filter)
);
// ✅ 只在 products 或 filter 变化时重新计算
const filteredProducts = useMemo(() =>
products.filter(p => p.name.includes(filter)),
[products, filter]
);
return <ul>{filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
```
#### 2. 复杂计算优化
```jsx
function DataTable({ data }) {
// ✅ 大数据量排序只计算一次
const sortedData = useMemo(() => {
console.log("Sorting...");
return [...data].sort((a, b) => a.score - b.score);
}, [data]);
// ✅ 复杂数据转换
const chartData = useMemo(() => {
return data.reduce((acc, item) => {
// 复杂聚合逻辑
return acc;
}, {});
}, [data]);
return <Chart data={chartData} />;
}
```
#### 3. 引用稳定性
```jsx
function Parent({ items }) {
// ❌ 每次渲染创建新对象,导致子组件重渲染
const style = { color: "red" };
// ✅ 保持对象引用稳定
const style = useMemo(() => ({ color: "red" }), []);
return <Child style={style} />;
}
```
## 组合使用
### useCallback 本质上是 useMemo 的语法糖
```jsx
// useCallback 实现
useCallback(fn, deps);
// 等价于
useMemo(() => fn, deps);
```
### 配合使用优化性能
```jsx
function SearchResults({ query, data }) {
// 缓存过滤结果
const results = useMemo(() => {
return data.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}, [data, query]);
// 缓存事件处理函数
const handleItemClick = useCallback((id) => {
console.log("Selected:", id);
}, []);
// 缓存传递给子组件的 props
const listProps = useMemo(() => ({
items: results,
onItemClick: handleItemClick
}), [results, handleItemClick]);
return <ResultList {...listProps} />;
}
```
## 常见误区
### 误区 1:过度使用
```jsx
// ❌ 不必要的优化,简单计算不需要 useMemo
const total = useMemo(() => a + b, [a, b]);
// ✅ 直接计算即可
const total = a + b;
```
### 误区 2:忽略依赖
```jsx
// ❌ 闭包陷阱
const multiplier = 2;
const result = useMemo(() => value * multiplier, [value]);
// ✅ 添加所有依赖
const multiplier = 2;
const result = useMemo(() => value * multiplier, [value, multiplier]);
```
## 最佳实践
1. **先测量,后优化**:使用 React DevTools Profiler 找出性能瓶颈
2. **简单计算不需要缓存**:缓存本身也有开销
3. **配合 React.memo 使用**:确保子组件实现了 shouldComponentUpdate
4. **正确声明依赖**:遵循 ESLint 提示
```jsx
// 好的组合示例
function UserList({ users, onSelect }) {
// 缓存排序结果
const sortedUsers = useMemo(
() => [...users].sort((a, b) => a.name.localeCompare(b.name)),
[users]
);
// 缓存事件处理
const handleSelect = useCallback(
(userId) => onSelect(userId),
[onSelect]
);
return (
<ul>
{sortedUsers.map(user => (
<UserItem
key={user.id}
user={user}
onSelect={handleSelect}
/>
))}
</ul>
);
}
```
服务端 · 3月15日 00:43
如何创建和使用 Zustand store?### 创建 Zustand store 的步骤:
1. **安装 Zustand**:
```bash
npm install zustand
# 或
yarn add zustand
```
2. **创建 store 文件**(例如 `store.js`):
```javascript
import { create } from 'zustand';
const useStore = create((set) => ({
// 状态
count: 0,
user: null,
// 操作状态的方法
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
setUser: (user) => set({ user }),
reset: () => set({ count: 0, user: null })
}));
export default useStore;
```
3. **在组件中使用 store**:
```javascript
import React from 'react';
import useStore from './store';
function Counter() {
// 方法 1:获取整个 store
const { count, increment, decrement } = useStore();
// 方法 2:选择性订阅(推荐,性能更好)
const countValue = useStore((state) => state.count);
const incrementCount = useStore((state) => state.increment);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Counter;
```
### 关键点:
* 使用 `create` 函数创建 store
* store 是一个函数,接收 `set` 和 `get` 两个参数
* `set` 用于更新状态,支持函数式更新
* 使用 `useStore` hook 在组件中访问状态
* 推荐使用选择性订阅来优化性能
服务端 · 3月7日 12:26
React Query 中的 useQuery 和 useMutation 钩子有什么区别,分别适用于什么场景?React Query 中的 `useQuery` 和 `useMutation` 是两个核心钩子,它们的区别和适用场景如下:
### useQuery
**功能**:用于执行只读操作,如获取数据。
**适用场景**:
- 获取列表数据(如用户列表、产品列表)
- 获取详情数据(如用户详情、订单详情)
- 任何需要从服务器读取数据而不修改的场景
**特点**:
- 自动缓存数据
- 支持数据失效和背景刷新
- 返回数据、加载状态、错误状态等
- 可以配置重试策略
**基本用法**:
```javascript
const { data, isLoading, error } = useQuery('todos', fetchTodos);
```
### useMutation
**功能**:用于执行修改操作,如创建、更新、删除数据。
**适用场景**:
- 提交表单数据
- 更新用户信息
- 删除资源
- 任何需要修改服务器数据的场景
**特点**:
- 支持乐观更新
- 可以配置成功/失败回调
- 支持请求取消
- 可以触发相关查询的重新获取
**基本用法**:
```javascript
const mutation = useMutation(addTodo, {
onSuccess: () => {
// 成功回调,如重新获取数据
queryClient.invalidateQueries('todos');
},
});
// 调用方式
mutation.mutate(newTodo);
```
### 核心区别
1. **操作类型**:`useQuery` 用于读取操作,`useMutation` 用于写入操作
2. **缓存行为**:`useQuery` 自动缓存数据,`useMutation` 不缓存结果
3. **调用方式**:`useQuery` 自动执行(可配置),`useMutation` 需要手动调用 `mutate` 方法
4. **返回值**:`useQuery` 返回数据和状态,`useMutation` 返回 mutation 函数和状态
正确使用这两个钩子可以有效地管理应用中的数据获取和修改操作,提高开发效率和用户体验。
服务端 · 3月7日 12:25
React Query 与传统状态管理库(如 Redux)的区别是什么,什么时候应该使用 React Query?React Query 和传统状态管理库(如 Redux)在设计理念和使用场景上有显著区别:
### 核心区别
1. **管理的状态类型**:
- **React Query**:专门管理服务器状态(从API获取的数据)
- **Redux**:管理全局客户端状态(如用户偏好、UI状态、应用配置等)
2. **状态管理方式**:
- **React Query**:声明式数据获取,自动处理缓存、失效、重试等
- **Redux**:需要手动编写action、reducer来管理状态更新
3. **缓存机制**:
- **React Query**:内置强大的缓存系统,自动处理数据的缓存和失效
- **Redux**:没有内置缓存机制,需要手动实现或使用额外库
4. **数据获取**:
- **React Query**:集成了数据获取逻辑,支持自动重试、轮询等
- **Redux**:需要手动集成axios/fetch等库,处理异步逻辑
5. **代码复杂度**:
- **React Query**:减少了样板代码,API简洁直观
- **Redux**:需要编写更多样板代码(action types、actions、reducers)
### 适用场景
#### 何时使用 React Query:
1. **需要频繁与API交互的应用**:React Query 专门优化了服务器状态管理
2. **需要缓存数据的场景**:内置缓存机制减少重复请求
3. **需要乐观更新的场景**:提升用户体验
4. **需要处理分页、无限滚动的场景**:内置支持
5. **需要自动重试和错误处理的场景**:简化错误处理逻辑
#### 何时使用传统状态管理库:
1. **需要管理复杂的客户端状态**:如多级嵌套的UI状态
2. **需要中间件支持**:如日志、路由、持久化等
3. **需要时间旅行调试**:Redux DevTools提供强大的调试能力
4. **需要严格的状态变更控制**:如金融应用
### 最佳实践
实际上,React Query 和传统状态管理库并不是互斥的,它们可以结合使用:
- 使用 React Query 管理所有服务器状态
- 使用 Redux/Zustand 等管理客户端状态
- 这样可以充分发挥各自的优势,构建更高效、可维护的应用
React Query 的出现并不是要完全取代传统状态管理库,而是为了解决服务器状态管理这一特定领域的问题,让开发人员能够更专注于业务逻辑和UI开发。
服务端 · 3月7日 12:25
如何在 React Query 中处理错误和重试,有哪些最佳实践?React Query 提供了强大的错误处理和重试机制,帮助开发者构建更健壮的应用:
### 错误处理
1. **基本错误处理**:
```javascript
const { data, error, isError } = useQuery('todos', fetchTodos);
if (isError) {
return <div>Error: {error.message}</div>;
}
```
2. **全局错误处理**:通过 QueryClient 配置
```javascript
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: (error) => {
console.error('Query error:', error);
// 可以在这里添加全局错误通知
},
},
},
});
```
3. **Mutation 错误处理**:
```javascript
const mutation = useMutation(addTodo, {
onError: (error, variables, context) => {
console.error('Mutation error:', error);
// 错误处理逻辑
},
});
```
### 重试机制
1. **基本重试配置**:
```javascript
const { data } = useQuery('todos', fetchTodos, {
retry: 3, // 默认值为 3
});
```
2. **高级重试配置**:
```javascript
const { data } = useQuery('todos', fetchTodos, {
retry: (failureCount, error) => {
// 自定义重试逻辑
if (error.status === 404) return false; // 404 不重试
if (failureCount >= 3) return false; // 最多重试 3 次
return true;
},
retryDelay: (attemptIndex) => {
// 指数退避策略
return Math.min(1000 * 2 ** attemptIndex, 30000);
},
});
```
### 最佳实践
1. **错误边界**:使用 React 错误边界捕获查询错误
```javascript
<ErrorBoundary fallback={<ErrorComponent />}>
<QueryComponent />
</ErrorBoundary>
```
2. **错误状态 UI**:为不同类型的错误提供不同的 UI 反馈
```javascript
if (isError) {
if (error.status === 401) {
return <div>请先登录</div>;
} else if (error.status === 404) {
return <div>资源不存在</div>;
} else {
return <div>发生错误:{error.message}</div>;
}
}
```
3. **重试策略**:
- 对网络错误使用重试
- 对 5xx 服务器错误使用重试
- 对 4xx 客户端错误(如 401、404)不使用重试
4. **错误日志**:集成错误监控服务(如 Sentry)
```javascript
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: (error) => {
Sentry.captureException(error);
},
},
},
});
```
5. **用户反馈**:
- 显示加载状态
- 显示错误信息
- 提供重试按钮
6. **乐观更新错误处理**:在 mutation 中正确处理回滚
```javascript
const mutation = useMutation(updateTodo, {
onMutate: () => { /* 乐观更新 */ },
onError: (error, variables, context) => {
// 回滚数据
queryClient.setQueryData('todos', context.previousTodos);
// 显示错误信息
showNotification('更新失败', 'error');
},
});
```
通过合理配置错误处理和重试机制,可以显著提高应用的可靠性和用户体验。
服务端 · 3月7日 12:25
React Query 有哪些高级特性,如依赖查询、并行查询和窗口聚焦重新获取?React Query 提供了许多高级特性,使数据管理更加灵活和强大:
### 1. 依赖查询(Dependent Queries)
依赖查询是指一个查询的执行依赖于另一个查询的结果。
**使用场景**:当你需要先获取一个资源的 ID,然后用这个 ID 获取详细信息时。
**实现方式**:
```javascript
const { data: user } = useQuery(['user', userId], fetchUser);
// 只有当 user 存在时才执行第二个查询
const { data: userPosts } = useQuery(
['posts', user?.id],
() => fetchPosts(user.id),
{ enabled: !!user }
);
```
### 2. 并行查询(Parallel Queries)
并行查询是指同时执行多个独立的查询。
**使用场景**:当你需要在一个组件中获取多个不相关的数据时。
**实现方式**:
```javascript
// 方式1:多个 useQuery 钩子
const { data: users } = useQuery('users', fetchUsers);
const { data: posts } = useQuery('posts', fetchPosts);
// 方式2:使用 useQueries 钩子(React Query v3+)
const results = useQueries([
{ queryKey: ['users'], queryFn: fetchUsers },
{ queryKey: ['posts'], queryFn: fetchPosts },
]);
```
### 3. 窗口聚焦重新获取(Window Focus Refetching)
当用户重新聚焦浏览器窗口时,React Query 可以自动重新获取数据。
**使用场景**:确保用户看到的是最新数据,特别是在多标签页应用中。
**配置方式**:
```javascript
// 全局配置
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: true, // 默认值
},
},
});
// 单个查询配置
const { data } = useQuery('todos', fetchTodos, {
refetchOnWindowFocus: false, // 禁用
});
```
### 4. 分页和无限滚动(Pagination & Infinite Scroll)
React Query 提供了专门的钩子来处理分页和无限滚动。
**实现方式**:
```javascript
// 分页查询
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
['posts', pageSize],
({ pageParam = 1 }) => fetchPosts(pageParam, pageSize),
{
getNextPageParam: (lastPage, pages) => {
return lastPage.hasMore ? pages.length + 1 : undefined;
},
}
);
```
### 5. 轮询(Polling)
自动定期重新获取数据。
**使用场景**:需要显示实时数据的应用,如仪表盘、聊天应用等。
**配置方式**:
```javascript
const { data } = useQuery('todos', fetchTodos, {
refetchInterval: 5000, // 每5秒重新获取
refetchIntervalInBackground: true, // 后台也轮询
});
```
### 6. 预取(Prefetching)
提前获取可能需要的数据,提升用户体验。
**实现方式**:
```javascript
// 手动预取
queryClient.prefetchQuery('todos', fetchTodos);
// 视口预取(使用 react-query/prefetch)
usePrefetchQuery('todos', fetchTodos);
```
### 7. 持久化缓存(Persisted Queries)
将缓存数据持久化到 localStorage 或其他存储中。
**实现方式**:使用 `persistQueryClient` 插件
这些高级特性使得 React Query 能够应对各种复杂的数据获取场景,提供更灵活、更高效的数据管理方案。
服务端 · 3月7日 12:25
React Query 如何与 React Suspense 集成使用,有哪些注意事项?React Query 支持与 React Suspense 集成,使得数据获取可以像处理组件渲染一样自然:
### 基本集成方法
1. **启用 Suspense 模式**:
```javascript
const { data } = useQuery('todos', fetchTodos, {
suspense: true,
});
```
2. **使用 Suspense 组件包裹**:
```javascript
function TodoList() {
const { data: todos } = useQuery('todos', fetchTodos, {
suspense: true,
});
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<TodoList />
</Suspense>
);
}
```
### 错误边界处理
当使用 Suspense 模式时,错误需要通过 Error Boundary 捕获:
```javascript
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return <div>Error: {this.state.error.message}</div>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<TodoList />
</Suspense>
</ErrorBoundary>
);
}
```
### 注意事项
1. **兼容性**:
- Suspense 模式需要 React 16.6+ 版本
- 确保所有相关组件都支持 Suspense
2. **错误处理**:
- 必须使用 Error Boundary 捕获错误
- 不能再使用 useQuery 返回的 error 和 isError 属性
3. **缓存行为**:
- 即使数据在缓存中,首次渲染时仍会触发 Suspense
- 后续渲染会直接使用缓存数据,不会触发 Suspense
4. **性能考虑**:
- Suspense 模式可能会导致组件树的多次渲染
- 对于复杂应用,需要权衡用户体验和性能
5. **与其他功能的兼容性**:
- **窗口聚焦重新获取**:可能会导致意外的 Suspense 触发
- **轮询**:需要谨慎配置,避免频繁触发 Suspense
6. **服务器端渲染**:
- 在 SSR 环境中使用 Suspense 需要特殊处理
- 推荐使用 `dehydrate` 和 `hydrate` 方法
### 最佳实践
1. **渐进式采用**:
- 先在非关键组件中尝试 Suspense 模式
- 逐步扩展到整个应用
2. **合理使用 fallback**:
- 提供有意义的加载状态
- 避免使用过于简单的加载指示器
3. **结合预取**:
- 使用 `queryClient.prefetchQuery` 提前获取数据
- 减少 Suspense 的触发次数
4. **错误边界策略**:
- 在适当的层级放置 Error Boundary
- 提供具体的错误信息和恢复选项
通过合理集成 React Query 和 Suspense,可以创建更加流畅、响应迅速的用户界面,同时保持代码的简洁性和可读性。
服务端 · 3月7日 12:25
如何在 Zustand 中优化状态更新和性能?### Zustand 中的性能优化方法:
1. **选择性订阅**:
```javascript
// 不推荐:订阅整个 store,会导致组件在任何状态变化时都重渲染
const { count, user } = useStore();
// 推荐:只订阅需要的状态部分
const count = useStore((state) => state.count);
const user = useStore((state) => state.user);
```
2. **使用 shallow 比较**(对于复杂对象):
```javascript
import { create } from 'zustand';
import { shallow } from 'zustand/shallow';
// 订阅多个状态并使用 shallow 比较
const { count, user } = useStore(
(state) => ({ count: state.count, user: state.user }),
shallow // 只有当 count 或 user 真正变化时才重渲染
);
```
3. **状态拆分**:
```javascript
// 按功能拆分多个 store
// userStore.js
const useUserStore = create((set) => ({
user: null,
setUser: (user) => set({ user })
}));
// counterStore.js
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}));
```
4. **使用 get 访问当前状态**(避免闭包陷阱):
```javascript
const useStore = create((set, get) => ({
count: 0,
// 推荐:使用 get 获取最新状态
increment: () => set((state) => ({ count: state.count + 1 })),
// 也可以使用 get
incrementAsync: async () => {
await someAsyncOperation();
set({ count: get().count + 1 });
}
}));
```
5. **批量更新**:
```javascript
// Zustand 会自动批量处理多个 set 调用
const updateMultiple = () => {
set({ count: 1 });
set({ user: { name: 'John' } });
// 只会触发一次重渲染
};
```
6. **避免在组件渲染时创建新函数**:
```javascript
// 不推荐:每次渲染都创建新函数
const incrementBy = (value) => useStore.getState().incrementBy(value);
// 推荐:在 store 中定义方法
// 在 store 中:
incrementBy: (value) => set((state) => ({ count: state.count + value }))
// 在组件中:
const incrementBy = useStore((state) => state.incrementBy);
```
### 关键点:
* 选择性订阅是 Zustand 性能优化的核心
* 使用 shallow 比较可以优化复杂对象的订阅
* 状态拆分可以减少不必要的重渲染
* 合理使用 get 可以避免闭包陷阱
* Zustand 会自动处理批量更新
服务端 · 3月7日 12:01
如何在 Zustand 中创建自定义中间件?在 Zustand 中创建自定义中间件非常灵活,可以用来实现各种功能,如日志记录、状态验证、性能监控等。
### 基本自定义中间件结构:
```javascript
const customMiddleware = (config) => (set, get, api) => {
// 在原始 store 之前执行的逻辑
const originalSet = set;
// 包装 set 函数
const wrappedSet = (partial, replace) => {
// 在状态更新前执行逻辑
console.log('State will update:', partial);
// 调用原始 set
const result = originalSet(partial, replace);
// 在状态更新后执行逻辑
console.log('State updated:', get());
return result;
};
// 创建 store
const store = config(wrappedSet, get, api);
// 返回增强后的 store
return store;
};
```
### 示例 1:日志中间件
```javascript
const loggerMiddleware = (config) => (set, get, api) => {
const originalSet = set;
const wrappedSet = (partial, replace) => {
const previousState = get();
const result = originalSet(partial, replace);
const nextState = get();
console.log('Previous state:', previousState);
console.log('Action:', partial);
console.log('Next state:', nextState);
return result;
};
return config(wrappedSet, get, api);
};
// 使用
const useStore = create(
loggerMiddleware((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}))
);
```
### 示例 2:状态验证中间件
```javascript
const validationMiddleware = (schema) => (config) => (set, get, api) => {
const originalSet = set;
const wrappedSet = (partial, replace) => {
// 验证状态更新
const newState = typeof partial === 'function'
? partial(get())
: partial;
const validation = schema.safeParse({ ...get(), ...newState });
if (!validation.success) {
console.error('State validation failed:', validation.error);
throw new Error('Invalid state update');
}
return originalSet(partial, replace);
};
return config(wrappedSet, get, api);
};
// 使用
import { z } from 'zod';
const storeSchema = z.object({
count: z.number().min(0),
user: z.object({
id: z.string(),
name: z.string().min(1)
}).nullable()
});
const useStore = create(
validationMiddleware(storeSchema)((set) => ({
count: 0,
user: null,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: Math.max(0, state.count - 1) }))
}))
);
```
### 示例 3:性能监控中间件
```javascript
const performanceMiddleware = (config) => (set, get, api) => {
const originalSet = set;
const renderCounts = {};
const wrappedSet = (partial, replace) => {
const startTime = performance.now();
const result = originalSet(partial, replace);
const endTime = performance.now();
const duration = endTime - startTime;
if (duration > 10) {
console.warn(`Slow state update: ${duration.toFixed(2)}ms`, partial);
}
return result;
};
const store = config(wrappedSet, get, api);
// 跟踪组件渲染次数
const originalSubscribe = api.subscribe;
api.subscribe = (listener, selector) => {
const wrappedListener = (state, previousState) => {
const key = selector ? selector.toString() : 'full-store';
renderCounts[key] = (renderCounts[key] || 0) + 1;
if (renderCounts[key] % 10 === 0) {
console.log(`Render count for ${key}:`, renderCounts[key]);
}
listener(state, previousState);
};
return originalSubscribe(wrappedListener, selector);
};
return store;
};
// 使用
const useStore = create(
performanceMiddleware((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}))
);
```
### 示例 4:撤销/重做中间件
```javascript
const undoRedoMiddleware = (config) => (set, get, api) => {
let history = [];
let future = [];
const MAX_HISTORY = 50;
const originalSet = set;
const wrappedSet = (partial, replace) => {
const previousState = get();
const result = originalSet(partial, replace);
const nextState = get();
// 保存到历史记录
history.push(previousState);
if (history.length > MAX_HISTORY) {
history.shift();
}
// 清空未来记录
future = [];
return result;
};
const store = config(wrappedSet, get, api);
// 添加撤销功能
store.undo = () => {
if (history.length === 0) return;
const previousState = history.pop();
future.push(get());
originalSet(previousState, true);
};
// 添加重做功能
store.redo = () => {
if (future.length === 0) return;
const nextState = future.pop();
history.push(get());
originalSet(nextState, true);
};
// 清空历史
store.clearHistory = () => {
history = [];
future = [];
};
return store;
};
// 使用
const useStore = create(
undoRedoMiddleware((set, get) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 }))
}))
);
// 在组件中使用
function Counter() {
const { count, increment, decrement } = useStore();
const undo = useStore((state) => state.undo);
const redo = useStore((state) => state.redo);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={undo}>Undo</button>
<button onClick={redo}>Redo</button>
</div>
);
}
```
### 关键点:
* 自定义中间件是一个高阶函数,接收 config 并返回新的配置函数
* 可以包装 set、get 和 api 来增强功能
* 中间件的执行顺序很重要,通常外层中间件先执行
* 可以在中间件中添加额外的功能,如日志、验证、性能监控等
* 中间件可以返回增强后的 store,添加新的方法或属性
服务端 · 3月7日 12:01
如何在 Zustand 中处理异步操作?### 在 Zustand 中处理异步操作的方法:
1. **基本异步操作**:
```javascript
import { create } from 'zustand';
const useStore = create((set, get) => ({
// 状态
user: null,
loading: false,
error: null,
// 异步操作
fetchUser: async (userId) => {
try {
set({ loading: true, error: null });
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
set({ user: userData, loading: false });
} catch (err) {
set({ error: err.message, loading: false });
}
},
// 另一种方式:使用 get 获取最新状态
updateUserProfile: async (updates) => {
try {
set({ loading: true, error: null });
const currentUser = get().user;
const response = await fetch(`/api/users/${currentUser.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
});
const updatedUser = await response.json();
set({ user: updatedUser, loading: false });
} catch (err) {
set({ error: err.message, loading: false });
}
}
}));
```
2. **使用 Promise 链**:
```javascript
const useStore = create((set) => ({
data: null,
status: 'idle', // idle, loading, success, error
fetchData: () => {
set({ status: 'loading' });
return fetch('/api/data')
.then((response) => response.json())
.then((data) => {
set({ data, status: 'success' });
return data;
})
.catch((error) => {
set({ error: error.message, status: 'error' });
throw error;
});
}
}));
```
3. **结合 React Query 或 SWR**:
```javascript
// 可以在 Zustand 中存储查询结果,同时使用 React Query 处理缓存和失效
import { create } from 'zustand';
import { useQuery } from 'react-query';
const useStore = create((set) => ({
user: null,
setUser: (user) => set({ user })
}));
// 在组件中
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery(
['user', userId],
() => fetch(`/api/users/${userId}`).then(res => res.json())
);
// 当查询成功时,更新 Zustand store
React.useEffect(() => {
if (data) {
useStore.getState().setUser(data);
}
}, [data]);
// 使用 Zustand 中的用户数据
const user = useStore(state => state.user);
return (
<div>
{isLoading && <p>Loading...</p>}
{error && <p>Error: {error.message}</p>}
{user && <p>User: {user.name}</p>}
</div>
);
}
```
### 关键点:
* Zustand 支持直接在 store 方法中使用 async/await
* 可以在异步操作中管理 loading 和 error 状态
* 使用 `get()` 获取最新状态,避免闭包陷阱
* 可以返回 Promise 以便在组件中处理异步操作的结果
* 可以与 React Query 或 SWR 等库结合使用,获得更好的缓存和失效策略
服务端 · 3月7日 11:48