@zero-bits/taro-components 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +261 -291
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,211 +1,257 @@
1
- # @zero-bits/taro
1
+ # @zero-bits/taro-components
2
2
 
3
- 这是一个针对 Taro 环境深度定制的企业级基础库,包含极其强大的 **OnionRequest (洋葱模型网络请求引擎)** 以及 **Permission (全局权限状态控制组件)**。
3
+ 针对 Taro 环境深度定制的企业级基础组件库,包含 **View**、**Text** 布局增强组件、**Permission 全局权限状态管理** 以及常用 **Hooks**。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pnpm add @zero-bits/taro-components
9
+ ```
10
+
11
+ 在入口文件中引入样式:
12
+
13
+ ```typescript
14
+ import '@zero-bits/taro-components/index.css'
15
+ ```
4
16
 
5
17
  ## 目录
6
- - [OnionRequest 请求引擎](#onionrequest-请求引擎)
7
- - [1. 引擎初始化配置](#1-引擎初始化配置)
8
- - [2. 在业务中的极简用法](#2-在业务中的极简用法)
9
- - [3. 文件上传与二进制流支持](#3-文件上传与二进制流支持)
10
- - [4. 请求主动强杀 (Abort)](#4-请求主动强杀-abort)
11
- - [Permission 权限组件](#permission-权限组件)
18
+
19
+ - [View 布局组件](#view-布局组件)
20
+ - [Text 文本组件](#text-文本组件)
21
+ - [Permission 权限管理](#permission-权限管理)
12
22
  - [1. 创建全局 Store](#1-创建全局-store)
13
23
  - [2. 挂载 Provider](#2-挂载-provider)
14
24
  - [3. 在组件中使用权限状态](#3-在组件中使用权限状态)
15
- - [Alova CQRS 请求钩子引擎 (Hooks)](#alova-cqrs-请求钩子引擎-hooks)
16
- - [1. 钩子工厂初始化](#1-钩子工厂初始化)
17
- - [2. useQuery (声明式读取)](#2-usequery-声明式读取)
18
- - [3. useMutation (命令式写入)](#3-usemutation-命令式写入)
25
+ - [useWeappUpdate 自动更新](#useweappupdate-自动更新)
19
26
 
20
27
  ---
21
28
 
22
- ## OnionRequest 请求引擎
29
+ ## View 布局组件
23
30
 
24
- 基于洋葱模型(Koa 风格)构建的终极请求引擎,支持**无感 Token 刷新、自动提纯数据、原生 Abort 强杀防爆红、并发竞态控制、跨端 Blob/ArrayBuffer 免疫**等高级特性。
31
+ 对 `@tarojs/components` 的 `View` 进行深度封装,提供大量 Flex 布局与样式的 **Props 语法糖**,告别内联 `style` 对象,代码更简洁。
25
32
 
26
- ### 1. 引擎初始化配置
33
+ ### 引入
27
34
 
28
- 在项目中创建 `src/utils/request.ts` 进行初始化:
35
+ ```tsx
36
+ import { View } from '@zero-bits/taro-components'
37
+ ```
29
38
 
30
- ```typescript
31
- import Taro from '@tarojs/taro'
32
- import { createRequest, AbortError } from '@zero-bits/taro/onion-request'
33
-
34
- export const request = createRequest({
35
- // 1. 基础配置
36
- baseURL: 'https://api.your-company.com',
37
- timeout: 10000,
38
-
39
- // 2. 动态 Token 注入
40
- getToken: () => Taro.getStorageSync('access_token'),
41
- authHeaderKey: 'Authorization',
42
- formatAuthValue: (token) => `Bearer ${token}`,
43
-
44
- // 3. 全局业务报错拦截 (已免疫 Abort 误杀)
45
- onBizError: (err) => {
46
- Taro.showToast({
47
- title: err?.msg || err?.message || '网络繁忙,请稍后再试',
48
- icon: 'none'
49
- })
50
- },
51
-
52
- // 4. 数据提纯与业务成功判定
53
- isSuccess: (data) => data.code === 0 || data.code === 200,
54
- resolveData: (data) => data.data, // 业务层调用时只需拿到真正的有效数据
55
-
56
- // 5. 终极防御:无感刷新策略
57
- refreshToken: {
58
- url: '/v1/auth/refresh', // 刷新 Token 的接口
59
- method: 'POST', // 后端要求的方法
60
- getParams: () => ({
61
- refresh_token: Taro.getStorageSync('refresh_token')
62
- }),
63
-
64
- // 异步落地新 Token(此时被 401 拦截挂起的请求正在排队等待)
65
- onRefreshed: async (resData) => {
66
- Taro.setStorageSync('access_token', resData.data.access_token)
67
- Taro.setStorageSync('refresh_token', resData.data.refresh_token)
68
- },
69
-
70
- // 刷新失败(例如 refresh_token 也过期了),强制登出
71
- onRefreshFailed: () => {
72
- Taro.removeStorageSync('access_token')
73
- Taro.reLaunch({ url: '/pages/login/index' })
74
- }
75
- }
76
- })
39
+ ### Props 一览
40
+
41
+ #### Flex 布局
42
+
43
+ | Prop | 类型 | 说明 |
44
+ |---|---|---|
45
+ | `row` | `boolean` | `flex-direction: row`,主轴水平 |
46
+ | `column` | `boolean` | `flex-direction: column`,主轴垂直 |
47
+ | `flex` | `boolean` | `flex: 1`,自动占满剩余空间 |
48
+ | `wrap` | `boolean` | `flex-wrap: wrap`,换行 |
49
+ | `between` | `boolean` | `justify-content: space-between` |
50
+ | `around` | `'justify-around' \| 'content-around'` | 主轴平均分布 |
51
+ | `justifyItems` | `'flex-start' \| 'flex-end' \| 'center'` | 主轴对齐方式 |
52
+ | `alignItems` | `'flex-start' \| 'flex-end' \| 'center'` | 交叉轴对齐方式 |
53
+ | `alignSelf` | `string` | 自身对齐方式 |
54
+ | `gap` | `string \| number` | 子元素间距 |
55
+ | `rowGap` | `string \| number` | 行间距 |
56
+ | `columnGap` | `string \| number` | 列间距 |
57
+ | `flexShrink` | `number \| string` | 收缩比例 |
58
+ | `flexGrow` | `number \| string` | 放大比例 |
59
+ | `flexBasis` | `string \| number` | 初始大小 |
60
+
61
+ #### 尺寸与定位
62
+
63
+ | Prop | 类型 | 说明 |
64
+ |---|---|---|
65
+ | `full` | `boolean` | `width: 100%; height: 100%` |
66
+ | `width` | `string \| number` | 宽度(数字自动加 `px`) |
67
+ | `height` | `string \| number` | 高度 |
68
+ | `position` | `'static' \| 'relative' \| 'absolute' \| 'fixed' \| 'sticky'` | 定位方式 |
69
+ | `zIndex` | `number \| string` | 层级 |
70
+ | `top / right / bottom / left` | `number \| string` | 定位偏移量 |
71
+ | `overflow` | `'hidden' \| 'visible' \| 'scroll'` | 溢出处理 |
72
+
73
+ #### 外观
74
+
75
+ | Prop | 类型 | 说明 |
76
+ |---|---|---|
77
+ | `bgColor` | `string` | 背景色 |
78
+ | `radius` | `string \| number` | 圆角 |
79
+ | `border` | `boolean` | 显示边框 |
80
+ | `borderColor` | `string` | 边框颜色 |
81
+ | `borderWidth` | `number \| string` | 边框宽度,默认 `1` |
82
+ | `borderStyle` | `'solid' \| 'dashed' \| 'dotted'` | 边框样式,默认 `solid` |
83
+ | `opacity` | `number \| string` | 透明度 |
84
+ | `hidden` | `boolean` | 隐藏(`display: none`) |
85
+ | `hover` | `boolean` | 点击降低透明度的悬停效果 |
86
+ | `skeleton` | `boolean` | 骨架屏闪烁效果 |
87
+
88
+ #### 间距(数字自动加 `px`)
89
+
90
+ | Prop | 对应 CSS |
91
+ |---|---|
92
+ | `margin` | `margin` |
93
+ | `mTop` | `margin-top` |
94
+ | `mBottom` | `margin-bottom` |
95
+ | `mLeft` | `margin-left` |
96
+ | `mRight` | `margin-right` |
97
+ | `mHorizontal` | `margin-left` + `margin-right` |
98
+ | `mVertical` | `margin-top` + `margin-bottom` |
99
+ | `padding` | `padding` |
100
+ | `pTop` | `padding-top` |
101
+ | `pBottom` | `padding-bottom` |
102
+ | `pLeft` | `padding-left` |
103
+ | `pRight` | `padding-right` |
104
+ | `pHorizontal` | `padding-left` + `padding-right` |
105
+ | `pVertical` | `padding-top` + `padding-bottom` |
106
+
107
+ #### 安全区域
108
+
109
+ | Prop | 类型 | 说明 |
110
+ |---|---|---|
111
+ | `safeArea` | `'top' \| 'bottom' \| 'both'` | 自动叠加 `env(safe-area-inset-*)` |
112
+
113
+ ### 使用示例
77
114
 
