Repository: jdf2e/nutui-react
Pull request #3149: feat(switch): loading 态受控
Author: login: irisSong
Labels: name: size/L
name: 3.x
description: Target branch 3.x
name: action:review
description: This PR needs more reviews (less than 2 approvals)
Created: 2025-04-03T06:49:56Z | Merged: 2025-04-25T09:00:45Z

<!--
非常感谢您的贡献 维护者审核通过后会合并。
请确保填写以下 pull request 的信息，谢谢！~
-->

### 🤔 这个变动的性质是？

- [ ] 新特性提交
- [ ] 日常 bug 修复
- [ ] 站点、文档改进
- [ ] 演示代码改进
- [ ] 组件样式/交互改进
- [ ] TypeScript 定义更新
- [ ] 包体积优化
- [ ] 性能优化
- [ ] 功能增强
- [ ] 国际化改进
- [ ] 重构
- [ ] 代码风格优化
- [ ] 测试用例
- [ ] 分支合并
- [ ] 其他改动（是关于什么的改动？）

### 🔗 相关 Issue

<!--
1. 描述相关需求的来源，如相关的 issue 讨论链接。
-->

### 💡 需求背景和解决方案

<!--
1. 要解决的具体问题。
2. 列出最终的 API 实现和用法。
3. 涉及UI/交互变动需要有截图或 GIF。
-->

### ☑️ 请求合并前的自查清单

⚠️ 请自检并全部**勾选全部选项**。⚠️

- [ ] 文档已补充或无须补充
- [ ] 代码演示已提供或无须提供
- [ ] TypeScript 定义已补充或无须补充
- [ ] fork仓库代码是否为最新避免文件冲突
- [ ] Files changed 没有 package.json lock 等无关文件


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **新功能**
  - 新增 Demo8 演示组件，展示异步状态更新和交互提示效果。
  - Switch 组件现支持受控 loading 状态，可通过新增属性 `loading` 和事件处理器 `onLoadingChange` 进行加载提示控制。

- **文档**
  - 文档中新增“loading 态受控”章节，详细介绍了相关新属性和事件的使用。
  - 增加中英文及繁体中文翻译，提升多语言用户体验。
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/src/packages/switch/demo.taro.tsx b/src/packages/switch/demo.taro.tsx
index ac961c8b0d..cf8e3ffc3e 100644
--- a/src/packages/switch/demo.taro.tsx
+++ b/src/packages/switch/demo.taro.tsx
@@ -10,6 +10,7 @@ import Demo4 from './demos/taro/demo4'
 import Demo5 from './demos/taro/demo5'
 import Demo6 from './demos/taro/demo6'
 import Demo7 from './demos/taro/demo7'
