范围:本导览只管「挑选」这一族在文档层面的跨篇路由——选项挑选(
C05-01Select、C05-02AutoComplete、C05-03Cascader、C05-04TreeSelect、C05-05Transfer)与时间挑选(C05-06DatePicker、C05-07TimePicker、C05-08Calendar),外加把三个下拉组件横着切的C05-00。基座机制不在本类展开,各有各的去处:日期算术与时间列算法归I07,弹层开合与定位归I02、I01、A07,虚拟化引擎归I08,受控 hook 归I13,注入链归A05,导出物与全局类型生成链归A02。
这一类九个组件解的是同一道题:把调用方手里的一份集合压成一个受控值,再把这个值压回成看得见的东西。状态流向在两个族里都是单向的——取值、归一、挑选、显示回查、事件回流(C05-00 §一、C05-06 §一)。差别只在「集合」长什么样:选项族的集合是一份候选数据,时间族的集合是一整套由日历格子与时间列构成的坐标系。也正因此,本类真正难的地方不在交互而在值语义——同一条 v-model 在五个选项组件和三个时间组件里有五种不同的形状约定(C05-00 论点 3、C05-03 论点 1、C05-06 论点 2)。
第一条分界线沿内核划。选项族真正共享的只有两样:一份 970 行的外壳 vc-select/BaseSelect.tsx,被 tree-select 与 cascader 直接 import 当选择器基座(C05-00 六);以及一处数据段的真复用——把树打成实体表的 convertDataToEntities,被 tree-select 与 cascader 同一份调用(C05-00 论点 2)。除这两处之外,入口归一、字段名归一、过滤、显示回查、出口快照五段各写一遍,横写篇的图 1 里三条泳道之间只画得出四条实线(C05-00 图 1)。所以「同一份业务数据在三个组件之间迁移」这件事,成本由使用者承担而不是由库承担(C05-00 六)。
第二条分界线在值的类型上。时间族不碰上面任何一条流水线,底座是 I07 描述的日期内核,公开层做的是「实例化」而不是「实现」:六个公开组件出自同一个工厂在模块加载时的返回值(C05-06 论点 1),TimePicker 又在工厂之上套了一层(C05-07 论点 1),Calendar 只从内核取走 PickerPanel 一个符号(C05-08 论点 1)。这一族的值必须经过日期对象与字符串的往返,转换点是组件层散落的 8 加 6 处 toDate 与 toString(C05-06 论点 2)。
弹层不属于本类任何一篇,这是选型时最容易记错的一条界。三个下拉选择器的开合、焦点、点外关闭全部落在 I 层的 I02 与 A 层的 A07,横写篇在 §一 就把这段整体划走(C05-00 §一)。时间族里只有 DatePicker 与 TimePicker 走浮层,本类交出去的只是一张 placement 到 dropdownAlign 的纯查表,碰撞检测本体归 I01(C05-06 §五)。Calendar 一项都不用到——「面板常显」不是把 open 焊成常量,而是换了一个 import 符号(C05-08 论点 1)。一句话:改「下拉里出现什么」是本类的事,改「下拉怎么出现、出现在哪」不是。
两族的状态分布恰好相反。选项族把状态切碎散在很多处:开合在基座里是三个变量而刻意不用 useMergedState,输入框状态被切成四片分给四个文件(C05-01 论点 4);tree-select 的下拉内容走 18 个键的 Symbol context 注入而不是 props(C05-04 取舍一)。时间族几乎把状态全交给内核,组件层剩下的「实现」就是 JSX 属性的书写顺序——value、disabled、prefixCls、onChange 能拿到转换后的版本,全靠显式属性写在 restProps 展开之后(C05-06 论点 5)。AutoComplete 是这条谱系的最极端点:零响应式状态,实现等于 15 个键的写入顺序(C05-02 论点 1)。
Transfer 是族外的对照组。它既不接选择器基座也不接选项流水线,唯一持久化的状态是 targetKeys 一个数组,左右两列全是它的派生视图(C05-05 论点 1),它唯一的浮层是表头那个批量操作下拉菜单(C05-05 §一)。把它留在这一类,是因为九个组件共享同一族失败模式:声明面活着、实现面死了——删除清单两份力度不同(C05-02 论点 2)、类型上有而运行时表里没有(C05-06 论点 5)、props 表里的默认值其实由内核补(C05-07 坑 5)、对外承诺的第二个参数从不发出去(C05-08 论点 2)。这类坑不会因为换组件而消失,只会因为换组件而换一处发生。
| 要解决的问题 | 选它 | 而不是另一个 | 判据 |
|---|---|---|---|
| 一层扁平候选、单选或多选、可能要承载上千条 | C05-01 Select | C05-03 Cascader | Select 的索引是两张 Map 的点查,刻意不建父子实体表,因为它是本类最常承载大列表的那个组件(C05-00 六);Cascader 的值必须整条路径建模(C05-03 论点 1) |
| 一条值本身就代表多级路径(省市区、分类链) | C05-03 Cascader | C05-04 TreeSelect | Cascader 的主键是路径拼接串,能直接进 Set、能当 DOM 属性反查、也能被传导算法当成与 Tree 同构的 key(C05-03 论点 1);TreeSelect 的内部 key 被硬绑到 value 字段,同值即同节点(C05-04 坑 6) |
| 层级数据但父子勾选要彼此独立,且要拿到带标签的值对象 | C05-04 TreeSelect 开 treeCheckStrictly | C05-03 Cascader | treeCheckStrictly 会把值形状换成带 halfChecked 的对象数组并强制 labelInValue(C05-04 坑 2);Cascader 没有独立勾选语义,一次勾选就是重算整棵集合(C05-03 论点 4) |
| 搜索结果里要不要保留层级可见 | 保父级留树形选 C05-04,打平成一条路径串选 C05-03,整组保留选 C05-01 | 以为三者过滤语义相同 | 过滤段是三套算法且输出形状都不同,只有 cascader 会把树打平并改写 label(C05-00 论点 4),另见 C05-03 坑 5 |
| 只要「能输入的框加建议列表」,不要选中语义 | C05-02 AutoComplete | C05-01 Select 的 combobox 写法 | combobox 在公开 API 上并不存在,它是一对镜像字符串常量在 computed 里翻译出来的哨兵(C05-01 论点 2);AutoComplete 零状态,实现就是键的写入顺序(C05-02 论点 1) |
| 全集固定,要把一批条目显式搬到另一侧并保留右侧顺序 | C05-05 Transfer | C05-01 Select 的 multiple 形态 | Transfer 只有 targetKeys 是值,右侧顺序由它的下标充当排序键、左侧顺序被 dataSource 锁死(C05-05 取舍一),两个方向的落位规则还彼此不对称(C05-05 论点 2);且不共享选项流水线,label 与 value 归一在这里全部不成立(C05-05 §一) |
| 值要在日期对象与字符串之间稳定进出(存库、回显、二次编辑) | C05-06 DatePicker 配 valueFormat | 自己在 onChange 里手动格式化 | 转换点是组件层散落的 8 加 6 处 toDate 与 toString,单选与范围在空值归一上并不一致(C05-06 论点 2),手写等于把漂移再复制一遍 |
| 起止两个时间点要联动,且允许只填一端 | C05-06 RangePicker | 两个 DatePicker 拼接 | 内核侧同时维护三份值状态,点错顺序时清空另一端而不是交换(I07 论点 4);allowEmpty 与 order 的交互只存在于范围路径(C05-07 坑 3) |
| 只挑时间不挑日期 | C05-07 TimePicker | C05-06 DatePicker 加 showTime | 裸 TimePicker 不传 format 时子串反推整段不触发、三列默认全开(C05-07 论点 2 与 坑 1);走 showTime 那条路要经过对格式串的字符扫描而不是 token 解析(C05-06 论点 6) |
| 面板常驻、占满一整块区域,不要输入框也不要浮层 | C05-08 Calendar | 把 DatePicker 的 open 焊成常开 | Calendar 只 import PickerPanel 一个出口,A07 那套 trigger 与 align 用到 0 项,面板常显是文件级的结构决定而不是配置项(C05-08 论点 1) |
| 想按年、月、季、周切换面板 | C05-06 DatePicker 的 picker 属性 | WeekPicker、MonthPicker 这类别名标签 | 工厂的闭包参数优先于 prop,别名件上写 picker 会被三重压制且没有任何提示(C05-06 坑 5);别名告警的条件写反了,除 quarter 外永不触发(C05-06 坑 6) |
options,新选项要关一次再开一次才出现 → C05-00 坑 1;冻结在末端快照的 useMemo 谓词上,且 cascader 是唯一不受影响的组件(C05-00 论点 6)。C05-00 坑 2;根因是四份字段名归一的返回键集合互不兼容(C05-00 论点 3)。optionFilterProp 之后 tags 模式的「搜索词补成新标签」也跟着变了 → C05-00 坑 3;同一个 prop 被两处判定复用,而一处是全等比较一处是子串匹配;提交通道本来就有三条互不调用(C05-01 论点 3)。C05-04 坑 6 与 C05-00 坑 4 是同一事实的两面,三条 dev 校验整体包在 NODE_ENV 判断里被摇掉;cascader 允许同值不同路径,别把校验照搬过去(C05-00 坑 4 改动边界)。C05-03 坑 5;过滤阶段直接把拼接结果写回选项对象(C05-00 论点 4)。tagRender 里 option 恒为 undefined,value 是一串带分隔符的长文本 → C05-03 论点 5 与 坑 5;跨基座时路径数组被压成字符串主键(C05-03 取舍四),分隔符又不对外导出。C05-01 坑 1(三份互不复用的删除清单,C05-01 论点 1);同族还有 C05-04 论点 1、C05-04 论点 2、C05-02 论点 2(两份清单力度不同,四个被删的名字两真死两假活)。mode 拼错或写 combobox 时静默退化成单选,不报错 → C05-01 坑 2;内核的告警文件全文没有一处 mode 白名单枚举。status 传空串清不掉 Form 的校验态 → C05-01 坑 3(空串是假值被兜底跳过);同族形状在时间侧是范围传空数组被当成受控值,因为范围侧缺了单选那一步空串归一(C05-06 坑 4)。dropdownMatchSelectWidth 设成 false 顺带关掉虚拟滚动,显式 virtual 也救不回来 → C05-01 坑 5 与 C05-04 坑 8。这两篇口径不一致,需仲裁:C05-01 坑 5 的改动边界写「tree-select、cascader 共用同一条 realVirtual」,但现 grep 为 grep -rn "realVirtual" packages/xiaoye-ui/src,只命中 vc-select/Select.tsx:554 与 :587 两行;vc-tree-select/OptionList.tsx:232 是第二份独立表达式,改 Select 那份打不到它;vc-cascader 目录 virtual 零命中,它只在 vc-cascader/Cascader.tsx:547 给宽度键补了个 false 默认而不进任何虚拟化判定。结论以 C05-04 坑 8 与 C05-00 §一(I08 同口径)为准。顺带一条:这条绑定为什么要挂在宽度 API 上,C05-01 六 已就地降级为未证实推断,C05-04 取舍三 也只说「意图可以理解」。<xy-config-provider :virtual="false"> 对 Select 无效、对 TreeSelect 有效 → C05-01 坑 10;注入链已经合成好了,但公开层的解构清单里没取(A05 论点 1 那条汇合点的漏取变体)。C05-04 坑 7 与 C05-03 坑 4,两处是各写一遍的短路。loadData 失败或没回写 children,那一行转圈永久停不下来 → C05-03 坑 3;加载态是下拉列表私有状态、没有任何对外入口(C05-03 论点 3),与 C09-01 论点 4 记录的 Tree 侧是同一形状。selectedKeys 在点移动按钮后整批丢失,残留键之后点谁都被当勾过 → C05-05 坑 1;oneWay 的删除路径同样漏了清空与 Form 通知(C05-05 坑 2),因为那条通道没复用主算法的收尾三步(C05-05 取舍二)。C05-05 坑 3;当前页被钳成 0 让切片恒空,两端对「当页」的定义靠手工保持一致(C05-05 论点 7)。prefixCls,下拉里整棵树丢样式 → C05-04 坑 1;CSS 侧与 DOM 侧是两条派生路径,只在默认前缀下重合(C05-04 论点 5,上游口径见 A05 论点 2)。C05-03 坑 7(控件类名全是 select 前缀)、C05-08 坑 3(面板视觉本体归 date-picker)、C05-02 取舍 2(连自己的前缀都没有,主题系统里没身份)。@change、@blur、@search、@tree-expand 就在 dev 打一条 prop 类型警告 → C05-04 坑 3;事件键经 mergeProps 被收集成数组而不是互相覆盖,这条与共则里「JSX 属性顺序覆盖」在 on* 键上方向相反。ref 上少方法:scrollTo 类型里有、运行时没有 → C05-04 坑 5(断链只发生在公开层这一跳)与 C05-02 坑 9。hourStep 传 5 或 7 只弹一条 warning,列照样错位 → C05-07 坑 2;校验不改值、列生成原样读 step,算法本体与同一条坑在 I07 坑 5;想在本层给 step 补默认值是无效的(C05-07 坑 5)。v-model:mode 在 TimePicker 上被强制失效 → C05-07 坑 6,外层无条件写 mode 为 undefined;这是设计上的封锁而不是 bug。<xy-year-picker> 写不出来、DatePicker.TimePicker 少了外层壳 → C05-06 坑 7(install 与生成物三张名单互不对齐,生成物侧口径见 A02 论点 11)与 C05-07 坑 4(同名不同体的两个 TimePicker)。PickerTag 不会改变 preset 外观;preset 的 value 绕过 valueFormat → C05-06 坑 8 与 C05-06 论点 3;后者「字符串 preset 会让面板选中态异常」的具体失败形态在 C05-06 §七 未证清单里已降级,别当实测结论用。onSelect 的第二个参数永远 undefined → C05-08 坑 1,属于类型面承诺、实现面丢弃;C05-08 论点 2 给了同类对照。C05-04 坑 9(vendor 全目录零测试、公开层 8 行冒烟)、C05-05 坑 7(5 个 skip 加一个说谎的夹具)、C05-06 坑 3(逗号表达式让断言强度只剩一半)。@change 与 @ok 像是被调了两次 → 2026-09-29 已修:日期侧 C05-06 论点 5 那三条同时成立的条件已拆掉(onChange 仍既被声明成 props 又被声明进 emits,但处理函数里 emit 与 callEvent 相邻两句只剩 emit,公开层另外剥掉摊给厂商层的桥接事件键),实测点一次日期单元格把外部 @change 打 3 次、修后 1 次,回归用例 packages/xiaoye-ui/src/_util/__tests__/event-dispatch-count.test.tsx;时间侧的外层截走内层回调再自己 emit 见 C05-07 论点 5,该侧次数在 jsdom 里数不到(点时间格不提交),改由 mergeProps 数组告警断言钉住(基线 4 条 → 0 条,用例在 event-dispatch-count.test.tsx:371)。判别方法是数次数而不是看参数——仓内原有断言全是 toHaveBeenCalledWith,同一函数被调两遍照样绿。表单侧已证的真实后果是重复通知校验,见 C06-01 论点 4 与 坑 3。C05-00 论点 1 至论点 6 与 坑 1、坑 2,再落到对应组件篇的装配段;C05-00 §一 已经写明三段不归它——弹层 I02、虚拟化 I08、受控链 I13。BaseSelect 的开合、焦点、清除:读 C05-01 取舍 3 与 坑 8、C05-00 六(三个组件共享同一份 970 行外壳)、C05-04 取舍一(18 键 context 注入的对应关系全靠人维持),弹层侧配 A07 论点 2 与 I02 论点 2。C05-01 坑 6(三条互不调用的通道)、C05-03 坑 2(鼠标与键盘走的不是同一道门禁)、C05-00 坑 8(空态被伪装成一条选项,键盘可聚焦但不可选)。C05-06 论点 2、坑 1、坑 4 必读,且单选与范围必须成对改;内核侧读 I07 论点 5,preset 那条绕过转换的旁路读 C05-06 论点 3。C05-07 论点 3、坑 2、坑 5;算法本体与另一份同名坑在 I07 论点 6 与 坑 5。本层只是透传两跳,改默认值要去内核改。C05-03 坑 7、C05-08 坑 3 与 坑 4、C05-06 §二(那个 1446 行的样式文件是本类最大单文件),token 分层口径归 A03。C05-01 坑 1、C05-04 论点 2、C05-02 论点 2;改完的生成物面(components.ts、global.d.ts、exports)归 A02,本类已经有一处三张名单不一致的实测后果在 C05-06 坑 7。C05-01 坑 10 与 C05-04 坑 8 给出两个取值点差异,注入链本体读 A05 论点 1 与 论点 3。C05-06 论点 4(两条互不相干的注入线)、C05-05 坑 2(第二条变更路径没通知 Form)、C05-07 论点 5(TimePicker 侧的数组形状已由告警断言钉住,2026-09-30),字段侧配 C06-01 论点 4 与 坑 3。C05-03 论点 3 与 坑 3、C05-04 坑 7、C05-00 坑 6(六个魔法字符串是流水线的隐式协议);同形状的第二例在 C09-01 论点 4,改时要一并核。