78
- // 按需导出核心错误类,供页面使用
79
- export { AbortError }
115
+ ```tsx
116
+ import { View } from '@zero-bits/taro-components'
117
+
118
+ // 水平居中布局
119
+ <View row alignItems="center" justifyItems="center" gap={12}>
120
+ <View width={40} height={40} bgColor="#f0f0f0" radius={8} />
121
+ <View flex column>
122
+ <View height={16} bgColor="#333" radius={4} />
123
+ </View>
124
+ </View>
125
+
126
+ // 底部安全区域
127
+ <View safeArea="bottom" bgColor="#fff" pHorizontal={16}>
128
+ <Button>提交</Button>
129
+ </View>
130
+
131
+ // 绝对定位遮罩
132
+ <View position="absolute" top={0} left={0} full bgColor="rgba(0,0,0,0.5)" />
133
+
134
+ // 骨架屏占位
135
+ <View skeleton width={200} height={20} radius={4} />
80
136
  ```
81
137
 
82
- ### 2. 在业务中的极简用法
138
+ ---
83
139
 
84
- 引擎在底层吞下了所有苦难(包括提纯、401 刷新等),留给业务层的只有纯粹的快乐路径 (Happy Path):
140
+ ## Text 文本组件
85
141
 
86
- ```typescript
87
- import { useEffect } from 'react'
88
- import { request, AbortError } from '@/utils/request'
89
-
90
- export default function Home() {
91
- useEffect(() => {
92
- // 1. 发起请求
93
- request.get('/v1/user/profile')
94
- .then((data) => {
95
- // data 已经是提纯后的有效数据,没有 code/msg 外壳
96
- // 且它绝对是通过了 2XX 和业务 code === 0 双重校验的
97
- console.log('获取用户信息成功:', data)
98
- })
99
- .catch((err) => {
100
- // 全局弹窗已经替你报错了,如果是主动取消,可以选择忽略
101
- if (err instanceof AbortError) return
102
- console.error('获取失败兜底:', err)
103
- })
104
- }, [])
105
-
106
- return <View>Hello OnionRequest</View>
107
- }
142
+ 对 `@tarojs/components` 的 `Text` 进行封装,提供字体、间距、溢出省略等 Props 语法糖。
143
+
144
+ ### 引入
145
+
146
+ ```tsx
147
+ import { Text } from '@zero-bits/taro-components'
108
148
  ```
109
149
 
110
- ### 3. 文件上传与二进制流支持
150
+ ### Props 一览
111
151
 
112
- 提供极致优雅的上传语法糖,自动识别并配置 `multipart/form-data`。并自动对下载的二进制流实现提纯免疫。
152
+ | Prop | 类型 | 说明 |
153
+ |---|---|---|
154
+ | `fontSize` | `string \| number` | 字体大小 |
155
+ | `color` | `string` | 字体颜色 |
156
+ | `bold` | `boolean \| number \| string` | 字体粗细,`true` 等同于 `bold` |
157
+ | `lineHeight` | `string \| number` | 行高 |
158
+ | `textAlign` | `'left' \| 'center' \| 'right' \| 'justify'` | 对齐方式 |
159
+ | `decoration` | `'underline' \| 'line-through' \| 'none'` | 文字装饰 |
160
+ | `clamp` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | 溢出省略显示行数 |
161
+ | `selectable` | `boolean` | 是否可长按选中 |
162
+ | `bgColor` | `string` | 背景色 |
163
+ | `width` | `string \| number` | 宽度 |
164
+ | `height` | `string \| number` | 高度 |
165
+ | `skeleton` | `boolean` | 骨架屏效果 |
166
+ | `style` | `CSSProperties` | 自定义样式(与上述 Props 合并) |
113
167
 
114
- ```typescript
115
- // 纯图片上传
116
- const res1 = await request.upload('/v1/upload', 'wxfile://tmp_xxx.jpg')
117
-
118
- // 带额外表单参数的上传
119
- const res2 = await request.upload('/v1/upload', 'wxfile://tmp_xxx.jpg', {
120
- userId: '10086',
121
- type: 'avatar'
122
- })
123
-
124
- // 自定义上传字段名 (比如后端要求字段名是 'avatar' 而不是默认的 'file')
125
- const res3 = await request.upload('/v1/upload', 'wxfile://tmp_xxx.jpg', null, {
126
- name: 'avatar'
127
- })
128
-
129
- // 文件下载(二进制流)
130
- const res4 = await request.get('/v1/report/export', null, {
131
- responseType: 'arraybuffer' // 引擎会自动探测到流并静默放行,避免提纯引发崩溃
132
- })
133
- ```
168
+ > 同样支持所有 `margin` / `padding` 系列间距 Props,与 `View` 一致。
134
169
 
135
- ### 4. 请求主动强杀 (Abort)
170
+ ### 使用示例
136
171
 
137
- 框架赋予了 `request` 暴露出的 Promise 直接调用 `.abort()` 的能力。支持极速切断网络层,彻底杜绝组件卸载后请求依然在后台发送的情况。
172
+ ```tsx
173
+ import { Text } from '@zero-bits/taro-components'
138
174
 
