Просмотр исходного кода

docs(mini-ui-standards): 添加useMutation和useInfiniteQuery规范(v1.3)

更新内容:
- 9.1.4节: 新增useMutation数据修改规范
  * 基本用法示例
  * 错误处理和乐观更新模式
  * 表单提交最佳实践
  * 成功后invalidateQueries缓存失效

- 9.1.5节: 新增useInfiniteQuery无限滚动规范
  * 分页数据结构定义
  * getNextPageParam实现逻辑
  * pages.flatMap数据合并模式
  * ScrollView onScrollToLower集成

- 11.4节: 补充mutation和infinite query常见问题
  * mutation后数据未更新解决方案
  * 无限滚动重复数据问题处理

- 12节最佳实践: 新增数据获取/修改/无限滚动子章节
  * 12.1: 数据获取(useQuery)
  * 12.2: 数据修改(useMutation)
  * 12.3: 无限滚动(useInfiniteQuery)
  * 12.4: 状态管理
  * 12.5: 性能优化
  * 12.6: 代码质量

参考实现: rencai-employment-ui使用React Query的完整示例

🤖 Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 3 недель назад
Родитель
Сommit
e570c2ce6b
1 измененных файлов с 146 добавлено и 2 удалено
  1. 146 2
      docs/architecture/mini-ui-package-standards.md

+ 146 - 2
docs/architecture/mini-ui-package-standards.md

