Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: cascader #2956

Open
wants to merge 12 commits into
base: next
Choose a base branch
from
Open

Conversation

oasis-cloud
Copy link
Collaborator

@oasis-cloud oasis-cloud commented Jan 23, 2025

🤔 这个变动的性质是?

  • 新特性提交
  • 日常 bug 修复

🔗 相关 Issue

💡 需求背景和解决方案

  • 重新整理了实现逻辑,各个功能之间耦合比之前松散了一些
  • lazy 用于全部 options 的懒加载
  • onLoad 用于部分数据和 lazy 模式的数据加载
  • 修复 loading 状态不展示的问题

Summary by CodeRabbit

发布说明

  • 新功能

    • 级联选择器(Cascader)组件新增非受控用法
    • 支持部分数据动态加载
    • 新增多个示例组件以展示新功能
  • 改进

    • 重构级联选择器的状态管理
    • 简化组件的数据处理逻辑
    • 增强组件的类型安全性
    • 优化异步加载数据的处理方式
  • 文档更新

    • 补充非受控用法的使用说明
    • 更新异步加载数据的文档描述
    • 增加关于新示例组件的文档说明
  • 测试

    • 更新测试用例以适配新的组件结构
    • 移除不再使用的测试用例

Copy link

coderabbitai bot commented Jan 23, 2025

概述

遍历

这个拉取请求对Cascader组件进行了全面的重构和改进。主要变更包括重新设计组件的状态管理、简化测试套件、更新文档,以及引入新的实用函数来处理级联选择器的选项。这些修改旨在提高组件的灵活性、性能和可维护性。

变更

文件路径 变更摘要
src/packages/cascader/cascader.taro.tsx
src/packages/cascader/cascader.tsx
- 重构CascaderProps接口
- 更新onLoad方法签名
- 引入新的状态管理方法
src/packages/cascader/demos/* - 重命名状态变量
- 简化异步数据加载逻辑
- 更新事件处理函数
src/packages/cascader/doc* - 添加非受控用法文档
- 更新onLoadonChangeonPathChange属性描述
src/packages/cascader/utils.ts - 新增normalizeOptionsnormalizeListOptions实用函数
src/packages/cascader/helper.ts
src/packages/cascader/tree.ts
- 删除这些文件

建议的审阅者

  • xiaoyatong

相关的拉取请求

诗歌

🐰 级联的兔子,代码如流水
重构路径,状态如诗
选项归一,函数飞舞
组件轻盈,性能飞扬
重生的级联,兔子的梦想 🌈


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • 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 for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @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 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 for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the 2.x label Jan 23, 2025
@oasis-cloud oasis-cloud requested review from Alex-huxiyang and irisSong and removed request for Alex-huxiyang January 23, 2025 09:43
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (29)
src/packages/cascader/demos/h5/demo5.tsx (3)

1-7: 代码结构清晰,但可以改进类型定义!

状态管理的重构很好,变量命名更加简洁。建议为了更好的类型安全性,可以定义一个类型来描述选择器的值类型。

type CascaderValue = string[];
const [value, setValue] = useState<CascaderValue>([]);

29-30: 建议添加具体的类型定义

onChange 处理函数使用了 any 类型,这降低了代码的类型安全性。建议:

const onChange = (value: string[], path: CascaderOption[]) => {
  setValue(value);
};

32-54: 建议增加无障碍访问支持

组件的基本功能实现良好,但建议增加以下无障碍访问支持:

  1. 为 Cell 添加 aria-label 属性
  2. 为 Cascader 添加 aria-expanded 状态
  3. 添加键盘导航支持
 <Cell
   title="选择地址"
   description={value.length ? value : '请选择地址'}
+  aria-label="地址选择器"
+  role="button"
   onClick={() => {
     setVisible(true)
   }}
 />
src/packages/cascader/demos/h5/demo6.tsx (4)

22-89: 建议优化数据加载策略

  1. 使用 setTimeout 延迟 300ms 加载数据可能会影响用户体验。建议考虑以下优化方案:

    • 使用加载状态指示器
    • 考虑使用骨架屏(Skeleton)
    • 如果可能,减少延迟时间
  2. 代码中存在被注释的代码(第24行),建议清理无用代码。

建议应用以下改进:

  useEffect(() => {
+   const loadOptions = async () => {
+     setLoading(true)
      try {
-       setTimeout(() => {
-         // setValue(['浙江', '温州', '鹿城区'])
          setOptions([/* ... */])
-       }, 300)
+     } finally {
+       setLoading(false)
+     }
+   }
+   loadOptions()
  }, [])

27-87: 建议改进数据结构设计

  1. 数据中存在重复的地区名称,例如不同省份下都有"西湖区"和"温州",这可能会导致混淆。建议:

    • 为每个选项添加唯一标识符
    • 考虑使用更具区分性的命名
  2. disabled 状态的使用需要注意:

    • 当父节点被禁用时,其子节点的状态可能造成困惑
    • 建议在文档中说明禁用状态的继承规则

建议的数据结构改进:

  {
    value: '浙江',
    text: '浙江',
+   id: 'ZJ',
    children: [
      {
        value: '杭州',
        text: '杭州',
+       id: 'ZJ-HZ',
        disabled: true,
        children: [
          {
            value: '西湖区',
            text: '西湖区',
+           id: 'ZJ-HZ-XH',
            disabled: true
          }
        ]
      }
    ]
  }