139
- ```typescript
140
- import { useEffect, useRef } from 'react'
141
- import { request } from '@/utils/request'
175
+ // 标题
176
+ <Text fontSize={18} bold color="#333" lineHeight={28}>用户名称</Text>
142
177
 
143
- export default function UserList() {
144
- const reqRef = useRef<any>()
178
+ // 两行省略
179
+ <Text clamp={2} fontSize={14} color="#666">
180
+ 这是一段很长的描述文字,超过两行之后会自动省略显示...
181
+ </Text>
145
182
 
146
- useEffect(() => {
147
- reqRef.current = request.get('/v1/users')
183
+ // 价格文字
184
+ <Text fontSize={24} bold color="#f00" decoration="underline">¥99.00</Text>
148
185
 
149
- return () => {
150
- // 组件卸载时的极致防御:强杀正在排队/请求中的任务
151
- reqRef.current?.abort()
152
- }
153
- }, [])
154
- }
186
+ // 骨架屏占位
187
+ <Text skeleton fontSize={14} width={120}>占位文字</Text>
155
188
  ```
156
189
 
157
190
  ---
158
191
 
159
- ## Permission 权限组件
192
+ ## Permission 权限管理
193
+
194
+ 基于 `useSyncExternalStore` 构建的脱离 React 上下文的全局权限管理器,支持在请求拦截器中触发注销,自动驱动全站 UI 响应。
160
195
 