+import Demo8 from './demos/taro/demo8'
 
 const SwitchDemo = () => {
   const [translated] = useTranslate({
@@ -17,6 +18,7 @@ const SwitchDemo = () => {
       basic: '非受控',
       disabled: '禁用状态',
       asyncControl: '受控',
+      loadingControl: 'loading 态受控',
       customColor: '自定义颜色',
       supportText: '支持文字',
       supportIcon: '支持Icon',
@@ -26,6 +28,7 @@ const SwitchDemo = () => {
       basic: '非受控',
       disabled: '禁用狀態',
       asyncControl: '受控',
+      loadingControl: 'loading 態受控',
       customColor: '自定義顏色',
       supportText: '支持文字',
       supportIcon: '支持Icon',
@@ -35,6 +38,7 @@ const SwitchDemo = () => {
       basic: 'Uncontrolled',
       disabled: 'Disabled',
       asyncControl: 'controlled',
+      loadingControl: 'Controlled with loading',
       customColor: 'Custom Color',
       supportText: 'Support Text',
       supportIcon: 'Support Icon',
@@ -49,6 +53,8 @@ const SwitchDemo = () => {
         <Demo1 />
         <View className="h2">{translated.asyncControl}</View>
         <Demo2 />
+        <View className="h2">{translated.loadingControl}</View>
+        <Demo8 />
         <View className="h2">{translated.disabled}</View>
         <Demo3 />
         <View className="h2">{translated.supportText}</View>
diff --git a/src/packages/switch/demo.tsx b/src/packages/switch/demo.tsx
index 7d33a634d2..758aeaf5ad 100644
--- a/src/packages/switch/demo.tsx
+++ b/src/packages/switch/demo.tsx
@@ -7,6 +7,7 @@ import Demo4 from './demos/h5/demo4'
 import Demo5 from './demos/h5/demo5'
 import Demo6 from './demos/h5/demo6'
 import Demo7 from './demos/h5/demo7'
+import Demo8 from './demos/h5/demo8'
 
 const SwitchDemo = () => {
   const [translated] = useTranslate({
@@ -14,6 +15,7 @@ const SwitchDemo = () => {
       basic: '非受控',
       disabled: '禁用状态',
       asyncControl: '受控',
+      loadingControl: 'loading 态受控',
       customColor: '自定义颜色',
       supportText: '支持文字',
       supportIcon: '支持Icon',
@@ -23,6 +25,7 @@ const SwitchDemo = () => {
       basic: '非受控',
       disabled: '禁用狀態',
       asyncControl: '受控',
+      loadingControl: 'loading 態受控',
       customColor: '自定義顏色',
       supportText: '支持文字',
       supportIcon: '支持Icon',
@@ -32,6 +35,7 @@ const SwitchDemo = () => {
       basic: 'Uncontrolled',
       disabled: 'Disabled',
       asyncControl: 'controlled',
+      loadingControl: 'Controlled with loading',
       customColor: 'Custom Color',
       supportText: 'Support Text',
       supportIcon: 'Support Icon',
@@ -45,6 +49,8 @@ const SwitchDemo = () => {
         <Demo1 />
         <h2>{translated.asyncControl}</h2>
         <Demo2 />
+        <h2>{translated.loadingControl}</h2>
+        <Demo8 />
         <h2>{translated.disabled}</h2>
         <Demo3 />
         <h2>{translated.supportText}</h2>
diff --git a/src/packages/switch/demos/h5/demo8.tsx b/src/packages/switch/demos/h5/demo8.tsx
new file mode 100644
index 0000000000..c137687c35
--- /dev/null
+++ b/src/packages/switch/demos/h5/demo8.tsx
@@ -0,0 +1,35 @@
+import React, { useState } from 'react'
+import { Cell, Switch, Toast } from '@nutui/nutui-react'
+
+const Demo8 = () => {
+  const [checkedAsync, setCheckedAsync] = useState(true)
+  const [externalLoading, setExternalLoading] = useState(false)
+
+  const mockRequest = (): Promise<void> => {
+    return new Promise((resolve) => {
+      setTimeout(() => {
+        resolve()
+      }, 2000)
+    })
+  }
+
+  const onChangeAsync = async (value: boolean) => {
+    Toast.show(`2秒后异步触发 ${value}`)
+    await mockRequest()
+    setCheckedAsync(value)
+    // setExternalLoading(false)
+  }
+  return (
+    <Cell>
+      <Switch
+        loading={externalLoading}
+        onLoadingChange={async (loading: boolean) => {
+          setExternalLoading(loading)
+        }}
+        checked={checkedAsync}
+        onChange={(value) => onChangeAsync(value)}
+      />
+    </Cell>
+  )
+}
+export default Demo8
diff --git a/src/packages/switch/demos/taro/demo8.tsx b/src/packages/switch/demos/taro/demo8.tsx
new file mode 100644
index 0000000000..13f847b6f9
--- /dev/null
+++ b/src/packages/switch/demos/taro/demo8.tsx
@@ -0,0 +1,46 @@
+import React, { useState } from 'react'
+import { Cell, Switch, Toast } from '@nutui/nutui-react-taro'
+
+const Demo8 = () => {
+  const [checkedAsync, setCheckedAsync] = useState(true)
+  const [value, setValue] = useState(false)
+  const [showToast, setShowToast] = useState(false)
+  const [externalLoading, setExternalLoading] = useState(false)
+  const mockRequest = (): Promise<void> => {
+    return new Promise((resolve) => {
+      setTimeout(() => {
+        resolve()
+      }, 2000)
+    })
+  }
+
+  const onChangeAsync = async (value: boolean) => {
+    setValue(value)
+    setShowToast(true)
+    await mockRequest()
+    setCheckedAsync(value)
+    // setExternalLoading(false)
+  }
+  return (
+    <>
+      <Cell>
+        <Switch
+          loading={externalLoading}
+          onLoadingChange={async (loading: boolean) => {
+            setExternalLoading(loading)
+          }}
+          checked={checkedAsync}
+          onChange={(value) => onChangeAsync(value)}
+        />
+      </Cell>
+      <Toast
+        content={`2秒后异步触发 ${value}`}
+        visible={showToast}
+        onClose={() => {
+          setShowToast(false)
+        }}
+      />
+    </>
+  )
+}
+export default Demo8
diff --git a/src/packages/switch/doc.en-US.md b/src/packages/switch/doc.en-US.md
index b4090e42d0..3495efdb10 100644
--- a/src/packages/switch/doc.en-US.md
+++ b/src/packages/switch/doc.en-US.md
@@ -26,6 +26,14 @@ import { Switch } from '@nutui/nutui-react'
 
 :::
 
+### loading 态受控
+
+:::demo
+
+<CodeBlock src='h5/demo8.tsx'></CodeBlock>
+
+:::
+
 ### disabled status
 
 :::demo
@@ -77,8 +85,10 @@ import { Switch } from '@nutui/nutui-react'
 | disabled | Disabled | `boolean` | `false` |
 | activeText | Text description when opening | `ReactNode` | `-` |
 | inactiveText | Text description when closed | `ReactNode` | `-` |
-| loadingIcon | Controlled loading state icon | `ReactNode` | `<Loading1 />` |
-| onChange | Trigger when switching switches | `onChange:(value: boolean, event: Event)` | `-` |
+| loadingIcon | Control the loading state icon, disable the loading state when a null | `ReactNode` | `<Loading1 />` |
+| loading | loading state, controlled | `boolean` | `-` |
+| onLoadingChange | Triggered when switching loading | `onLoadingChange:(value: boolean)` | `-` |
+| onChange | Trigger when switching switches | `onChange:(value: boolean)` | `-` |
 
 ## Theming
 
diff --git a/src/packages/switch/doc.md b/src/packages/switch/doc.md
index d699265bd2..3a9e7b2afc 100644
--- a/src/packages/switch/doc.md
+++ b/src/packages/switch/doc.md
@@ -26,6 +26,14 @@ import { Switch } from '@nutui/nutui-react'
 
 :::
 
+### loading 态受控
+
+:::demo
+
+<CodeBlock src='h5/demo8.tsx'></CodeBlock>
+
+:::
+
 ### 禁用状态
 
 :::demo
@@ -77,8 +85,10 @@ import { Switch } from '@nutui/nutui-react'
 | disabled | 禁用状态 | `boolean` | `false` |
 | activeText | 打开时文字描述 | `ReactNode` | `-` |
 | inactiveText | 关闭时文字描述 | `ReactNode` | `-` |
-| loadingIcon | 受控 loading 态图标 | `ReactNode` | `<Loading1 />` |
-| onChange | 切换开关时触发 | `onChange:(value: boolean, event: Event)` | `-` |
+| loadingIcon | 控制加载状态的图标，传入空值时禁用 loading 态 | `ReactNode` | `<Loading1 />` |
+| loading | loading 态，受控 | `boolean` | `-` |
+| onLoadingChange | 切换 loading 态时触发 | `onLoadingChange:(value: boolean)` | `-` |
+| onChange | 切换开关时触发 | `onChange:(value: boolean)` | `-` |
 
 ## 主题定制
 
diff --git a/src/packages/switch/doc.taro.md b/src/packages/switch/doc.taro.md
index 77a4646b2f..c11f5a5581 100644
--- a/src/packages/switch/doc.taro.md
+++ b/src/packages/switch/doc.taro.md
@@ -26,6 +26,14 @@ import { Switch } from '@nutui/nutui-react-taro'
 
 :::
 
+### loading 态受控
+
+:::demo
+
+<CodeBlock src='taro/demo8.tsx'></CodeBlock>
+
+:::
+
 ### 禁用状态
 
 :::demo
@@ -77,8 +85,10 @@ import { Switch } from '@nutui/nutui-react-taro'
 | disabled | 禁用状态 | `boolean` | `false` |
 | activeText | 打开时文字描述 | `ReactNode` | `-` |
 | inactiveText | 关闭时文字描述 | `ReactNode` | `-` |
-| loadingIcon | 受控 loading 态图标 | `ReactNode` | `<Loading1 />` |
-| onChange | 切换开关时触发 | `onChange:(value: boolean, event: Event)` | `-` |
+| loadingIcon | 控制加载状态的图标，传入空值时禁用 loading 态 | `ReactNode` | `<Loading1 />` |
+| loading | loading 态，受控 | `boolean` | `-` |
+| onLoadingChange | 切换 loading 态时触发 | `onLoadingChange:(value: boolean)` | `-` |
+| onChange | 切换开关时触发 | `onChange:(value: boolean)` | `-` |
 
 ## 主题定制
 
diff --git a/src/packages/switch/doc.zh-TW.md b/src/packages/switch/doc.zh-TW.md
index 0f12e4205a..e392e58924 100644
--- a/src/packages/switch/doc.zh-TW.md
+++ b/src/packages/switch/doc.zh-TW.md
@@ -26,6 +26,14 @@ import { Switch } from '@nutui/nutui-react'
 
 :::
 
+### loading 態受控
+
+:::demo
+
+<CodeBlock src='h5/demo8.tsx'></CodeBlock>
+
+:::
+
 ### 禁用狀態
 
 :::demo
@@ -77,8 +85,10 @@ import { Switch } from '@nutui/nutui-react'
 | disabled | 禁用狀態 | `boolean` | `false` |
 | activeText | 打開時文字描述 | `ReactNode` | `-` |
 | inactiveText | 關閉時文字描述 | `ReactNode` | `-` |
-| loadingIcon | 受控 loading 態圖標 | `ReactNode` | `<Loading1 />` |
-| onChange | 切換開關時觸發 | `onChange:(value: boolean, event: Event)` | `-` |
+| loadingIcon | 控制加載狀態的圖標，當傳入空值時禁用 loading 狀態 | `ReactNode` | `<Loading1 />` |
+| loading | loading 態，受控 | `boolean` | `-` |
+| onLoadingChange | 切換 loading 態時觸發 | `onLoadingChange:(value: boolean)` | `-` |
+| onChange | 切換開關時觸發 | `onChange:(value: boolean)` | `-` |
 
 ## 主題定製
 
diff --git a/src/packages/switch/switch.taro.tsx b/src/packages/switch/switch.taro.tsx
index 1d71905a6d..5d72b21c7f 100644
--- a/src/packages/switch/switch.taro.tsx
+++ b/src/packages/switch/switch.taro.tsx
@@ -1,5 +1,4 @@
 import React, { FunctionComponent, useEffect, useState } from 'react'
-
 import { View } from '@tarojs/components'
 import classNames from 'classnames'
 import { Loading1 } from '@nutui/icons-react-taro'
@@ -14,6 +13,8 @@ const defaultProps = {
   activeText: '',
   inactiveText: '',
   loadingIcon: <Loading1 />,
+  loading: undefined,
+  onLoadingChange: (loading: boolean) => {},
 } as TaroSwitchProps
 export const Switch: FunctionComponent<Partial<TaroSwitchProps>> = (props) => {
   const {
@@ -26,6 +27,8 @@ export const Switch: FunctionComponent<Partial<TaroSwitchProps>> = (props) => {
     className,
     style,
     onChange,
+    loading: propLoading,
+    onLoadingChange,
     ...rest
   } = {
     ...defaultProps,
@@ -38,12 +41,21 @@ export const Switch: FunctionComponent<Partial<TaroSwitchProps>> = (props) => {
     defaultValue: defaultChecked,
   })
 
+  const [internalLoading, setInternalLoading] = useState(false)
+  const loading = propLoading !== undefined ? propLoading : internalLoading
+
+  const setLoading = (val: boolean) => {
+    if (propLoading !== undefined) {
+      onLoadingChange(val)
+    } else {
+      setInternalLoading(val)
+    }
+  }
+
   useEffect(() => {
-    changing && setChanging(false)
+    loading && setLoading(false)
   }, [value])
 
-  const [changing, setChanging] = useState(false)
-
   const classes = () => {
     return classNames([
       classPrefix,
@@ -57,13 +69,14 @@ export const Switch: FunctionComponent<Partial<TaroSwitchProps>> = (props) => {
   }
 
   const onClick = async () => {
-    if (disabled || changing) return
+    if (disabled || loading) return
     if (onChange) {
-      setChanging(true)
+      loadingIcon && setLoading(true)
+      onChange(!value)
       try {
         await onChange(!value)
       } catch (e) {
-        setChanging(false)
+        setLoading(false)
       }
     }
     setValue(!value)
@@ -84,7 +97,7 @@ export const Switch: FunctionComponent<Partial<TaroSwitchProps>> = (props) => {
           },
         ])}
       >
-        {changing && loadingIcon ? (
+        {loading && loadingIcon ? (
           <>{loadingIcon}</>
         ) : (
           <>
diff --git a/src/packages/switch/switch.tsx b/src/packages/switch/switch.tsx
index b8a9d487fe..7622a9d50d 100644
--- a/src/packages/switch/switch.tsx
+++ b/src/packages/switch/switch.tsx
@@ -12,6 +12,8 @@ const defaultProps = {
   activeText: '',
   inactiveText: '',
   loadingIcon: <Loading1 />,
+  loading: undefined,
+  onLoadingChange: (loading: boolean) => {},
 } as WebSwitchProps
 export const Switch: FunctionComponent<Partial<WebSwitchProps>> = (props) => {
   const {
@@ -24,6 +26,8 @@ export const Switch: FunctionComponent<Partial<WebSwitchProps>> = (props) => {
     className,
     style,
     onChange,
+    loading: propLoading,
+    onLoadingChange,
     ...rest
   } = {
     ...defaultProps,
@@ -38,12 +42,21 @@ export const Switch: FunctionComponent<Partial<WebSwitchProps>> = (props) => {
     defaultValue: defaultChecked,
   })
 
+  const [internalLoading, setInternalLoading] = useState(false)
+  const loading = propLoading !== undefined ? propLoading : internalLoading
+
+  const setLoading = (val: boolean) => {
+    if (propLoading !== undefined) {
+      onLoadingChange(val)
+    } else {
+      setInternalLoading(val)
+    }
+  }
+
   useEffect(() => {
-    changing && setChanging(false)
+    loading && setLoading(false)
   }, [value])
 
-  const [changing, setChanging] = useState(false)
-
   const classes = () => {
     return classNames([
       classPrefix,
@@ -57,13 +70,13 @@ export const Switch: FunctionComponent<Partial<WebSwitchProps>> = (props) => {
   }
 
   const onClick = async () => {
-    if (disabled || changing) return
+    if (disabled || loading) return
     if (onChange) {
-      setChanging(true)
+      loadingIcon && setLoading(true)
       try {
         await onChange(!value)
       } catch (e) {
-        setChanging(false)
+        setLoading(false)
       }
     }
     setValue(!value)
@@ -84,7 +97,7 @@ export const Switch: FunctionComponent<Partial<WebSwitchProps>> = (props) => {
           },
         ])}
       >
-        {changing && loadingIcon ? (
+        {loading && loadingIcon ? (
           <>{loadingIcon}</>
         ) : (
           <>
diff --git a/src/sites/sites-react/doc/docs/react/migrate-from-v2.md b/src/sites/sites-react/doc/docs/react/migrate-from-v2.md
index f986185539..aa80122103 100644
--- a/src/sites/sites-react/doc/docs/react/migrate-from-v2.md
+++ b/src/sites/sites-react/doc/docs/react/migrate-from-v2.md
@@ -239,6 +239,8 @@ plugins: [
 - `activeText` 属性类型更改为`ReactNode`
 - `inactiveText` 属性类型更改为 `ReactNode`
 - 新增 `loadingIcon` 属性，受控 loading 态图标
+- 新增 `loading` 属性，loading 态受控
+- 新增 `onLodingingChange` ，切换 loading 态时触发
 
 [//]: # '#### Toast'
 
diff --git a/src/sites/sites-react/doc/docs/react/official-theme-react.md b/src/sites/sites-react/doc/docs/react/official-theme-react.md
index e851e1c5b8..3d641eb27c 100644
--- a/src/sites/sites-react/doc/docs/react/official-theme-react.md
+++ b/src/sites/sites-react/doc/docs/react/official-theme-react.md
@@ -12,7 +12,6 @@ NutUI 默认提供多套官方`UI`主题，同时允许在一定程度上定制
 | 京东 JDesign 主题 ([预览](https://nutui.jd.com/h5/react/jdesign-3x/#/zh-CN/component/button)) | `variables-jmapp.scss` |
 | 京东 JRKF 主题 | `variables-jrkf.scss` |
 
-
 ## 使用方式
 
 需要注意的是，配置主题时，你还需要在入口文件中引入 global 类的文件来加载一些 NutUI React 的全局性逻辑和样式：
diff --git a/src/sites/sites-react/doc/docs/taro/migrate-from-v2.md b/src/sites/sites-react/doc/docs/taro/migrate-from-v2.md
index e7f888b66d..5391e76faa 100644
--- a/src/sites/sites-react/doc/docs/taro/migrate-from-v2.md
+++ b/src/sites/sites-react/doc/docs/taro/migrate-from-v2.md
@@ -238,6 +238,8 @@ plugins: [
 - `activeText` 属性类型更改为`ReactNode`
 - `inactiveText` 属性类型更改为 `ReactNode`
 - 新增 `loadingIcon` 属性，受控 loading 态图标
+- 新增 `loading` 属性，loading 态受控
+- 新增 `onLodingingChange` ，切换 loading 态时触发
 
 [//]: # '#### Toast'
 
diff --git a/src/types/spec/switch/base.ts b/src/types/spec/switch/base.ts
index 156821089c..e0b81fd5d0 100644
--- a/src/types/spec/switch/base.ts
+++ b/src/types/spec/switch/base.ts
@@ -8,5 +8,7 @@ export interface BaseSwitch extends BaseProps {
   activeText: ReactNode
   inactiveText: ReactNode
   loadingIcon: ReactNode
+  loading: boolean | undefined
+  onLoadingChange: (loading: boolean) => void
   onChange: (val: boolean) => void
 }


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>src/types/spec/switch/base.ts (1)</summary><blockquote>

`11-12`: **类型定义清晰，建议考虑添加注释**

类型定义明确，`changing`属性使用`boolean | undefined`类型适当地表示了可控状态的三种可能性（true/false/undefined）。`onChangingChange`回调函数定义也符合预期。

建议考虑为这两个新属性添加JSDoc注释，说明它们的具体用途和行为，特别是`undefined`状态的处理方式，以提高代码的可读性和可维护性。

```diff
 export interface BaseSwitch extends BaseProps {
   checked: boolean
   defaultChecked: boolean
   disabled: boolean
   activeText: ReactNode
   inactiveText: ReactNode
   loadingIcon: ReactNode
+  /**
+   * 控制组件loading状态
+   * @description 为undefined时由组件内部控制，为boolean时受外部控制
+   */
   changing: boolean | undefined
+  /**
+   * loading状态变化时触发
+   * @param changing 当前loading状态
+   */
   onChangingChange: (changing: boolean) => void
   onChange: (val: boolean) => void
 }
```

</blockquote></details>
<details>
<summary>src/packages/switch/demos/h5/demo8.tsx (1)</summary><blockquote>

`1-34`: **示例代码结构清晰，展示了 loading 态受控的实现方式**

示例代码通过 `externalChanging` 状态和 `onChangingChange` 回调很好地展示了如何控制 Switch 组件的 loading 态。异步操作的模拟也很恰当，为用户提供了一个实际应用场景。

建议在 Toast 提示中添加更多上下文信息，比如说明这是切换开关导致的异步操作，这样对用户更直观。


```diff
-    Toast.show(`2秒后异步触发 ${value}`)
+    Toast.show(`2秒后切换开关状态为 ${value ? '开启' : '关闭'}`)
```

</blockquote></details>
<details>
<summary>src/packages/switch/switch.taro.tsx (1)</summary><blockquote>

`57-59`: **状态切换处理的时机恰当**

在 `value` 变化时重置 loading 状态是合理的，因为这表示切换已经完成。不过可以考虑添加对 `propChanging` 和 `value` 的依赖检查，避免不必要的更新。

```diff
  useEffect(() => {
    changing && setChanging(false)
-  }, [value])
+  }, [value, propChanging])
```

</blockquote></details>
<details>
<summary>src/packages/switch/switch.tsx (1)</summary><blockquote>

`45-54`: **建议考虑使用 usePropsValue 钩子统一状态管理方式。**

当前组件中 `checked` 状态使用了 `usePropsValue` 钩子，而 `changing` 状态使用了自定义的实现。为了保持一致性，可以考虑也对 `changing` 状态使用 `usePropsValue` 钩子。


```diff
- const [internalChanging, setInternalChanging] = useState(false)
- const changing = propChanging !== undefined ? propChanging : internalChanging
- 
- const setChanging = (val: boolean) => {
-   if (propChanging !== undefined) {
-     onChangingChange(val)
-   } else {
-     setInternalChanging(val)
-   }
- }

+ const [changing, setChanging] = usePropsValue<boolean>({
+   value: propChanging,
+   defaultValue: false,
+   onChange: onChangingChange,
+ })
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 50-50: src/packages/switch/switch.tsx#L50
Added line #L50 was not covered by tests

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 5545133b8e7e960a51f9c91ad1ff2685e930e039 and 9150905be5f0f72a024c92250fe9977c507277a1.

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `src/packages/switch/demo.taro.tsx` (4 hunks)
* `src/packages/switch/demo.tsx` (4 hunks)
* `src/packages/switch/demos/h5/demo8.tsx` (1 hunks)
* `src/packages/switch/demos/taro/demo8.tsx` (1 hunks)
* `src/packages/switch/doc.en-US.md` (2 hunks)
* `src/packages/switch/doc.md` (2 hunks)
* `src/packages/switch/doc.taro.md` (2 hunks)
* `src/packages/switch/doc.zh-TW.md` (2 hunks)
* `src/packages/switch/switch.taro.tsx` (4 hunks)
* `src/packages/switch/switch.tsx` (4 hunks)
* `src/sites/sites-react/doc/docs/react/migrate-from-v2.md` (1 hunks)
* `src/sites/sites-react/doc/docs/taro/migrate-from-v2.md` (1 hunks)
* `src/types/spec/switch/base.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (2)</summary>

<details>
<summary>src/packages/switch/demos/h5/demo8.tsx (2)</summary><blockquote>

<details>
<summary>src/packages/switch/switch.tsx (1)</summary>

* `Switch` (18-121)

</details>
<details>
<summary>src/packages/switch/switch.taro.tsx (1)</summary>

* `Switch` (19-122)

</details>

</blockquote></details>
<details>
<summary>src/packages/switch/demos/taro/demo8.tsx (2)</summary><blockquote>

<details>
<summary>src/packages/switch/switch.tsx (1)</summary>

* `Switch` (18-121)

</details>
<details>
<summary>src/packages/switch/switch.taro.tsx (1)</summary>

* `Switch` (19-122)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Check: codecov/patch</summary>

<details>
<summary>src/packages/switch/switch.tsx</summary>

[warning] 50-50: src/packages/switch/switch.tsx#L50
Added line #L50 was not covered by tests

</details>

</details>
<details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>src/packages/switch/doc.en-US.md</summary>

[grammar] ~91-~91: You’ve repeated a verb. Did you mean to only write one of them?
Context: ...ean)` | `-` | | onChange | Trigger when switching switches | `onChange:(value: boolean)` | `-` |  ...

(REPEATED_VERBS)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (24)</summary><blockquote>

<details>
<summary>src/sites/sites-react/doc/docs/react/migrate-from-v2.md (1)</summary>

`242-243`: **属性添加的文档说明清晰明了**

文档中对Switch组件新增的`changing`和`onChangingChange`属性描述简洁清晰，很好地说明了这两个属性的作用。这些新增属性增强了Switch组件的可控性，允许开发者更灵活地控制加载状态。

</details>
<details>
<summary>src/sites/sites-react/doc/docs/taro/migrate-from-v2.md (1)</summary>

`241-242`: **Taro文档与React文档保持一致**

文档中对Switch组件新增的`changing`和`onChangingChange`属性描述与React版本保持一致，确保了跨平台开发体验的一致性。

</details>
<details>
<summary>src/packages/switch/demo.taro.tsx (1)</summary>

`13-13`: **Demo结构清晰，国际化处理完善**

新增Demo8示例及其相关翻译条目结构清晰，国际化处理完善。三种语言的翻译表述一致且符合各语言习惯，确保了良好的用户体验。

Demo的顺序安排也很合理，将新增的loading态受控示例放在"受控"示例之后，符合逻辑顺序，有助于用户理解组件的使用方式。



Also applies to: 21-21, 31-31, 41-41, 56-57

</details>
<details>
<summary>src/packages/switch/demo.tsx (5)</summary>

`10-10`: **导入新增的 Demo8 组件正确**

成功导入了新的 Demo8 组件，用于展示 loading 态受控的功能。

---

`18-18`: **中文翻译添加正确**

为简体中文环境添加了 "loading 态受控" 的翻译文本。

---

`28-28`: **繁体中文翻译添加正确**

为繁体中文环境添加了 "loading 態受控" 的翻译文本。

---

`38-38`: **英文翻译添加正确**

为英文环境添加了 "Controlled with loading" 的翻译文本。

---

`52-53`: **组件渲染添加正确**

成功添加了新的标题和 Demo8 组件渲染，使文档结构清晰完整。

</details>
<details>
<summary>src/packages/switch/doc.en-US.md (1)</summary>

`88-90`: **属性描述清晰并保持一致性**

`loadingIcon` 和 `changing` 属性的描述清晰明了，与组件的实际行为一致。

</details>
<details>
<summary>src/packages/switch/doc.md (3)</summary>

`29-35`: **新增功能演示说明文档完成**

新增了 "loading 态受控" 的演示部分，并正确引用了对应的代码示例。

---

`88-90`: **属性说明清晰并符合功能实现**

`loadingIcon` 属性描述更新为更精确的说明，并添加了新增的 `changing` 属性和说明，文档描述与代码实现一致。

---

`91-91`: **事件处理函数说明简洁明了**

`onChange` 的触发条件描述简洁明了，符合实际使用场景。

</details>
<details>
<summary>src/packages/switch/doc.zh-TW.md (2)</summary>

`29-35`: **文档新增了 loading 态受控部分，清晰展示了新功能**

文档中增加了关于受控 loading 状态的新章节，并通过代码示例很好地展示了这一新功能，这对用户理解如何使用该特性非常有帮助。

---

`88-91`: **属性表更新及时，明确了新增特性的使用方法**

1. 更新了 `loadingIcon` 属性的描述，明确了传入空值时会禁用 loading 状态
2. 新增了 `changing` 属性用于控制 loading 态
3. 新增了 `onChangingChange` 事件
4. 优化了 `onChange` 的描述，移除了不必要的 event 参数

这些更新使文档与实现保持一致，有助于开发者正确使用这些新特性。

</details>
<details>
<summary>src/packages/switch/doc.taro.md (2)</summary>

`29-35`: **新增的受控 loading 态文档与 zh-TW 版本保持一致**

文档中新增了 loading 态受控的示例部分，与繁体中文版本保持一致，确保了不同语言版本的用户都能获得相同的信息。

---

`88-91`: **属性表描述准确，与组件实现一致**

属性表中对于 `loadingIcon`、`changing` 和 `onChangingChange` 的描述准确清晰，与组件实际实现一致。特别是指出了 `loadingIcon` 传入空值时会禁用 loading 态，这对开发者理解组件行为很有帮助。

</details>
<details>
<summary>src/packages/switch/switch.taro.tsx (4)</summary>

`16-17`: **新增默认属性定义合理**

为 `changing` 和 `onChangingChange` 设置的默认值是合理的。`changing` 默认为 undefined 可以让组件内部判断是否受控，`onChangingChange` 设置为空函数可以避免调用时的空检查。

---

`30-31`: **属性解构正确处理了新增的 props**

从 props 中正确解构了新增的 `changing` 和 `onChangingChange` 属性，并将 `changing` 重命名为 `propChanging` 以区分外部传入和内部状态。

---

`46-55`: **受控与非受控状态处理合理**

代码使用了 React 推荐的受控/非受控组件模式，通过判断 `propChanging !== undefined` 来确定是否使用外部传入的状态。`setChanging` 函数根据组件是否受控选择更新方式，实现了灵活的状态管理。

---

`76-76`: **条件触发 loading 态的逻辑正确**

只有在提供了 `loadingIcon` 时才启动 loading 态，这与文档中描述的"传入空值时禁用 loading 态"一致。这样的设计使组件更加灵活，用户可以根据需要决定是否使用 loading 状态。

</details>
<details>
<summary>src/packages/switch/switch.tsx (4)</summary>

`15-16`: **添加了受控态属性的默认值定义，实现良好！**

代码采用了恰当的默认值设计：
- `changing` 设置为 `undefined`，便于后续区分受控和非受控模式
- `onChangingChange` 提供了空函数作为默认实现，避免了空引用异常

这样的实现遵循了 React 组件设计的最佳实践。

---

`29-31`: **属性解构方式适当，使用重命名避免命名冲突。**

通过将 `changing` 重命名为 `propChanging`，有效避免了与本地变量的命名冲突，代码结构清晰。

---

`45-47`: **内部状态和计算属性实现受控模式的方式合理。**

通过内部状态 `internalChanging` 和条件判断来确定实际使用的 `changing` 值，成功实现了受控和非受控两种模式的支持。

---

`75-75`: **点击处理逻辑更新合理，增加了对 loadingIcon 的检查。**

只有在提供了 `loadingIcon` 的情况下才激活 changing 状态，这个变更逻辑合理，避免了在没有加载图标时不必要的状态变更。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-03T06:57:57Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>src/packages/switch/doc.en-US.md (1)</summary><blockquote>

`29-35`: _🛠️ Refactor suggestion_

**英文文档中应使用英文标题**

英文文档中的标题 "loading 态受控" 未翻译成英文，应该使用英文标题以保持文档的一致性。

```diff
-### loading 态受控
+### Controlled with loading
```

</blockquote></details>
<details>
<summary>src/packages/switch/switch.tsx (1)</summary><blockquote>

`48-54`: **建议增加单元测试覆盖 onLoadingChange 回调**

代码逻辑设计合理，`setLoading` 函数根据是否处于受控模式选择不同的状态更新方式，其中在受控模式下第 50 行调用 `onLoadingChange(val)`。目前静态搜索未见相关测试覆盖，建议在 `src/packages/switch/__test__/switch.spec.tsx` 中补充测试用例，确保在受控模式下回调函数能够正常触发及工作。

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>src/packages/switch/demos/taro/demo8.tsx (2)</summary><blockquote>

`9-15`: **mock请求函数缺少错误处理**

mock请求函数实现了延迟操作，但没有实现错误处理逻辑，实际应用中建议添加错误处理机制。

```diff
 const mockRequest = (): Promise<void> => {
   return new Promise((resolve) => {
     setTimeout(() => {
       resolve()
     }, 2000)
   })
 }

+// 带错误处理的模拟请求示例
+// const mockRequest = (): Promise<void> => {
+//   return new Promise((resolve, reject) => {
+//     setTimeout(() => {
+//       if (Math.random() > 0.2) { // 模拟80%成功率
+//         resolve()
+//       } else {
+//         reject(new Error('请求失败'))
+//       }
+//     }, 2000)
+//   })
+// }
```

---

`29-31`: **不必要的异步函数声明**

`onLoadingChange`处理函数被声明为异步函数，但内部没有使用`await`，可以简化为普通函数。

```diff
-  onLoadingChange={async (loading: boolean) => {
+  onLoadingChange={(loading: boolean) => {
     setExternalLoading(loading)
   }}
```

</blockquote></details>
<details>
<summary>src/packages/switch/doc.en-US.md (1)</summary><blockquote>

`91-91`: **英文描述中存在语法问题**

英文描述中 "Trigger when switching switches" 存在重复动词问题，导致表达不清晰。

```diff
-| onChange | Trigger when switching switches | `onChange:(value: boolean)` | `-` |
+| onChange | Trigger when toggle state changes | `onChange:(value: boolean)` | `-` |
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[grammar] ~91-~91: You’ve repeated a verb. Did you mean to only write one of them?
Context: ...ean)` | `-` | | onChange | Trigger when switching switches | `onChange:(value: boolean)` | `-` |  ...

(REPEATED_VERBS)

</details>

</details>

</blockquote></details>
<details>
<summary>src/packages/switch/demos/h5/demo8.tsx (3)</summary><blockquote>

`8-14`: **mock请求函数缺少错误处理**

mock请求函数实现了延迟操作，但没有实现错误处理逻辑，实际应用中建议添加错误处理机制。

```diff
 const mockRequest = (): Promise<void> => {
   return new Promise((resolve) => {
     setTimeout(() => {
       resolve()
     }, 2000)
   })
 }

+// 带错误处理的模拟请求示例
+// const mockRequest = (): Promise<void> => {
+//   return new Promise((resolve, reject) => {
+//     setTimeout(() => {
+//       if (Math.random() > 0.2) { // 模拟80%成功率
+//         resolve()
+//       } else {
+//         reject(new Error('请求失败'))
+//       }
+//     }, 2000)
+//   })
+// }
```

---

`16-20`: **Toast提示信息需考虑国际化**

在 `onChangeAsync` 函数中直接使用了中文提示信息 `2秒后异步触发 ${value}`，对于多语言支持不够友好。

```diff
 const onChangeAsync = async (value: boolean) => {
-  Toast.show(`2秒后异步触发 ${value}`)
+  Toast.show(`Async trigger after 2s: ${value}`)
   await mockRequest()
   setCheckedAsync(value)
 }
```

---

`25-27`: **不必要的异步函数声明**

`onLoadingChange`处理函数被声明为异步函数，但内部没有使用`await`，可以简化为普通函数。

```diff
-    onLoadingChange={async (loading: boolean) => {
+    onLoadingChange={(loading: boolean) => {
       setExternalLoading(loading)
     }}
```

</blockquote></details>
<details>
<summary>src/packages/switch/switch.tsx (1)</summary><blockquote>

`15-54`: **建议考虑 onLoadingChange 可能导致的无限循环问题**

在受控模式下，如果父组件在 onLoadingChange 回调中立即更新 loading prop，可能会导致组件不断重新渲染。建议考虑添加防抖或节流机制，或在组件文档中提醒用户避免在回调中立即更新 loading 状态。

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 90d7460af9c65aaf7e8cb59efc9aeac0a6ee138f and 04aee9a38a085352af883f8320e1a2e17bfc30b2.

</details>

<details>
<summary>📒 Files selected for processing (11)</summary>

* `src/packages/switch/demo.taro.tsx` (4 hunks)
* `src/packages/switch/demo.tsx` (4 hunks)
* `src/packages/switch/demos/h5/demo8.tsx` (1 hunks)
* `src/packages/switch/demos/taro/demo8.tsx` (1 hunks)
* `src/packages/switch/doc.en-US.md` (2 hunks)
* `src/packages/switch/doc.md` (2 hunks)
* `src/packages/switch/doc.taro.md` (2 hunks)
* `src/packages/switch/doc.zh-TW.md` (2 hunks)
* `src/packages/switch/switch.taro.tsx` (5 hunks)
* `src/packages/switch/switch.tsx` (5 hunks)
* `src/types/spec/switch/base.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (6)</summary>

* src/packages/switch/doc.taro.md
* src/types/spec/switch/base.ts
* src/packages/switch/demo.tsx
* src/packages/switch/demo.taro.tsx
* src/packages/switch/switch.taro.tsx
* src/packages/switch/doc.md

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>src/packages/switch/demos/taro/demo8.tsx (3)</summary><blockquote>

<details>
<summary>src/packages/switch/switch.taro.tsx (1)</summary>

* `Switch` (19-122)

</details>
<details>
<summary>src/packages/switch/switch.tsx (1)</summary>

* `Switch` (18-121)

</details>
<details>
<summary>src/packages/toast/toast.taro.tsx (1)</summary>

* `Toast` (39-193)

</details>

</blockquote></details>
<details>
<summary>src/packages/switch/demos/h5/demo8.tsx (2)</summary><blockquote>

<details>
<summary>src/packages/toast/toast.taro.tsx (1)</summary>

* `Toast` (39-193)

</details>
<details>
<summary>src/packages/switch/switch.tsx (1)</summary>

* `Switch` (18-121)

</details>

</blockquote></details>

</details><details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>src/packages/switch/doc.en-US.md</summary>

[grammar] ~91-~91: You’ve repeated a verb. Did you mean to only write one of them?
Context: ...ean)` | `-` | | onChange | Trigger when switching switches | `onChange:(value: boolean)` | `-` |  ...

(REPEATED_VERBS)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (13)</summary><blockquote>

<details>
<summary>src/packages/switch/demos/taro/demo8.tsx (3)</summary>

`1-7`: **开关组件受控状态基本实现**

组件实现了多个状态变量来管理受控状态：checkedAsync（开关状态）、value（当前值）、showToast（提示显示）和externalLoading（外部loading状态）。状态管理方式合理。

---

`17-23`: **异步处理函数逻辑合理**

`onChangeAsync`函数实现了切换开关状态、显示Toast提示、等待异步请求完成，并最终更新状态的完整流程。第22行有一个被注释掉的代码`// setExternalLoading(false)`，应考虑是否需要保留。

请确认第22行注释掉的代码是否需要保留？如果这是遗留的调试代码，建议删除；如果是可选功能，建议添加注释说明。

---

`24-43`: **组件渲染结构合理**

组件渲染了一个包含Switch的Cell，以及一个条件显示的Toast。Switch组件正确绑定了loading、onLoadingChange、checked和onChange属性，实现了受控状态的完整流程。

</details>
<details>
<summary>src/packages/switch/doc.en-US.md (1)</summary>

`88-90`: **属性文档更新合理**

更新了 `loadingIcon` 属性的描述，并且添加了新的 `loading` 和 `onLoadingChange` 属性的说明，文档修改合理清晰。

</details>
<details>
<summary>src/packages/switch/doc.zh-TW.md (2)</summary>

`29-35`: **文档新增演示部分结构合理**

新增了"loading 態受控"部分，并引用了相应的 demo 示例，文档结构清晰。

---

`88-91`: **组件属性文档更新全面**

更新了 `loadingIcon` 属性的描述，新增了 `loading` 和 `onLoadingChange` 属性的说明，并修改了 `onChange` 的类型签名。文档修改详细且符合繁体中文语言规范。

</details>
<details>
<summary>src/packages/switch/demos/h5/demo8.tsx (2)</summary>

`1-7`: **状态管理简洁有效**

组件使用 useState 钩子管理 `checkedAsync` 和 `externalLoading` 状态，实现了对 Switch 组件loading状态的控制。

---

`21-33`: **组件结构清晰**

组件使用Cell包裹Switch，并正确设置了loading、onLoadingChange、checked和onChange属性，实现了受控loading状态的完整功能。

</details>
<details>
<summary>src/packages/switch/switch.tsx (5)</summary>

`15-16`: **组件新增 loading 和 onLoadingChange 属性，引入了受控状态模式**

在 defaultProps 中添加了 loading 和 onLoadingChange 属性，这符合 React 组件的受控模式设计模式。loading 设置为 undefined 允许组件根据 props 判断是否处于受控模式，而 onLoadingChange 回调则使父组件能够响应状态变化。

---

`29-31`: **属性解构命名清晰，有助于区分内部状态和外部属性**

将外部传入的 loading 属性重命名为 propLoading 是一个很好的做法，能够清晰区分内部状态和外部传入的属性值，提高代码可读性。

---

`45-47`: **内部状态与受控状态实现合理**

使用 internalLoading 维护内部状态，同时通过条件判断 propLoading 是否存在来确定使用哪个状态值，实现了标准的受控组件模式。

---

`73-76`: **点击事件处理逻辑更新正确**

将 loading 状态的检查集成到点击事件处理函数中，并在加载图标存在时设置加载状态为 true，这种逻辑更新正确地实现了预期功能。

---

`96-98`: **加载图标条件渲染逻辑完善**

条件渲染逻辑依赖 loading 状态和 loadingIcon 两个条件，确保只有在两者都满足时才显示加载图标，这是一个合理的实现。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-11T08:04:47Z
user: login: oasis-cloud
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-16T10:40:14Z

Review comments:
diff_hunk: @@ -0,0 +1,46 @@
+import React, { useState } from 'react'
+import { Cell, Switch, Toast } from '@nutui/nutui-react-taro'
+
+const Demo8 = () => {
+  const [checkedAsync, setCheckedAsync] = useState(true)
+  const [value, setValue] = useState(false)
+  const [showToast, setShowToast] = useState(false)
+  const [externalChanging, setExternalChanging] = useState(false)
+  const mockRequest = (): Promise<void> => {
+    return new Promise((resolve) => {
+      setTimeout(() => {
+        resolve()
+      }, 2000)
+    })
+  }
+
+  const onChangeAsync = async (value: boolean) => {
+    setValue(value)
+    setShowToast(true)
+    await mockRequest()
+    setCheckedAsync(value)
+    // setExternalChanging(false)
+  }
+  return (
+    <>
+      <Cell>
+        <Switch
+          changing={externalChanging}
+          onChangingChange={async (changing: boolean) => {
+            setExternalChanging(changing)
+          }}
+          checked={checkedAsync}
+          onChange={(value) => onChangeAsync(value)}
+        />
+      </Cell>
+      <Toast
+        content={`2秒后异步触发 ${value}`}
+        visible={showToast}
+        onClose={() => {
+          setShowToast(false)
+        }}
+      />
+    </>
+  )
+}
+export default Demo8
path: src/packages/switch/demos/taro/demo8.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**代码实现了受控 loading 状态的 Switch 组件，但有几处可以优化**

代码实现了一个展示 Switch 组件受控 loading 状态的示例。整体逻辑是正确的，但有几点建议：

1. 第22行有一个被注释掉的代码 `// setExternalChanging(false)`，不清楚是否需要保留。
2. `onChangingChange` 处理函数被声明为异步函数，但内部没有使用 `await`，可以简化为普通函数。
3. 考虑在 `mockRequest` 添加错误处理机制，以防实际应用中出现异常情况。


建议修改如下：

```diff
-  // setExternalChanging(false)
```

```diff
-  onChangingChange={async (changing: boolean) => {
+  onChangingChange={(changing: boolean) => {
     setExternalChanging(changing)
   }}
```

```diff
 const mockRequest = (): Promise<void> => {
   return new Promise((resolve) => {
     setTimeout(() => {
       resolve()
     }, 2000)
   })
 }
+
+// 可以考虑添加错误处理版本
+// const mockRequest = (): Promise<void> => {
+//   return new Promise((resolve, reject) => {
+//     setTimeout(() => {
+//       if (Math.random() > 0.2) { // 模拟80%成功率
+//         resolve()
+//       } else {
+//         reject(new Error('模拟请求失败'))
+//       }
+//     }, 2000)
+//   })
+// }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import React, { useState } from 'react'
import { Cell, Switch, Toast } from '@nutui/nutui-react-taro'

const Demo8 = () => {
  const [checkedAsync, setCheckedAsync] = useState(true)
  const [value, setValue] = useState(false)
  const [showToast, setShowToast] = useState(false)
  const [externalChanging, setExternalChanging] = useState(false)
  const mockRequest = (): Promise<void> => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve()
      }, 2000)
    })
  }

  const onChangeAsync = async (value: boolean) => {
    setValue(value)
    setShowToast(true)
    await mockRequest()
    setCheckedAsync(value)
  }
  return (
    <>
      <Cell>
        <Switch
          changing={externalChanging}
          onChangingChange={(changing: boolean) => {
            setExternalChanging(changing)
          }}
          checked={checkedAsync}
          onChange={(value) => onChangeAsync(value)}
        />
      </Cell>
      <Toast
        content={`2秒后异步触发 ${value}`}
        visible={showToast}
        onClose={() => {
          setShowToast(false)
        }}
      />
    </>
  )
}
export default Demo8

// 可以考虑添加错误处理版本
// const mockRequest = (): Promise<void> => {
//   return new Promise((resolve, reject) => {
//     setTimeout(() => {
//       if (Math.random() > 0.2) { // 模拟80%成功率
//         resolve()
//       } else {
//         reject(new Error('模拟请求失败'))
//       }
//     }, 2000)
//   })
// }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-03T06:57:56Z
author_association: NONE
diff_hunk: @@ -77,8 +85,10 @@ import { Switch } from '@nutui/nutui-react'
 | disabled | Disabled | `boolean` | `false` |
 | activeText | Text description when opening | `ReactNode` | `-` |
 | inactiveText | Text description when closed | `ReactNode` | `-` |
-| loadingIcon | Controlled loading state icon | `ReactNode` | `<Loading1 />` |
-| onChange | Trigger when switching switches | `onChange:(value: boolean, event: Event)` | `-` |
+| loadingIcon | Control the loading state icon, disable the loading state when a null | `ReactNode` | `<Loading1 />` |
+| changing | loading state, controlled | `boolean` | `-` |
+| onChangingChange | Triggered when switching loading | `onChangingChange:(value: boolean)` | `-` |
+| onChange | Trigger when switching switches | `onChange:(value: boolean)` | `-` |
path: src/packages/switch/doc.en-US.md
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**重复动词导致语法问题**

英文描述中 "Trigger when switching switches" 存在重复动词问题，导致表达不清晰。


```diff
-| onChange | Trigger when switching switches | `onChange:(value: boolean)` | `-` |
+| onChange | Trigger when toggle state changes | `onChange:(value: boolean)` | `-` |
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
| onChange | Trigger when toggle state changes | `onChange:(value: boolean)` | `-` |
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[grammar] ~91-~91: You’ve repeated a verb. Did you mean to only write one of them?
Context: ...ean)` | `-` | | onChange | Trigger when switching switches | `onChange:(value: boolean)` | `-` |  ...

(REPEATED_VERBS)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 04aee9a
created_at: 2025-04-03T06:57:57Z
author_association: NONE
diff_hunk: @@ -26,6 +26,14 @@
 
 :::
 
+### loading 态受控
+
+:::demo
+
+<CodeBlock src='h5/demo8.tsx'></CodeBlock>
+
+:::
path: src/packages/switch/doc.en-US.md
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**英文文档中保留了中文标题，需要翻译**

英文文档中的标题 "loading 态受控" 未翻译成英文，应该使用 "Controlled with loading" 以保持一致性。


```diff
-### loading 态受控
+### Controlled with loading
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
### Controlled with loading

:::demo

<CodeBlock src='h5/demo8.tsx'></CodeBlock>

:::
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-03T06:57:57Z
author_association: NONE
diff_hunk: @@ -38,12 +42,21 @@
     defaultValue: defaultChecked,
   })
 
+  const [internalChanging, setInternalChanging] = useState(false)
+  const changing = propChanging !== undefined ? propChanging : internalChanging
+
+  const setChanging = (val: boolean) => {
+    if (propChanging !== undefined) {
+      onChangingChange(val)
+    } else {
+      setInternalChanging(val)
+    }
+  }
path: src/packages/switch/switch.tsx
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**设置状态值的逻辑设计恰当，但缺少测试覆盖。**

`setChanging` 函数根据是否为受控模式选择不同的更新方式，实现了良好的状态管理。然而，静态分析显示第 50 行 `onChangingChange(val)` 缺少测试覆盖。

建议添加相关测试用例，确保受控模式下的回调函数正常工作。


---

🏁 Script executed:

```shell
#!/bin/bash
# 查找是否存在测试文件及其中是否包含对受控模式的测试
fd "switch.spec" --type file
rg "changing.*onChangingChange" --type ts
```

Length of output: 117

---



</details>

**建议增加单元测试覆盖 onChangingChange 回调**

代码逻辑设计合理，`setChanging` 函数根据是否处于受控模式选择不同的状态更新方式，其中在受控模式下第 50 行调用 `onChangingChange(val)`。目前静态搜索未见相关测试覆盖，建议在 `src/packages/switch/__test__/switch.spec.tsx` 中补充测试用例，确保在受控模式下回调函数能够正常触发及工作。

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 50-50: src/packages/switch/switch.tsx#L50
Added line #L50 was not covered by tests

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-03T06:57:57Z
author_association: NONE
diff_hunk: @@ -38,12 +42,21 @@ export const Switch: FunctionComponent<Partial<WebSwitchProps>> = (props) => {
     defaultValue: defaultChecked,
   })
 
+  const [internalLoading, setInternalLoading] = useState(false)
+  const loading = propLoading !== undefined ? propLoading : internalLoading
+
+  const setLoading = (val: boolean) => {
+    if (propLoading !== undefined) {
+      onLoadingChange(val)
+    } else {
+      setInternalLoading(val)
+    }
+  }
+
   useEffect(() => {
-    changing && setChanging(false)
+    loading && setLoading(false)
   }, [value])
path: src/packages/switch/switch.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**useEffect 缺少依赖项**

useEffect 钩子中使用了 loading 变量，但依赖数组为空，这可能导致依赖项变化时 effect 不会重新执行。建议将 loading 添加到依赖数组中。


```diff
  useEffect(() => {
    loading && setLoading(false)
-  }, [value])
+  }, [value, loading])
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  useEffect(() => {
    loading && setLoading(false)
  }, [value, loading])
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-11T08:04:46Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-03T06:50:04Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
本次变更在开关组件中引入了新的 Demo8 演示组件，并增加了新翻译条目 `loadingControl`，用于描述受控 loading 状态。同时，更新了相关文档，对 `loadingIcon`、`loading`、`onLoadingChange` 等属性进行了补充说明和描述调整。开关组件内部也增加了状态变量和默认属性，以支持受控 loading 状态，并对异步交互流程进行了处理。此外，类型定义与迁移文档也相应进行了更新，确保 API 定义的一致性。

## Changes

| 文件/文件组 | 变更摘要 |
| --- | --- |
| `src/packages/switch/demo.taro.tsx`<br>`src/packages/switch/demo.tsx` | 为 `SwitchDemo` 组件增加了对 `Demo8` 的引用，并添加新翻译 `loadingControl`（中英文）以展示 loading 状态。 |
| `src/packages/switch/demos/h5/demo8.tsx`<br>`src/packages/switch/demos/taro/demo8.tsx` | 新增 `Demo8` 组件，采用异步请求模拟延时更新开关状态，并在状态切换时展示 toast 通知。 |
| `src/packages/switch/doc.en-US.md`<br>`src/packages/switch/doc.md`<br>`src/packages/switch/doc.taro.md`<br>`src/packages/switch/doc.zh-TW.md` | 文档中新增 “loading 态受控” 节，引入 demo 代码块，更新了 `loadingIcon` 属性描述，并增加了 `loading` 与 `onLoadingChange` 新属性说明。 |
| `src/packages/switch/switch.taro.tsx`<br>`src/packages/switch/switch.tsx` | 在 Switch 组件中新增 `loading` 与 `onLoadingChange` 默认属性，调整内部状态管理逻辑，更新 `onClick` 及异步状态处理逻辑。 |
| `src/sites/sites-react/doc/docs/react/migrate-from-v2.md`<br>`src/sites/sites-react/doc/docs/taro/migrate-from-v2.md` | 更新迁移文档，说明新增了开关组件的 `loading` 属性和 `onLoadingChange` 回调，明确 API 变化。 |
| `src/types/spec/switch/base.ts` | 为 `BaseSwitch` 接口增加了 `loading` (boolean | undefined) 和 `onLoadingChange` (boolean 回调) 两个新属性。 |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
  participant U as 用户
  participant D as Demo8组件
  participant M as mockRequest
  participant T as Toast

  U->>D: 切换开关状态
  D->>T: 显示加载提示
  D->>M: 发起异步请求（2秒延时）
  M-->>D: 请求完成结果
  D->>T: 更新并显示结果通知
  D->>D: 更新状态（checkedAsync / externalLoading）
```

## Possibly related PRs
- **jdf2e/nutui-react#2779**: 此 PR 与本次变更相关，均引入了新的 Demo8 组件，并为 `SwitchDemo` 组件添加了 `loadingControl` 翻译条目。
- **jdf2e/nutui-react#3122**: 此 PR 与本次变更密切相关，同样引入了新的 Demo8 演示组件和 `loadingControl` 翻译条目，同时调整了开关组件的异步行为处理。
- **jdf2e/nutui-react#3143**: 此 PR 涉及开关组件的异步操作和 loading 状态管理的修改，与本次变更在功能上有相关性。

## Suggested reviewers
- xiaoyatong
- oasis-cloud

## Poem
> 跳跃在代码田野间的我，  
> 小兔子见证新 Demo 的出现在路上，🐰  
> 受控 loading 状态轻轻开启，  
> 文档清晰如晨光照亮每一步。  
> 异步请求悄然传来温柔的回响，  
> 更新后的组件，令代码世界更加生动。

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡💬 Agentic Chat (Pro Plan, General Availability)</summary>
> 
> - We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 04aee9a38a085352af883f8320e1a2e17bfc30b2 and fe2c80f197aa62b98257a4fbe05fae6b341dd387.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `src/packages/switch/demos/h5/demo8.tsx` (1 hunks)
* `src/packages/switch/switch.taro.tsx` (5 hunks)
* `src/packages/switch/switch.tsx` (5 hunks)
* `src/sites/sites-react/doc/docs/react/migrate-from-v2.md` (1 hunks)
* `src/sites/sites-react/doc/docs/react/official-theme-react.md` (0 hunks)
* `src/sites/sites-react/doc/docs/taro/migrate-from-v2.md` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* src/sites/sites-react/doc/docs/react/official-theme-react.md

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary>

* src/sites/sites-react/doc/docs/taro/migrate-from-v2.md
* src/sites/sites-react/doc/docs/react/migrate-from-v2.md
* src/packages/switch/switch.taro.tsx
* src/packages/switch/demos/h5/demo8.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

<details>
<summary>src/packages/switch/switch.tsx</summary>

[warning] 50-50: src/packages/switch/switch.tsx#L50
Added line #L50 was not covered by tests

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: test

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>src/packages/switch/switch.tsx (5)</summary>

`14-16`: **新增的默认属性设计合理**

新增的 `loading` 和 `onLoadingChange` 默认属性遵循了 React 受控组件的最佳实践。将 `loading` 默认值设为 `undefined` 可以区分受控和非受控模式，而 `onLoadingChange` 的空函数实现避免了调用时的空值检查。

---

`29-31`: **属性解构命名规范合理**

将 `loading` 重命名为 `propLoading` 是一个良好的实践，可以清晰地区分外部传入的属性和内部使用的状态变量。

---

`45-47`: **内部状态管理实现完善**

针对 loading 状态的内部管理逻辑实现完善，通过条件判断 `propLoading !== undefined` 来区分受控和非受控模式，符合 React 组件设计模式。

---

`72-76`: **关于 loadingIcon 与 loading 状态的关联设计**

当前实现中，只有在提供了 `loadingIcon` 的情况下才会在 `onChange` 回调前设置 loading 状态（第75行）。如果没有提供图标，即使异步操作正在进行，loading 状态也不会被激活。

请确认这是否是有意的设计决策。如果希望无论是否有图标都能反映异步操作的状态，可以考虑将这两个条件解耦。

---

`100-102`: **组件渲染逻辑更新合理**

更新后的渲染条件 `loading && loadingIcon` 确保只有在组件处于加载状态且提供了加载图标时才会渲染图标，与组件的预期行为一致。

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=jdf2e/nutui-react&utm_content=3149):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-03T06:52:18Z
body: ## [Codecov](https://app.codecov.io/gh/jdf2e/nutui-react/pull/3149?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jdf2e) Report
Attention: Patch coverage is `94.44444%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 87.20%. Comparing base [(`794a53f`)](https://app.codecov.io/gh/jdf2e/nutui-react/commit/794a53ffe26604fb616490c2fd192c145c61c88d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jdf2e) to head [(`fe2c80f`)](https://app.codecov.io/gh/jdf2e/nutui-react/commit/fe2c80f197aa62b98257a4fbe05fae6b341dd387?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jdf2e).
> Report is 25 commits behind head on feat_v3.x.

| [Files with missing lines](https://app.codecov.io/gh/jdf2e/nutui-react/pull/3149?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jdf2e) | Patch % | Lines |
|---|---|---|
| [src/packages/switch/switch.tsx](https://app.codecov.io/gh/jdf2e/nutui-react/pull/3149?src=pr&el=tree&filepath=src%2Fpackages%2Fswitch%2Fswitch.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jdf2e#diff-c3JjL3BhY2thZ2VzL3N3aXRjaC9zd2l0Y2gudHN4) | 94.44% | [1 Missing :warning: ](https://app.codecov.io/gh/jdf2e/nutui-react/pull/3149?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jdf2e) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           feat_v3.x    #3149   +/-   ##
==========================================
  Coverage      87.19%   87.20%           
==========================================
  Files            279      279           
  Lines          18544    18556   +12     
  Branches        2822     2825    +3     
==========================================
+ Hits           16170    16181   +11     
- Misses          2369     2370    +1     
  Partials           5        5           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/jdf2e/nutui-react/pull/3149?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jdf2e).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jdf2e).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: oasis-cloud
created_at: 2025-04-03T07:09:50Z
body: 同 switch 另外一个 pr 中的建议，可以参考，不需要增加额外状态
author_association: COLLABORATOR