90-94: 建议加强类型安全性

事件处理函数使用了 any 类型,建议使用具体的类型定义:

- const onChange = (value: any, path: any) => {
+ const onChange = (value: string[], path: CascaderOption[]) => {
    setValue(value)
  }
- const onPathChange = (value: any, path: any) => {
+ const onPathChange = (value: string[], path: CascaderOption[]) => {
    console.log('onPathChange', value, path)
  }

Line range hint 96-123: 建议增强可访问性

组件的渲染结构清晰,但建议添加以下可访问性改进:

  1. 为可交互元素添加 aria-label 属性
  2. 确保颜色对比度符合 WCAG 标准
  3. 添加键盘导航支持
  <Cell
    title="选择地址"
    description={value.length ? value : '请选择地址'}
+   aria-label="选择地址按钮"
+   role="button"
    onClick={() => {
      setVisible(true)
    }}
  />
  <ConfigProvider theme={customTheme}>
    <Cascader
      visible={visible}
      activeColor="#3768FA"
      value={value}
      title="选择地址"
      options={options}
      closeable
      activeIcon="star"
+     aria-label="地址选择器"
+     role="dialog"
      onClose={() => {
        setVisible(false)
      }}
      onChange={onChange}
      onPathChange={onPathChange}
    />
  </ConfigProvider>
src/packages/cascader/demos/taro/demo1.tsx (2)

6-10: 类型安全性改进建议

建议添加更严格的类型定义以提高代码的可维护性和类型安全性。

-  const [value, setValue] = useState([])
+  const [value, setValue] = useState<string[]>([])
-  const onChange = (value: any, path: any) => {
+  const onChange = (value: string[], path: CascaderOption[]) => {
     setValue(value)
   }

81-86: 提升无障碍访问性

建议添加适当的 ARIA 属性以提升组件的无障碍访问性。

   <Cell
     title="选择地址"
     description={value.length ? value : '请选择地址'}
+    role="button"
+    aria-haspopup="listbox"
+    aria-expanded={visible}
     onClick={() => {
       setVisible(true)
     }}
   />
src/packages/cascader/demos/taro/demo6.tsx (1)

Line range hint 9-16: 优化主题配置管理

建议将主题配置提取到独立的配置文件中,以便复用和统一管理。

+// src/packages/cascader/demos/taro/theme-configs.ts
+export const blueTheme = {
+  nutuiCascaderItemHeight: '48px',
+  nutuiCascaderItemMargin: '0 10px',
+  // ...
+}

-const customTheme = {
-  nutuiCascaderItemHeight: '48px',
-  nutuiCascaderItemMargin: '0 10px',
-  // ...
-}
+import { blueTheme } from './theme-configs'
src/packages/cascader/demos/h5/demo3.tsx (2)

25-26: 建议完善类型定义

onChange 函数的参数使用 any 类型过于宽松,建议使用更具体的类型定义。

- const onChange = (value: any, path: any) => {
+ const onChange = (value: CascaderValue, path: CascaderOption[]) => {

8-24: 优化异步加载实现

loadCascaderItemData 的实现逻辑清晰,但建议考虑以下改进:

  1. 添加错误处理
  2. 使用更合理的超时时间
  const loadCascaderItemData = (
    node: CascaderOption,
    level: number
  ): Promise<CascaderOption[]> => {
    return new Promise((resolve) => {
+     return new Promise((resolve, reject) => {
      setTimeout(() => {
+       try {
          const { value } = node
          const text = value?.toString().substring(0, 1)
          const value1 = `${text}${level + 1}1`
          const value2 = `${text}${level + 1}2`
          resolve([
            { value: value1, text: value1, leaf: level >= 2 },
            { value: value2, text: value2, leaf: level >= 2 },
          ])
+       } catch (error) {
+         reject(error)
+       }
      }, 500)
    })
  }
src/packages/cascader/demos/taro/demo5.tsx (2)

14-27: 建议优化初始化逻辑

在演示组件中使用 setTimeout 进行状态初始化显得多余,建议直接在 useEffect 中设置状态。这样可以减少不必要的延迟,提高用户体验。

  useEffect(() => {
-   setTimeout(() => {
      setValue(['广东省', '广州市'])
      setOptions([
        { value: '北京', text: '北京', id: 1, pidd: null },
        { value: '通州区', text: '通州区', id: 11, pidd: 1, sortKey: 2 },
        { value: '大兴区', text: '大兴区', id: 12, pidd: 1, sortKey: 1 },
        { value: '经海路', text: '经海路', id: 111, pidd: 12, sortKey: 2 },
        { value: '黄亦路', text: '黄亦路', id: 112, pidd: 12, sortKey: 1 },
        { value: '广东省', text: '广东省', id: 2, pidd: null },
        { value: '广州市', text: '广州市', id: 21, pidd: 2 },
      ])
-   }, 300)
  }, [])

8-12: 建议统一命名规范

format 对象中使用的 pidd 键名与标准的 pid 不一致,建议统一使用 pid 作为父级 ID 的键名。

  const format = {
    topId: null,
    idKey: 'id',
-   pidKey: 'pidd',
+   pidKey: 'pid',
  }
src/packages/cascader/utils.ts (1)

40-48: 建议优化类型定义

当前代码中使用了 any 类型,建议使用更具体的类型定义来提高代码的类型安全性。

- const newNode: any = { pid, id, ...others }
+ interface TreeNode extends CascaderOption {
+   pid: string | null
+   id: string | number
+ }
+ const newNode: TreeNode = { pid, id, ...others }
src/packages/cascader/demos/h5/demo4.tsx (2)

25-41: 建议优化异步加载示例

当前的 lazyLoadDemo4 实现使用了 setTimeout 来模拟异步加载,建议添加注释说明这只是演示用途,并展示更贴近实际场景的示例代码。

+ // 注意:此处使用 setTimeout 仅用于演示。实际应用中应该使用真实的 API 调用。
  const lazyLoadDemo4 = async (
-   node: any,
+   node: CascaderOption,
    level: number
  ): Promise<CascaderOption[]> => {
    return new Promise((resolve) => {
      setTimeout(() => {
        const { value } = node
        const text = value.substring(0, 1)
        const value1 = `${text}${level + 1}1`
        const value2 = `${text}${level + 1}2`
        resolve([
          { value: value1, text: value1, leaf: level >= 2 },
          { value: value2, text: value2, leaf: level >= 1 },
        ])
      }, 500)
    })
  }

42-43: 建议完善类型定义

change4 函数的参数使用了 any 类型,建议明确定义参数类型以提高代码的可维护性。

- const change4 = (value: any, path: any) => {
+ const change4 = (value: string[], path: CascaderOption[]) => {
src/packages/cascader/demos/taro/demo4.tsx (1)

29-40: 建议优化性能处理

使用固定的延迟时间(500ms)可能会影响用户体验。建议:

  1. 考虑使用动态超时时间
  2. 添加加载状态指示器
src/packages/cascader/demos/h5/demo7.tsx (1)

12-79: 建议优化数据加载机制

使用固定的 300ms 延迟加载初始数据可能不是最佳实践:

  1. 可能导致不必要的等待
  2. 在网络条件较差时可能不够用

建议:

  1. 考虑使用动态加载机制
  2. 添加加载状态提示
src/packages/cascader/demos/taro/demo7.tsx (3)

8-11: 建议改进类型安全性

onChange 处理函数中的参数类型使用 any 过于宽松,建议使用更具体的类型定义。

-  const onChange = (value: any, path: any) => {
+  const onChange = (value: string[], path: CascaderOption[]) => {
     console.log('onchange', value, path)
     setValue(value)
   }

12-79: 建议优化初始化逻辑

使用 setTimeout 模拟异步加载数据可能会影响测试和用户体验。建议:

  1. 考虑使用 loading 状态来处理异步加载
  2. 在实际场景中使用真实的 API 调用

82-88: 建议增加无障碍支持

为了提高组件的可访问性,建议添加 ARIA 属性:

 <Cell
   title="选择地址"
   description={value.length ? value : '请选择地址'}
+  role="button"
+  aria-haspopup="listbox"
+  aria-expanded={visible}
   onClick={() => {
     setVisible(true)
   }}
 />
src/packages/cascader/__tests__/cascader.spec.tsx (1)

214-228: 建议增强延迟加载测试

当前的测试实现可以进一步改进:

  1. 添加对错误情况的测试
  2. 验证加载状态的变化
  3. 测试不同的数据结构
 it('select with lazy', async () => {
   const lazyFunc = vi.fn()
+  const mockError = new Error('Loading failed')
   const { container } = render(
     <Cascader
       lazy
       value={['test']}
       onLoad={async (node: any) => {
+        if (node.value === 'error') {
+          throw mockError
+        }
         return new Promise((resolve) => {
           setTimeout(() => {
             lazyFunc()
             resolve([] as CascaderOption[])
           }, 50)
         })
       }}
     />
   )
   expect(lazyFunc).not.toBeCalled()
   await waitFor(() => {
     expect(lazyFunc).toBeCalled()
   })
+  // Test error handling
+  await expect(async () => {
+    fireEvent.click(container.querySelector('.error-node'))
+  }).rejects.toThrow(mockError)
 })
src/packages/cascader/doc.md (2)

23-31: 建议完善文档示例

新增的非受控用法部分建议添加:

  1. 更详细的代码示例
  2. 常见用例说明
  3. 与受控用法的对比说明

105-107: API 文档更新建议

API 文档的更新需要补充:

  1. 参数的详细类型定义
  2. 返回值的具体说明
  3. 各种场景下的使用示例
src/packages/cascader/doc.zh-TW.md (2)

57-57: 修复 Markdown 格式

强调标记内不应该有空格。

-**無需設定 `lazy` 屬性。 **
+**無需設定 `lazy` 屬性。**
🧰 Tools
🪛 Markdownlint (0.37.0)

57-57: null
Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


105-107: 建议改进类型定义

回调函数的参数类型使用 any 过于宽松,建议使用更具体的类型定义:

  • node 应该使用 CascaderOption 类型
  • pathNodes 应该使用 CascaderOption[] 类型
-| onLoad | 動態加載回調,開啟動態加載時生效 | `(node: any) => void` | `-` |
-| onChange | 選中值改變時觸發 | `(value: CascaderValue, pathNodes?: any) => void` | `-` |
-| onPathChange | 選中項改變時觸發 | `(value: CascaderValue, pathNodes: any) => void` | `-` |
+| onLoad | 動態加載回調,開啟動態加載時生效 | `(node: CascaderOption) => void` | `-` |
+| onChange | 選中值改變時觸發 | `(value: CascaderValue, pathNodes?: CascaderOption[]) => void` | `-` |
+| onPathChange | 選中項改變時觸發 | `(value: CascaderValue, pathNodes: CascaderOption[]) => void` | `-` |
src/packages/cascader/doc.en-US.md (2)

57-57: 修复 Markdown 格式

强调标记内不应该有空格。

-**No need to set the `lazy` attribute. **
+**No need to set the `lazy` attribute.**
🧰 Tools
🪛 Markdownlint (0.37.0)

57-57: null
Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


105-107: 建议改进类型定义

回调函数的参数类型使用 any 过于宽松,建议使用更具体的类型定义:

  • node 应该使用 CascaderOption 类型
  • pathNodes 应该使用 CascaderOption[] 类型
-| onLoad | Dynamic loading callback, which takes effect when dynamic loading is enabled | `(node: any) => void` | `-` |
-| onChange | Triggered when the selected value changes | `(value: CascaderValue, pathNodes?: any) => void` | `-` |
-| onPathChange | Triggered when the selected item changes | `(value: CascaderValue, pathNodes: any) => void` | `-` |
+| onLoad | Dynamic loading callback, which takes effect when dynamic loading is enabled | `(node: CascaderOption) => void` | `-` |
+| onChange | Triggered when the selected value changes | `(value: CascaderValue, pathNodes?: CascaderOption[]) => void` | `-` |
+| onPathChange | Triggered when the selected item changes | `(value: CascaderValue, pathNodes: CascaderOption[]) => void` | `-` |
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3300424 and a187e45.

⛔ Files ignored due to path filters (1)
  • src/packages/cascader/__tests__/__snapshots__/cascader.spec.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (29)
  • src/packages/cascader/__tests__/cascader.spec.tsx (4 hunks)
  • src/packages/cascader/cascader.taro.tsx (1 hunks)
  • src/packages/cascader/cascader.tsx (1 hunks)
  • src/packages/cascader/demo.taro.tsx (4 hunks)
  • src/packages/cascader/demo.tsx (4 hunks)
  • src/packages/cascader/demos/h5/demo1.tsx (1 hunks)
  • src/packages/cascader/demos/h5/demo2.tsx (1 hunks)
  • src/packages/cascader/demos/h5/demo3.tsx (1 hunks)
  • src/packages/cascader/demos/h5/demo4.tsx (1 hunks)
  • src/packages/cascader/demos/h5/demo5.tsx (1 hunks)
  • src/packages/cascader/demos/h5/demo6.tsx (3 hunks)
  • src/packages/cascader/demos/h5/demo7.tsx (1 hunks)
  • src/packages/cascader/demos/taro/demo1.tsx (1 hunks)
  • src/packages/cascader/demos/taro/demo2.tsx (1 hunks)
  • src/packages/cascader/demos/taro/demo3.tsx (1 hunks)
  • src/packages/cascader/demos/taro/demo4.tsx (1 hunks)
  • src/packages/cascader/demos/taro/demo5.tsx (1 hunks)
  • src/packages/cascader/demos/taro/demo6.tsx (3 hunks)
  • src/packages/cascader/demos/taro/demo7.tsx (1 hunks)
  • src/packages/cascader/doc.en-US.md (4 hunks)
  • src/packages/cascader/doc.md (4 hunks)
  • src/packages/cascader/doc.taro.md (4 hunks)
  • src/packages/cascader/doc.zh-TW.md (4 hunks)
  • src/packages/cascader/helper.ts (0 hunks)
  • src/packages/cascader/tree.ts (0 hunks)
  • src/packages/cascader/types.ts (2 hunks)
  • src/packages/cascader/utils.ts (1 hunks)
  • src/packages/tabs/tabs.tsx (1 hunks)
  • src/utils/is-empty.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • src/packages/cascader/tree.ts
  • src/packages/cascader/helper.ts
🧰 Additional context used
🪛 Markdownlint (0.37.0)
src/packages/cascader/doc.en-US.md

57-57: null
Spaces inside emphasis markers

(MD037, no-space-in-emphasis)

src/packages/cascader/doc.zh-TW.md

57-57: null
Spaces inside emphasis markers

(MD037, no-space-in-emphasis)

🪛 LanguageTool
src/packages/cascader/doc.zh-TW.md

[uncategorized] ~45-~45: 您的意思是“"不"透”?
Context: ...## 動態加載 lazy 屬性表示開啟資料的自動加載,Cascader 內部透過 valueonLoad 實作了自動載入資料的邏輯。 `laz...

(BU)

🔇 Additional comments (11)
src/packages/cascader/demos/h5/demo5.tsx (1)

8-12: 请确认排序功能是否受影响

format 对象中移除了 sortKey,但 options 中的一些选项仍然保留了 sortKey 属性。需要确认:

  1. 排序功能是否仍然正常工作
  2. sortKey 属性是否仍然需要保留在选项中
src/packages/cascader/demos/h5/demo6.tsx (1)

1-7: 代码结构优化得当!

状态变量的命名更加清晰,并且添加了类型定义,提高了代码的可维护性和类型安全性。

Also applies to: 19-21

src/packages/cascader/demos/taro/demo2.tsx (1)

89-102: 检查默认值与数据结构的匹配性

当前的 defaultValue 使用的是标准键名(['福建', '福州', '台江区']),但数据结构使用的是自定义键名(value1)。这可能导致选择器无法正确匹配默认值。

建议确保默认值与数据结构匹配:

-  defaultValue={value}
+  defaultValue={['福建', '福州', '台江区'].map(v => ({ value1: v }))}

或者更好的方案是统一使用标准键名,移除 optionKey 配置:

-  optionKey={{
-    textKey: 'text1',
-    valueKey: 'value1',
-    childrenKey: 'items',
-  }}
src/utils/is-empty.ts (1)

1-6: 函数实现正确,逻辑清晰

isEmpty 函数逻辑正确,能够有效判断传入的参数是否为空。

src/packages/cascader/types.ts (2)

15-16: 注意索引签名可能带来的类型安全问题

添加 [key: string]: any 索引签名虽然提供了更大的灵活性,但也可能导致类型检查失效,建议仅在确实需要动态属性时使用。

建议考虑以下替代方案:

  1. 使用 Pick/Omit 等工具类型来限制允许的属性
  2. 显式声明所有可能的属性,以保持类型安全

40-43: LGTM - 新增的 CascaderActions 类型定义清晰明确

类型定义简洁且符合组件的开放/关闭行为需求。

src/packages/cascader/demo.tsx (1)

9-9: LGTM - 新增非受控组件示例

新增的 Demo7 组件和相关翻译完善了组件的示例文档。

Also applies to: 47-48

src/packages/cascader/demo.taro.tsx (1)

11-11: 代码变更合理

新增的非受控组件示例及其相关翻译内容组织合理,符合组件文档规范。

Also applies to: 17-17, 26-26, 35-35, 50-51

src/packages/cascader/demos/taro/demo4.tsx (1)

4-4: 需要添加单元测试

当前缺少单元测试覆盖,这可能会导致潜在的问题在生产环境中才被发现。

需要我帮您生成单元测试代码吗?

src/packages/cascader/__tests__/cascader.spec.tsx (1)

Line range hint 169-185: 测试用例更新正确

选择器的更新正确反映了组件结构的变化,确保了测试的准确性。

src/packages/tabs/tabs.tsx (1)

27-27: 建议重新考虑类型定义的改变

title 属性的返回类型从 JSX.Element[] 改为 Element[] 可能会降低类型安全性。建议:

  1. 保持使用 JSX.Element[] 以获得更好的类型检查
  2. 如果需要更灵活的类型,考虑使用 ReactNode[]

Comment on lines +14 to +27
useEffect(() => {
setTimeout(() => {
setValue(['广东省', '广州市'])
setOptions([
{ value: '北京', text: '北京', id: 1, pidd: null },
{ value: '通州区', text: '通州区', id: 11, pidd: 1, sortKey: 2 },
{ value: '大兴区', text: '大兴区', id: 12, pidd: 1, sortKey: 1 },
{ value: '经海路', text: '经海路', id: 111, pidd: 12, sortKey: 2 },
{ value: '黄亦路', text: '黄亦路', id: 112, pidd: 12, sortKey: 1 },
{ value: '广东省', text: '广东省', id: 2, pidd: null },
{ value: '广州市', text: '广州市', id: 21, pidd: 2 },
])
}, 300)
}, [])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

建议改进异步数据加载的实现

当前使用 setTimeout 模拟异步加载数据的方式不适合生产环境。建议:

  1. 使用实际的异步数据获取方法
  2. 添加加载状态指示器
  3. 处理可能的错误情况
useEffect(() => {
  const fetchData = async () => {
    try {
      setLoading(true);
      const response = await fetchOptions();
      setOptions(response);
      setValue(['广东省', '广州市']);
    } catch (error) {
      console.error('Failed to fetch options:', error);
    } finally {
      setLoading(false);
    }
  };
  
  fetchData();
}, []);

Comment on lines +11 to +77
children: [
{ value: '鹿城区', text: '鹿城区' },
{ value: '瓯海区', text: '瓯海区' },
{
value: '杭州',
text: '杭州',
disabled: true,
children: [
{ value: '西湖区', text: '西湖区', disabled: true },
{ value: '余杭区', text: '余杭区' },
],
},
{
value: '温州',
text: '温州',
children: [
{ value: '鹿城区', text: '鹿城区' },
{ value: '瓯海区', text: '瓯海区' },
],
},
],
},
],
},
{
value: '湖南',
text: '湖南',
disabled: true,
children: [
{
value: '长沙',
text: '长沙',
value: '湖南',
text: '湖南',
disabled: true,
children: [
{ value: '芙蓉区', text: '芙蓉区' },
{ value: '岳麓区', text: '岳麓区' },
{
value: '长沙',
text: '长沙',
disabled: true,
children: [
{ value: '芙蓉区', text: '芙蓉区' },
{ value: '岳麓区', text: '岳麓区' },
],
},
{
value: '岳阳',
text: '岳阳',
children: [
{ value: '岳阳楼区', text: '岳阳楼区' },
{ value: '云溪区', text: '云溪区' },
],
},
],
},
{
value: '岳阳',
text: '岳阳',
value: '福建',
text: '福建',
children: [
{ value: '岳阳楼区', text: '岳阳楼区' },
{ value: '云溪区', text: '云溪区' },
{
value: '福州',
text: '福州',
children: [
{ value: '鼓楼区', text: '鼓楼区' },
{ value: '台江区', text: '台江区' },
],
},
],
},
],
},
{
value: '福建',
text: '福建',
children: [
{
value: '福州',
text: '福州',
children: [
{ value: '鼓楼区', text: '鼓楼区' },
{ value: '台江区', text: '台江区' },
],
},
],
},
])
const change1 = (value: any, path: any) => {
console.log('onChange', value, path)
setValue1(value)
}

])
}, 300)
}, [])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