161
- 基于 `useSyncExternalStore` 构建的脱离 React 上下文的全局权限管理器。支持在 `axios/request` 拦截器中触发注销,自动驱动全站 UI 响应。
196
+ ### 引入
197
+
198
+ ```typescript
199
+ import { createPermissionStore, PermissionProvider } from '@zero-bits/taro-components'
200
+ import { usePermission } from '@zero-bits/taro-components'
201
+ ```
162
202
 
163
203
  ### 1. 创建全局 Store
164
204
 
165
205
  在项目中创建 `src/store/permission.ts`:
166
206
 
167
207
  ```typescript
168
- import { createPermissionStore } from '@zero-bits/taro/components/Permission'
208
+ import { createPermissionStore } from '@zero-bits/taro-components'
169
209
 
170
- // 定义权限类型和用户信息类型
210
+ // 定义权限对象类型
171
211
  interface MyPermissions {
172
212
  canAddUser: boolean
173
213
  canDeleteUser: boolean
214
+ canExport: boolean
174
215
  }
175
216
 
217
+ // 定义用户信息类型
176
218
  interface MyUser {
177
219
  id: string
178
220
  name: string
179
- role: string
221
+ role: 'admin' | 'editor' | 'viewer'
180
222
  }
181
223
 
182
- // 初始化全局单例 Store
224
+ // 初始化全局单例 Store(整个应用只需一个)
183
225
  export const permissionStore = createPermissionStore<MyPermissions, MyUser>()
184
226
  ```