@@ -873,6 +873,125 @@ const MyPage: React.FC = () => {
 }
 ```
 
+#### 9.1.4 数据修改 (useMutation)
+
+对于需要修改服务端数据的操作(POST、PUT、DELETE),使用`useMutation`:
+
+```typescript
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { apiClient } from '../api'
+
+const MyPage: React.FC = () => {
+  const queryClient = useQueryClient()
+
+  // 数据修改mutation
+  const mutation = useMutation({
+    mutationFn: async (formData: MyFormData) => {
+      const res = await apiClient.resource.$post({
+        json: formData
+      })
+      if (!res.ok) {
+        throw new Error('操作失败')
+      }
+      return await res.json()
+    },
+    onSuccess: (data) => {
+      // 成功后刷新相关查询
+      queryClient.invalidateQueries({ queryKey: ['resource-list'] })
+      Taro.showToast({
+        title: '操作成功',
+        icon: 'success'
+      })
+    },
+    onError: (error) => {
+      Taro.showToast({
+        title: error.message,
+        icon: 'none'
+      })
+    }
+  })
+
+  const handleSubmit = (formData: MyFormData) => {
+    mutation.mutate(formData)
+  }
+
+  return (
+    <View>
+      <button onClick={() => handleSubmit(formData)}>
+        {mutation.isPending ? '提交中...' : '提交'}
+      </button>
+    </View>
+  )
+}
+```
+
+**关键点**:
+- 使用`mutationFn`定义异步操作
+- 使用`onSuccess`处理成功逻辑,通常需要`invalidateQueries`刷新数据
+- 使用`onError`处理错误
+- 使用`isPending`判断加载状态
+
+#### 9.1.5 无限滚动查询 (useInfiniteQuery)
+
+对于分页列表数据,使用`useInfiniteQuery`实现无限滚动:
+
+```typescript
+import { useInfiniteQuery } from '@tanstack/react-query'
+import { apiClient } from '../api'
+
+const MyPage: React.FC = () => {
+  // 无限滚动查询
+  const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
+    queryKey: ['infinite-list'],
+    queryFn: async ({ pageParam = 0 }) => {
+      const res = await apiClient.items.$get({
+        query: { skip: pageParam * 20, take: 20 }
+      })
+      if (!res.ok) {
+        throw new Error('获取数据失败')
+      }
+      const data = await res.json()
+      return {
+        items: data.data || [],
+        nextCursor: pageParam + 1
+      }
+    },
+    initialPageParam: 0,
+    getNextPageParam: (lastPage) => lastPage.nextCursor
+  })
+
+  // 扁平化所有页的数据
+  const allItems = data?.pages.flatMap(page => page.items) || []
+
+  const handleLoadMore = () => {
+    if (hasNextPage && !isFetchingNextPage) {
+      fetchNextPage()
+    }
+  }
+
+  return (
+    <ScrollView
+      scrollY
+      onScrollToLower={handleLoadMore}
+    >
+      {allItems.map(item => (
+        <View key={item.id}>{item.name}</View>
+      ))}
+
+      {isFetchingNextPage && <Text>加载更多...</Text>}
+      {!hasNextPage && allItems.length > 0 && <Text>没有更多数据了</Text>}
+    </ScrollView>
+  )
+}
+```
+
+**关键点**:
+- `pageParam`用于传递分页参数
+- `getNextPageParam`决定是否有下一页
+- 使用`pages.flatMap`合并所有页数据
+- 使用`fetchNextPage`加载下一页
+- 使用`hasNextPage`和`isFetchingNextPage`控制加载状态
+
 ## 测试规范
 
 ### 10.1 Jest配置
@@ -1020,6 +1139,14 @@ describe('MyPage', () => {
 - **原因**: 不同查询使用了相同的queryKey
 - **解决**: 为每个查询使用唯一的queryKey
 
+**问题**: mutation后数据没有更新
+- **原因**: 忘记调用invalidateQueries
+- **解决**: 在onSuccess回调中刷新相关查询
+
+**问题**: 无限滚动一直触发加载
+- **原因**: getNextPageParam返回逻辑错误
+- **解决**: 正确判断是否还有下一页,返回undefined或nextCursor
+
 ## 最佳实践
 
 ### 12.1 组件开发
@@ -1040,7 +1167,23 @@ describe('MyPage', () => {
 4. **多个独立查询使用不同的queryKey**
 5. **测试中使用真实的QueryClientProvider**
 
-### 12.3 性能优化
+### 12.3 数据修改
+
+1. **使用useMutation处理POST/PUT/DELETE操作**
+2. **在mutationFn中检查response.ok,失败时throw Error**
+3. **使用onSuccess刷新相关查询(invalidateQueries)**
+4. **使用onError显示错误提示**
+5. **使用isPending显示加载状态,避免重复提交**
+
+### 12.4 无限滚动
+
+1. **使用useInfiniteQuery处理分页列表**
+2. **使用pages.flatMap合并所有页数据**
+3. **正确实现getNextPageParam判断是否有下一页**
+4. **使用hasNextPage和isFetchingNextPage控制加载状态**
+5. **在ScrollView的onScrollToLower中触发fetchNextPage**
+
+### 12.5 性能优化
 
 1. **使用Image组件的lazyLoad属性**
 2. **列表数据使用虚拟滚动**
@@ -1048,7 +1191,7 @@ describe('MyPage', () => {
 4. **使用React.memo优化组件性能**
 5. **利用React Query的缓存机制减少重复请求**
 
-### 12.4 代码质量
+### 12.6 代码质量
 
 1. **遵循项目编码标准**
 2. **编写单元测试和集成测试**
@@ -1079,6 +1222,7 @@ describe('MyPage', () => {
 
 | 版本 | 日期 | 变更说明 | 作者 |
 |------|------|----------|------|
+| 1.3 | 2025-12-28 | 添加useMutation和useInfiniteQuery规范,完善React Query最佳实践 | James (Claude Code) |
 | 1.2 | 2025-12-28 | 添加React Query数据获取规范,更新测试规范章节 | James (Claude Code) |
 | 1.1 | 2025-12-26 | 添加图标使用规范(Heroicons) | Bob (Scrum Master) |
 | 1.0 | 2025-12-26 | 基于史诗011和017经验创建Mini UI包开发规范 | Bob (Scrum Master) |