建议优化数据加载逻辑

  1. setTimeout 模拟异步加载不是最佳实践,建议改用实际的数据获取方法。
  2. 缺少加载状态指示器,可能影响用户体验。

建议如下改进:

+  const [loading, setLoading] = useState(true)
   useEffect(() => {
-    setTimeout(() => {
+    const fetchOptions = async () => {
+      setLoading(true)
+      try {
         setOptions([/* ... */])
+      } finally {
+        setLoading(false)
+      }
+    }
+    fetchOptions()
-    }, 300)
   }, [])
📝 Committable suggestion

‼️ 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.

Suggested change
useEffect(() => {
setTimeout(() => {
setOptions([
{
value: '温州',
text: '温州',
value: '浙江',
text: '浙江',
children: [
{ value: '鹿城区', text: '鹿城区' },
{ value: '瓯海区', text: '瓯海区' },
{
value: '杭州',
text: '杭州',
disabled: true,
children: [
{ value: '西湖区', text: '西湖区', disabled: true },
{ value: '余杭区', text: '余杭区' },
],
},
{
value: '温州',
text: '温州',
children: [
{ value: '鹿城区', text: '鹿城区' },
{ value: '瓯海区', text: '瓯海区' },
],
},
],
},
],
},
{
value: '湖南',
text: '湖南',
disabled: true,
children: [
{
value: '长沙',
text: '长沙',
value: '湖南',
text: '湖南',
disabled: true,
children: [
{ value: '芙蓉区', text: '芙蓉区' },
{ value: '岳麓区', text: '岳麓区' },
{
value: '长沙',
text: '长沙',
disabled: true,
children: [
{ value: '芙蓉区', text: '芙蓉区' },
{ value: '岳麓区', text: '岳麓区' },
],
},
{
value: '岳阳',
text: '岳阳',
children: [
{ value: '岳阳楼区', text: '岳阳楼区' },
{ value: '云溪区', text: '云溪区' },
],
},
],
},
{
value: '岳阳',
text: '岳阳',
value: '福建',
text: '福建',
children: [
{ value: '岳阳楼区', text: '岳阳楼区' },
{ value: '云溪区', text: '云溪区' },
{
value: '福州',
text: '福州',
children: [
{ value: '鼓楼区', text: '鼓楼区' },
{ value: '台江区', text: '台江区' },
],
},
],
},
],
},
{
value: '福建',
text: '福建',
children: [
{
value: '福州',
text: '福州',
children: [
{ value: '鼓楼区', text: '鼓楼区' },
{ value: '台江区', text: '台江区' },
],
},
],
},
])
const change1 = (value: any, path: any) => {
console.log('onChange', value, path)
setValue1(value)
}
])
}, 300)
}, [])
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchOptions = async () => {
setLoading(true)
try {
setOptions([
{
value: '浙江',
text: '浙江',
children: [
{
value: '杭州',
text: '杭州',
disabled: true,
children: [
{ value: '西湖区', text: '西湖区', disabled: true },
{ value: '余杭区', text: '余杭区' },
],
},
{
value: '温州',
text: '温州',
children: [
{ value: '鹿城区', text: '鹿城区' },
{ value: '瓯海区', text: '瓯海区' },
],
},
],
},
{
value: '湖南',
text: '湖南',
disabled: true,
children: [
{
value: '长沙',
text: '长沙',
disabled: true,
children: [
{ value: '芙蓉区', text: '芙蓉区' },
{ value: '岳麓区', text: '岳麓区' },
],
},
{
value: '岳阳',
text: '岳阳',
children: [
{ value: '岳阳楼区', text: '岳阳楼区' },
{ value: '云溪区', text: '云溪区' },
],
},
],
},
{
value: '福建',
text: '福建',
children: [
{
value: '福州',
text: '福州',
children: [
{ value: '鼓楼区', text: '鼓楼区' },
{ value: '台江区', text: '台江区' },
],
},
],
},
])
} finally {
setLoading(false)
}
}
fetchOptions()
}, [])