185
227
 
186
- 你甚至可以在网络拦截器(如上面的 OnionRequest 的 `onRefreshFailed`)中直接使用它:
228
+ 可以在任何模块(如请求拦截器)中直接操作:
229
+
187
230
  ```typescript
188
- // 当刷新失败时,清除全局登录态
231
+ import { permissionStore } from '@/store/permission'
232
+
233
+ // 在请求拦截器的 Token 刷新失败回调中
189
234
  onRefreshFailed: () => {
190
- permissionStore.denied()
235
+ permissionStore.denied() // 触发全局登出,PermissionProvider 的 onDenied 会自动响应
191
236
  }
192
237
  ```
193
238
 
194
239
  ### 2. 挂载 Provider
195
240
 
196
- 在小程序的入口文件(如 `app.tsx`)中包裹 Provider:
241
+ 在小程序入口文件 `app.tsx` 中包裹 `PermissionProvider`:
197
242
 
198
- ```typescript
243
+ ```tsx
199
244
  import { PropsWithChildren } from 'react'
200
- import { PermissionProvider } from '@zero-bits/taro/components/Permission'
245
+ import Taro from '@tarojs/taro'
246
+ import { PermissionProvider } from '@zero-bits/taro-components'
201
247
  import { permissionStore } from '@/store/permission'
202
248
 