src/packages/cascader/demos/taro/demo2.tsx Show resolved Hide resolved
src/packages/cascader/demos/taro/demo6.tsx Outdated Show resolved Hide resolved
src/packages/cascader/demos/taro/demo6.tsx Show resolved Hide resolved
src/packages/cascader/demos/taro/demo4.tsx Show resolved Hide resolved
src/packages/cascader/demos/h5/demo2.tsx Show resolved Hide resolved
src/packages/cascader/demos/h5/demo1.tsx Show resolved Hide resolved
src/packages/cascader/demos/h5/demo7.tsx Show resolved Hide resolved
src/packages/cascader/doc.taro.md Outdated Show resolved Hide resolved
Copy link

codecov bot commented Jan 23, 2025

Codecov Report

Attention: Patch coverage is 92.43028% with 19 lines in your changes missing coverage. Please review.

Project coverage is 84.22%. Comparing base (3300424) to head (ef83db8).

Files with missing lines Patch % Lines
src/packages/cascader/cascader.tsx 89.94% 19 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             next    #2956      +/-   ##
==========================================
+ Coverage   84.20%   84.22%   +0.01%     
==========================================
  Files         267      267              
  Lines       18125    17910     -215     
  Branches     2671     2633      -38     
==========================================
- Hits        15263    15084     -179     
+ Misses       2857     2821      -36     
  Partials        5        5              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/packages/address/address.tsx (1)