203
249
  function App({ children }: PropsWithChildren) {
204
250
  return (
205
- <PermissionProvider
251
+ <PermissionProvider
206
252
  store={permissionStore}
207
253
  onDenied={() => {
208
- // 当收到 denied() 调用(比如从拦截器发出的),统一执行登出路由跳转
254
+ // 当 permissionStore.denied() 被调用时,统一执行登出跳转
209
255
  Taro.reLaunch({ url: '/pages/login/index' })
210
256
  }}
211
257
  >
@@ -213,31 +259,31 @@ function App({ children }: PropsWithChildren) {
213
259
  </PermissionProvider>
214
260
  )
215
261
  }
262
+
216
263
  export default App
217
264
  ```
218
265
 
219
266
  ### 3. 在组件中使用权限状态
220
267
 
221
- 在任何子组件中,你可以极其方便地获取登录态、权限字典、用户信息,或是手动操作权限:
222
-
223
- ```typescript
224
- import { usePermission } from '@zero-bits/taro/components/Permission'
225
- import { Button, View, Text } from '@tarojs/components'
268
+ ```tsx
269
+ import { Button } from '@tarojs/components'
270
+ import { View, Text } from '@zero-bits/taro-components'
271
+ import { usePermission } from '@zero-bits/taro-components'
226
272
 
227
273
  export default function Dashboard() {
228
- const {
229
- isGranted,
230
- user,
231
- permissions,
232
- granted,
233
- denied
234
- } = usePermission()
274
+ const {
275
+ isGranted, // boolean - 是否已登录/授权
276
+ user, // MyUser | null - 用户信息
277
+ permissions, // MyPermissions | null - 权限字典
278
+ granted, // (user, permissions) => void - 设置登录态
279
+ denied // () => void - 清除登录态(触发 onDenied)
280
+ } = usePermission<MyPermissions, MyUser>()
235
281
 
236
282
  // 模拟登录
237
283
  const handleLogin = () => {
238
284
  granted(
239
- { id: 'u1', name: 'Zou', role: 'admin' },
240
- { canAddUser: true, canDeleteUser: false }
285
+ { id: 'u001', name: '张三', role: 'admin' },
286
+ { canAddUser: true, canDeleteUser: false, canExport: true }
241
287
  )
242
288
  }
243
289
 
@@ -246,168 +292,92 @@ export default function Dashboard() {
246
292
  }
247
293
 
248
294
  return (
249
- <View>
250
- <Text>欢迎您: {user?.name}</Text>
251
-
295
+ <View column pHorizontal={16}>
296
+ <Text fontSize={16} bold>欢迎,{user?.name}</Text>
297
+
252
298
  {/* 细粒度权限控制 */}
253
- {permissions.canAddUser && <Button>添加用户</Button>}
254
- {permissions.canDeleteUser && <Button>删除用户</Button>}
255
-
256
- {/* 登出 */}
299
+ {permissions?.canAddUser && <Button>新增用户</Button>}
300
+ {permissions?.canDeleteUser && <Button>删除用户</Button>}
301
+ {permissions?.canExport && <Button>导出数据</Button>}
302
+
257
303
  <Button onClick={denied}>退出登录</Button>
258
304
  </View>
259
305
  )
260
306
  }
261
307
  ```
262
308
 
263
- ---
264
-
265
- ## Axios 适配器 (Taro Request)
266
-
267
- 如果您或您的团队更倾向于使用传统的 Axios 生态(或者需要复用已有的 Axios 拦截器与代码库),我们同样提供了一套极其严谨的 **Taro Axios 适配器**,完美抹平了 Web 与小程序的网络层差异。
268
-
269
- ### 1. 初始化 Axios 引擎
309
+ #### `usePermission` 返回值
270
310
 
271
- 在项目中引入并替换 Axios 的原生 Adapter:
311
+ | 字段 | 类型 | 说明 |
312
+ |---|---|---|
313
+ | `isGranted` | `boolean` | 当前是否处于已授权(登录)状态 |
314
+ | `user` | `U \| null` | 当前用户信息,未登录时为 `null` |
315
+ | `permissions` | `P \| null` | 当前权限字典,未登录时为 `null` |
316
+ | `granted` | `(user: U, permissions: P) => void` | 设置登录态与权限 |
317
+ | `denied` | `() => void` | 清除登录态,触发 Provider 的 `onDenied` 回调 |
272
318
 
273
- ```typescript
274
- import { initializeRequest, request, taroAdapter, defaultTransformRequest } from '@zero-bits/taro/request'
275
-
276
- // 全局初始化
277
- initializeRequest({
278
- baseURL: 'https://api.your-company.com',
279
- timeout: 10000,
280
-
281
- // 核心:强制 Axios 使用 Taro 的网络底层
282
- adapter: taroAdapter,
283
-
284
- // 推荐:使用我们深度防御后的序列化方法
285
- transformRequest: [defaultTransformRequest]
286
- })
287
-
288
- // 按需添加 Axios 传统的拦截器
289
- request.interceptors.request.use(config => {
290
- config.headers.Authorization = 'Bearer token123'
291
- return config
292
- })
293
- ```
294
-
295
- ### 2. 发起请求
296
-
297
- 配置完毕后,它完全等同于一个标准的 Axios 实例:
298
-
299
- ```typescript
300
- import { request } from '@/utils/request'
301
-
302
- // GET
303
- const res1 = await request.get('/v1/users')
304
-
305
- // POST
306
- const res2 = await request.post('/v1/users', { name: 'Zou' })
307
-
308
- // 文件上传 (原生适配)
309
- // 只要同时包含了 filePath、name 并且 Content-Type 为 multipart/form-data,
310
- // 适配器底层会自动切换为 Taro.uploadFile
311
- const res3 = await request.post('/v1/upload', {
312
- filePath: 'wxfile://tmp_xxx.jpg',
313
- name: 'file',
314
- type: 'avatar'
315
- }, {
316
- headers: {
317
- 'Content-Type': 'multipart/form-data'
318
- }
319
- })
320
-
321
- // 取消请求 (支持 AbortController 规范)
322
- const controller = new AbortController()
323
- request.get('/v1/long-polling', { signal: controller.signal })
324
- controller.abort() // 原生打断 Taro.RequestTask
325
- ```
326
-
327
- **对比总结**:
328
- - 如果您是从零搭建微信小程序/Taro 项目,强烈建议使用上面的 **OnionRequest**(洋葱模型),它内置了极其强悍的防爆红、无感刷新等特性。
329
- - 如果您的代码库是从 Web (React/Vue) 跨端迁移过来的,含有大量的 Axios 历史逻辑,可以直接使用本节的 **Axios 适配器**。
319
+ > **注意**:`usePermission` 必须在 `<PermissionProvider>` 内部使用,否则会抛出错误。
330
320
 
331
321
  ---
332
322
 
333
- ## Alova CQRS 请求钩子引擎 (Hooks)
334
-
335
- 基于 **Alova V3** 构建的终极 React 请求状态机。它是一个抽象工厂,**可以完美接入上述的 OnionRequest 或 Axios**。
336
- 提供极致的 CQRS (读写分离) 体验,告别手写 `useState(loading)` 和 `try...catch`,内置防抖、竞态控制、React 并发模型防撕裂(Tearing),以及极其严苛的 TypeScript 类型约束。
323
+ ## useWeappUpdate 自动更新
337
324
 
338
- ### 1. 钩子工厂初始化
325
+ 微信小程序版本自动检测与更新提示的封装 Hook。在 app 根组件中调用一次即可,非微信环境自动跳过。
339
326
 
340
- 在项目中创建 `src/utils/requestHooks.ts`,将您的请求实例(Axios 或 Onion)注入工厂,生成您的专属 Hook:
327
+ ### 引入
341
328
 
342
329
  ```typescript
343
- import { createRequestHooks } from '@zero-bits/taro/hooks'
344
- import { request } from './request' // 您的 axios 或 onion 实例
345
-
346
- export const { useQuery, useMutation, alovaInst } = createRequestHooks(request, {
347
- // 全局定义后端的标准响应壳字段,引擎会自动进行“启发式剥壳”
348
- fieldMap: { code: 'code', data: 'data', message: 'message' }
349
- })
330
+ import { useWeappUpdate } from '@zero-bits/taro-components'
350
331
  ```
351
332
 
352
- ### 2. useQuery (声明式读取)
353
-
354
- 专用于页面初次加载或依赖变化时自动获取数据。支持依赖追踪、内置防抖。
333
+ ### 使用示例
355
334
 
356
335
  ```tsx
357
- import { useState } from 'react'
358
- import { useQuery } from '@/utils/requestHooks'
359
-
360
- export default function UserList() {
361
- const [keyword, setKeyword] = useState('')
362
-
363
- // 泛型: <返回的数据体类型, 额外响应字段类型>
364
- const { data: users, loading, extra } = useQuery<User[], { total: number }>(
365
- () => ({
366
- url: '/api/users',
367
- params: { keyword }
368
- }),
369
- [keyword], // keyword 改变自动防抖重新请求
370
- { debounce: 500 }
371
- )
336
+ // app.tsx
337
+ import { useWeappUpdate } from '@zero-bits/taro-components'
372
338
 
373
- return (
374
- <View>
375
- <Input onInput={(e) => setKeyword(e.value)} />
376
- {loading ? <Text>加载中</Text> : <Text>共 {extra?.total} 人</Text>}
377
- {users?.map(u => <View key={u.id}>{u.name}</View>)}
378
- </View>
379
- )
339
+ function App({ children }) {
340
+ // 挂载时自动检测更新,有新版本弹窗提示用户重启
341
+ useWeappUpdate()
342
+
343
+ return <PermissionProvider ...>{children}</PermissionProvider>
380
344
  }
381
345
  ```
382
346
 
383
- ### 3. useMutation (命令式写入)
347
+ ### 行为说明
384
348
 
385
- 专用于增删改操作(如表单提交)。完全受控,通过 `send` 手动触发,并自带 `loading` 防重复提交锁。
349
+ - **检测到新版本**:弹出 Modal 提示"新版本已经准备好,是否重启应用?",用户确认后调用 `applyUpdate()` 重启。
350
+ - **下载失败**:弹出 Modal 提示用户删除小程序后重新搜索打开。
351
+ - **非微信环境**(H5 / RN 等):`process.env.TARO_ENV !== 'weapp'` 时自动跳过,无任何副作用。
386
352
 
387
- ```tsx
388
- import { useMutation } from '@/utils/requestHooks'
389
-
390
- export default function CreateUser() {
391
- // 泛型: <返回的数据体类型, [send的入参类型]>
392
- const { send, loading } = useMutation<null, [string, number]>(
393
- (name, age) => ({
394
- url: '/api/user/create',
395
- method: 'POST',
396
- data: { name, age }
397
- }),
398
- {
399
- onSuccess: (event) => console.log('保存成功', event.data),
400
- onError: (event) => console.error('报错了', event.error)
401
- }
402
- )
353
+ ---
403
354
 
404
- return (
405
- <Button
406
- loading={loading}
407
- onClick={() => send('张三', 25)} // 严格类型推导,必须传 string 和 number
408
- >
409
- 提交
410
- </Button>
411
- )
412
- }
355
+ ## 完整导出列表
356
+
357
+ ```typescript
358
+ // 主入口
359
+ import {
360
+ View,
361
+ Text,
362
+ PermissionProvider,
363
+ createPermissionStore,
364
+ usePermission,
365
+ useWeappUpdate
366
+ } from '@zero-bits/taro-components'
367
+
368
+ // 按路径导入(tree-shaking 友好)
369
+ import { View, Text, PermissionProvider, createPermissionStore } from '@zero-bits/taro-components/components'
370
+ import { usePermission, useWeappUpdate } from '@zero-bits/taro-components/hooks'
371
+
372
+ // 样式
373
+ import '@zero-bits/taro-components/index.css'
413
374
  ```
375
+
376
+ ## Peer Dependencies
377
+
378
+ | 包 | 版本要求 |
379
+ |---|---|
380
+ | `react` | `>= 18.0.0` |
381
+ | `react-dom` | `>= 18.0.0` |
382
+ | `@tarojs/components` | `>= 4.2.1` |
383
+ | `@tarojs/taro` | `>= 4.2.1` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zero-bits/taro-components",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Taro components and utilities",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -65,4 +65,4 @@
65
65
  "dependencies": {
66
66
  "classnames": "^2.3.0"
67
67
  }
68
- }
68
+ }