30-31: 实现方式值得肯定

使用 usePropsValue 来管理状态是个很好的做法,它能够正确处理受控和非受控的情况。建议在文档中说明这两个属性的使用方式。

  // 建议在属性注释中添加使用说明
  export interface AddressProps extends CascaderProps {
+   /** 当前选中的值(受控) */
    value: CascaderValue
+   /** 默认选中的值(非受控) */
    defaultValue: CascaderValue
src/packages/address/address.taro.tsx (2)

30-31: 建议完善类型定义和文档

代码实现保持了良好的一致性,但建议:

  1. 为了更好的开发体验,建议给 CascaderValue 类型添加更详细的文档说明
  2. 考虑添加类型测试以确保类型定义的正确性

让我们检查一下 CascaderValue 的使用情况:

#!/bin/bash
# 搜索 CascaderValue 的定义和使用
ast-grep --pattern 'type CascaderValue = $$$'

30-31: 关于此次改动的整体建议

  1. 接口变更

    • valuedefaultValue 改为必选是一个破坏性更改
    • 建议在下一个主版本更新中进行此变更
    • 需要在更新文档中明确说明此变更
  2. 文档完善

    • 补充类型定义的详细说明
    • 添加受控和非受控使用的示例代码
    • 更新升级指南
  3. 测试建议

    • 添加类型测试
    • 补充边界情况的单元测试
    • 添加 E2E 测试确保功能正常

Also applies to: 14-15

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a187e45 and 1d7b140.

⛔ Files ignored due to path filters (1)
  • src/packages/address/__test__/__snapshots__/address.spec.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (4)
  • src/packages/address/address.taro.tsx (1 hunks)
  • src/packages/address/address.tsx (1 hunks)
  • src/packages/address/customRender.taro.tsx (1 hunks)
  • src/packages/address/customRender.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (2)
src/packages/address/customRender.tsx (1)

14-15: ⚠️ Potential issue

需要注意接口变更可能导致的兼容性问题

valuedefaultValue 从可选改为必选是一个破坏性更改。虽然有 defaultProps 提供默认值,但这种改动可能会影响现有代码。建议:

  1. 在升级指南中说明这个变更
  2. 考虑是否真的需要将这些属性设为必选

让我们验证一下这个改动的影响范围:

src/packages/address/customRender.taro.tsx (1)

14-15: 保持了 Web 和 Taro 版本的一致性

接口变更与 Web 版本保持一致,这有助于代码维护。但同样需要注意兼容性问题。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/packages/cascader/demos/taro/demo6.tsx (1)

89-93: 🛠️ Refactor suggestion

建议完善事件处理器实现

  1. 需要添加正确的类型注解
  2. onPathChange 应该实现实际功能或移除
-  const onChange = (value: any, path: any) => {
+  const onChange = (value: string[], path: CascaderOption[]) => {
     setValue(value)
   }
-  const onPathChange = (value: any, path: any) => {
-    console.log('onPathChange', value, path)
+  const onPathChange = (value: string[], path: CascaderOption[]) => {
+    // 实现路径变更的实际处理逻辑
   }
🧹 Nitpick comments (9)
src/packages/cascader/doc.taro.md (3)

45-46: 建议增加代码示例说明返回值类型

为了更好地帮助开发者理解,建议在 onLoad 方法的返回值类型说明中添加一个简单的代码示例。

// 示例代码
const onLoad = async (node: CascaderOption): Promise<CascaderOption[]> => {
  return [
    { text: '选项1', value: '1' },
    { text: '选项2', value: '2' }
  ]
}

56-58: 说明清晰,建议补充示例场景

部分数据动态加载的说明很清楚,特别是强调了无需设置 lazy 属性这一重要细节。建议补充一个具体的应用场景示例,以帮助开发者更好理解。


105-107: 建议完善类型定义说明

Props 表格中的类型定义可以更详细:

  1. onLoad 的参数类型建议明确说明 node 的具体结构
  2. CascaderValue 类型需要在文档中说明具体定义
  3. pathNodes 参数的具体数据结构建议添加示例说明

建议添加如下类型说明:

interface CascaderOption {
  value: string | number
  text: string
  children?: CascaderOption[]
  // 其他可能的属性...
}

type CascaderValue = (string | number)[]
src/packages/cascader/demos/h5/demo6.tsx (2)

22-88: 建议优化模拟数据加载方式

在演示组件中使用 setTimeout 模拟数据加载可能会影响用户体验,建议考虑以下方案:

  1. 直接初始化数据
  2. 如果确实需要模拟异步加载,建议添加 loading 状态展示

89-93: 建议改进类型定义

事件处理函数使用了 any 类型,建议使用更具体的类型定义来提高代码的类型安全性:

-const onChange = (value: any, path: any) => {
+const onChange = (value: string[], path: CascaderOption[]) => {
  setValue(value)
}
-const onPathChange = (value: any, path: any) => {
+const onPathChange = (value: string[], path: CascaderOption[]) => {
  console.log('onPathChange', value, path)
}
src/packages/cascader/demos/taro/demo7.tsx (2)

12-79: 建议优化数据加载和数据结构定义

  1. 建议将选项数据结构提取为单独的常量,提高代码可维护性
  2. 模拟异步加载可以考虑使用更现代的方式,如 Promiseasync/await

建议重构为:

const ADDRESS_OPTIONS: CascaderOption[] = [
  {
    value: '浙江',
    text: '浙江',
    children: [
      // ... existing options
    ],
  },
  // ... other provinces
];

// In component:
useEffect(() => {
  const loadOptions = async () => {
    setValue(['浙江', '温州', '鹿城区']);
    setOptions(ADDRESS_OPTIONS);
  };
  loadOptions();
}, []);

80-108: 建议优化事件处理器并移除调试代码

  1. 需要移除 onPathChange 中的 console.log
  2. 组件结构和事件处理设计合理

建议修改:

-        onPathChange={(value, path) => {
-          console.log(value, path)
-        }}
+        onPathChange={(value, path) => {
+          // 根据需要处理路径变化
+        }}
src/packages/cascader/demos/taro/demo6.tsx (2)

19-21: 建议将初始值提取为常量

为了提高代码的可维护性和重用性,建议将初始值提取为一个命名常量。

+const INITIAL_VALUE = ['浙江', '温州', '鹿城区']

const Demo6 = () => {
  const [visible, setVisible] = useState(false)
-  const [value, setValue] = useState<string[]>(['浙江', '温州', '鹿城区'])
+  const [value, setValue] = useState<string[]>(INITIAL_VALUE)
  const [options, setOptions] = useState<CascaderOption[]>([])

106-112: 建议在UI中展示加载状态

为了提升用户体验,建议将加载状态传递给 Cascader 组件。

   <Cascader
     visible={visible}
     activeColor="#3768FA"
     value={value}
     title="选择地址"
     options={options}
+    loading={loading}
     closeable
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1d7b140 and ef83db8.

📒 Files selected for processing (4)
  • src/packages/cascader/demos/h5/demo6.tsx (3 hunks)
  • src/packages/cascader/demos/taro/demo6.tsx (3 hunks)
  • src/packages/cascader/demos/taro/demo7.tsx (1 hunks)
  • src/packages/cascader/doc.taro.md (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (8)
src/packages/cascader/doc.taro.md (1)

23-31: 文档结构清晰,示例引用正确!

新增的非受控用法部分结构合理,与文档风格保持一致。

src/packages/cascader/demos/h5/demo6.tsx (4)

Line range hint 1-21: 代码结构优化得当!

状态变量的命名更加规范,类型定义更加明确,这样的改动提高了代码的可维护性。


34-68: 注意数据重复问题

选项数据中存在重复的城市和区域名称:

  • "西湖区" 和 "余杭区" 在杭州和长沙下都出现
  • "温州" 在浙江和湖南下都出现

这可能会导致用户混淆,建议使用更真实的示例数据。


Line range hint 95-104: 单元格组件实现合理!

空值处理得当,点击事件处理简洁明了。


105-119: 级联选择器配置完善!

组件属性设置合理,自定义主题色彩运用得当,事件绑定完整。

src/packages/cascader/demos/taro/demo7.tsx (1)

1-7: 代码结构清晰,类型定义完整!

组件的基础架构和状态管理设置合理。

src/packages/cascader/demos/taro/demo6.tsx (2)

24-86: 建议将选项数据提取到单独的文件中

当前选项数据结构在组件中占用了大量空间,建议将其提取到单独的文件中以提高可维护性。

建议创建 src/packages/cascader/demos/taro/mock-data.ts 文件:

export const cascaderOptions: CascaderOption[] = [
  {
    value: '浙江',
    text: '浙江',
    children: [
      // ... 现有选项数据
    ]
  },
  // ... 其他省份数据
]

22-88: 🛠️ Refactor suggestion

建议添加错误处理和加载状态

异步加载数据时应该添加适当的错误处理机制和加载状态指示。

+  const [loading, setLoading] = useState(false)
   useEffect(() => {
+    setLoading(true)
     setTimeout(() => {
       setOptions([
         // ...
       ])
+      setLoading(false)
+    }, 300).catch((error) => {
+      console.error('加载选项失败:', error)
+      setLoading(false)
     })
   }, [])

Likely invalid or redundant comment.

Comment on lines +8 to +11
const onChange = (value: any, path: any) => {
console.log('onchange', value, path)
setValue(value)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

建议改进类型定义和移除调试代码

  1. 应该避免使用 any 类型,建议使用具体的类型定义
  2. 生产环境代码中不应该保留 console.log

建议按如下方式修改:

-  const onChange = (value: any, path: any) => {
-    console.log('onchange', value, path)
+  const onChange = (value: string[], path: CascaderOption[]) => {
     setValue(value)
   }

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant