如何引入
基本用法
多选
可搜索的
可搜索的多选
限制标签展示数量
限制选中数量
选择即改变
自定义显示
自定义分隔符
禁用
严格禁用
展示子菜单的时机
在顶部/底部渲染附加项
受控
自动合并 value
仅叶子节点
动态更新数据
异步加载数据
超长列表
自定义 Trigger
API 参考
Methods
Accessibility
文案规范
设计变量
输入类 · Cascader
级联选择
用于选择多级分类下的某个选项。
API 文档
设计文档
代码演示
如何引入
import { Cascader } from '@douyinfe/semi-ui';
基本用法
最简单的用法,默认只可以选叶子节点。
import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
/>
);
};
多选
version: >= 1.28.0
设置
multiple
,可以进行多选。import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
multiple
/>
);
};
可搜索的
通过设置
filterTreeNode
属性可支持搜索功能。默认对 label
值进行搜索,可通过 treeNodeFilterProp
更改。
默认搜索结果只会展示叶子结点的路径,想要显示更多的结果,可以设置 filterLeafOnly
为 false
。import React, { useState } from 'react';
import { Cascader, Typography } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<div>
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="默认对label值进行搜索"
filterTreeNode
/>
<br/>
<br/>
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="对value值进行搜索"
filterTreeNode
treeNodeFilterProp='value'
/>
<br/>
<br/>
<Typography.Title heading={6}>filterLeafOnly=false:</Typography.Title>
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="默认对label值进行搜索"
filterTreeNode
filterLeafOnly={false}
/>
</div>
);
};
可搜索的多选
支持多选和搜索同时使用(version >= v1.28.0),在这种场景下,可以通过按下 BackSpace 键来删除对应的已选项目。
import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const [value, setValue] = useState(['zhejiang', 'ningbo', 'haishu']);
const onChange = (val) => { setValue(val); };
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
value={value}
multiple
filterTreeNode
onChange={e => onChange(e)}
/>
);
};
可以使用
filterSorter
对筛选后的数据进行排序, filterSorter
于 v2.28.0 开始提供。import React, { useState } from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: 'Product',
value: 'Product',
children: [
{
label: 'Semi-Material',
value: 'Semi-Material',
},
{
label: 'Semi-DSM',
value: 'Semi-DSM',
},
{
label: 'Semi',
value: 'Semi',
},
{
label: 'Semi-C2D',
value: 'Semi-C2D',
},
{
label: 'Semi-D2C',
value: 'Semi-D2C',
},
],
}
];
return (
<div>
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="输入 s 查看排序效果"
filterTreeNode
filterSorter={(first, second, inputValue) => {
const firstData = first[first.length - 1];
const lastData = second[second.length - 1];
if (firstData.label === inputValue) {
return -1;
} else if (lastData.label === inputValue) {
return 1;
} else {
return firstData.label < lastData.label ? -1 : 1;
}
}}
/>
</div>
);
};
如果想要自定义渲染搜索后的选项,可以使用
filterRender
实现整行的自定义渲染,filterRender
于 v2.28.0 开始提供,函数参数如下:interface FilterRenderProps { className: string; inputValue: string; // 搜索栏搜索内容 disabled: boolean; // 是否禁用 data: CascaderData[]; // 搜索结果数据 selected: boolean; // 单选时的选中状态 checkStatus: { // 多选时的选中状态 checked: boolean; halfChecked: boolean; }; onClick: (e: React.MouseEvent) => void; // 单选点击选中回调 onCheck: (e: React.MouseEvent) => void; // 多选点击选中回调 }
使用示例如下
import React, { useState } from 'react';
import { Cascader, Typography, Checkbox } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: 'Semi',
value: 'Semi',
children: [
{
label: 'Semi-Material Semi-Material Semi-Material Semi-Material',
value: 'Semi-Material',
},
{
label: 'Semi-DSM Semi-DSM Semi-DSM Semi-DSM',
value: 'Semi-DSM',
},
{
label: 'Semi Design Semi Design Semi Design Semi Design',
value: 'Semi',
},
{
label: 'Semi-C2D Semi-C2D Semi-C2D Semi-C2D Semi-C2D',
value: 'Semi-C2D',
},
{
label: 'Semi-D2C Semi-D2C Semi-D2C Semi-D2C Semi-D2C ',
value: 'Semi-D2C',
},
],
}
];
const { Text } = Typography;
const renderSearchOptionSingle = (props) => {
const { className, data, selected, onClick } = props;
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<li
className={className}
style={{ justifyContent: 'flex-start' }}
role="treeitem"
onClick={onClick}
>
<Text
ellipsis={{ showTooltip: { opts: { style: { wordBreak: 'break-all' } } } }}
style={{ width: 270, color: selected ? 'var(--semi-color-primary)': undefined }}
>
{data.map(item => item.label ).join(' / ')}
</Text>
</li>
);
};
const renderSearchOptionMultiple = (props) => {
const { className, data, checkStatus, onCheck } = props;
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<li
className={className}
style={{ justifyContent: 'flex-start' }}
role="treeitem"
onClick={onCheck}
>
<Checkbox
onChange={onCheck}
indeterminate={checkStatus.halfChecked}
checked={checkStatus.checked}
style={{ marginRight: 8 }}
/>
<Text
ellipsis={{ showTooltip: { opts: { style: { wordBreak: 'break-all' } } } }}
style={{ width: 250 }}
>
{data.map(item => item.label).join(' / ')}
</Text>
</li>
);
};
return (
<div>
<p>鼠标 hover 到选项可查看被省略文本完整内容</p>
<br />
<Cascader
style={{ width: 320 }}
treeData={treeData}
placeholder="单选,输入 s 自定义搜索选项渲染结果"
filterTreeNode
filterRender={renderSearchOptionSingle}
/>
<br />
<Cascader
multiple
style={{ width: 320, marginTop: 20 }}
treeData={treeData}
placeholder="多选,输入 s 自定义搜索选项渲染结果"
filterTreeNode
filterRender={renderSearchOptionMultiple}
/>
</div>
);
};
如果搜索结果中存在大量 Option,可以通过设置 virtualizeInSearch 开启搜索结果面板的虚拟化来优化性能,virtualizeInSearch 自 v2.44.0 提供。virtualizeInSearch 是一个包含下列值的对象:
- height: Option 列表高度值
- width: Option 列表宽度值
- itemSize: 每行 Option 的高度
import React from 'react';
import { Cascader, Checkbox, Typography } from '@douyinfe/semi-ui';
() => {
const treeData = useMemo(() => (
['通用', '场景'].map((label, m) => ({
label: label,
value: m,
children: new Array(100).fill(0).map((item, n)=> ({
value: `${m}-${n}`,
label: `${m}-${n} 第二级`,
children: new Array(20).fill(0).map((item, o)=> ({
value: `${m}-${n}-${o}`,
label: `${m}-${n}-${o} 第三级详细内容`,
})),
}))
}))
), []);
let virtualize = {
// 高度为面板默认高度为 180px 减去上下padding 2 * 8px
height: 172,
width: 320,
itemSize: 36,
};
const filterRender = useCallback((props) => {
const { data, onCheck, checkStatus, className } = props;
return (
<div
key={data.value}
className={className}
style={{ justifyContent: 'start', padding: '8px 16px 8px 12px', boxSizing: 'border-box' }}
>
<Checkbox
onChange={onCheck}
indeterminate={checkStatus.halfChecked}
checked={checkStatus.checked}
style={{ marginRight: 8 }}
/>
<Typography.Text
ellipsis={{ showTooltip: { opts: { style: { wordBreak: 'break-all' } } } }}
style={{ maxWidth: 260 }}
>
{data.map(item => item.label).join(' | ')}
</Typography.Text>
</div>
);
}, []);
return (
<Cascader
multiple
filterTreeNode
style={{ width: 320 }}
treeData={treeData}
placeholder="输入 通用 or 场景 进行搜索"
virtualizeInSearch={virtualize}
filterRender={filterRender}
/>
);
};
限制标签展示数量
version: >= 1.28.0
在多选的场景中,利用 maxTagCount 可以限制展示的标签数量,超出部分将以 +N 的方式展示。
使用 showRestTagsPopover 可以设置在超出 maxTagCount 后,hover +N 是否显示 Popover,默认为 false。并且,还可以在 restTagsPopoverProps 属性中配置 Popover。
import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
multiple
showRestTagsPopover={true}
restTagsPopoverProps={{ position: 'top' }}
maxTagCount={1}
defaultValue={[
['zhejiang', 'ningbo', 'haishu'],
['zhejiang', 'hangzhou', 'xihu']
]}
/>
);
};
限制选中数量
version: >= 1.28.0
在多选的场景中,利用 max 可以限制多选选中的数量。超出 max 后将触发 onExceed 回调。
import React from 'react';
import { Cascader, Toast } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
multiple
max={1}
onExceed={v=>{
Toast.warning('exceed max');
console.log(v);
}}
defaultValue={['zhejiang', 'ningbo', 'haishu']}
/>
);
};
选择即改变
在单选的情况下,还可以通过设置
changeOnSelect
,允许选中父级选项。import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<div>
<Cascader
style={{ width: 300 }}
treeData={treeData}
changeOnSelect
placeholder="选择即改变"
/>
<br/>
<br/>
<Cascader
style={{ width: 300 }}
treeData={treeData}
changeOnSelect
placeholder="可搜索的选择即改变"
filterTreeNode
/>
</div>
);
};
自定义显示
可以通过
displayProp
设置回填选项显示的属性值,默认为 label
。import React from 'react';
import { Cascader, Typography } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<>
<Typography.Title heading={6}>单选</Typography.Title>
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="回填时显示数据的value值"
displayProp='value'
defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
/>
<br />
<br />
<Typography.Title heading={6}>多选</Typography.Title>
<Cascader
multiple
style={{ width: 300 }}
treeData={treeData}
defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
placeholder="回填时显示数据的value值"
displayProp='value'
/>
</>
);
};
可以通过设置
displayRender
可以设定返回格式。单选 (
multiple=false
) 时, displayRender((labelPath: string[]) => ReactNode)
, 其中 labelPath 是由 label 构成的 path 数组。多选 (
multiple=true
) 时, displayRender((item: Entity, index: number) => ReactNode)
, 其中 item 为节点的相关数据。interface Entity { children?: Entity[]; // children list data: CascaderData; // treedata ind: number; // index key: string; // key level: number; // node level parent?: Entity; // parent data parentKey?: string; // parent key path: string[]; // key path valuePath: string[]; // value path }
import React from 'react';
import { Cascader, Tag, Typography } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<>
<Typography.Title heading={6}>单选</Typography.Title>
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="自定义回填时显示数据的格式"
displayRender={list => '已选择:' + list.join(' -> ')}
defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
/>
<br />
<br />
<Typography.Title heading={6}>多选</Typography.Title>
<Cascader
multiple
style={{ width: 300 }}
treeData={treeData}
defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
placeholder="自定义回填时显示数据的格式"
displayRender={(item, idx) => (
<Tag
style={{ marginRight: 4 }}
color='white'
key={`${idx}-${item.data.label}`}
>
{item.data.label}
</Tag>
)}
/>
</>
);
};
自定义分隔符
版本: >=2.2.0
可以使用
separator
设置分隔符, 包括:搜索时显示在下拉框的内容以及单选时回显到 Trigger 的内容的分隔符。import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
filterTreeNode
separator=' > '
/>
);
};
禁用
import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
}
],
}
];
return (
<div>
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
disabled
/>
<br />
<br />
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
defaultValue={['zhejiang', 'hangzhou', 'xihu']}
filterTreeNode
disabled
/>
</div>
);
};
严格禁用
version: >= 1.32.0
可以使用 disableStrictly 来开启严格禁用。开启严格禁用后,当节点是 disabled 的时候,则不能通过子级或者父级的关系改变选中状态。
以下面的 demo 为例,节点"宁波"开启了严格禁用,因此,当我们改变其父节点"浙江省"的选中状态时,也不会影响到节点"宁波"的选中状态。
import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
disabled: true,
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
multiple
placeholder="请选择所在地区"
disableStrictly
/>
);
};
展示子菜单的时机
version: >= 1.29.0
可以使用
showNext
设置展开 Dropdown 子菜单的触发时机,可选: click
(默认)、hover
。import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
}
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
showNext="hover"
/>
);
};
在顶部/底部渲染附加项
我们在级联选择器的顶部、底部分别预留了插槽,你可以通过
topSlot
或 bottomSlot
来设置。import React from 'react';
import { Cascader, Typography } from '@douyinfe/semi-ui';
() => {
const { Text } = Typography;
const slotStyle = {
height: '36px',
display: 'flex',
padding: '0 32px',
alignItems: 'center',
cursor: 'pointer',
borderTop: '1px solid var(--semi-color-border)'
};
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
bottomSlot={
<div style={slotStyle}>
<Text>找不到相关选项?</Text>
<Text link>去新建</Text>
</div>
}
/>
);
};
受控
传入
value
时即为受控组件,可以配合 onChange
使用。import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
class Demo extends React.Component {
constructor() {
super();
this.state = {
value: []
};
}
onChange(value) {
this.setState({ value });
}
render() {
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
value={this.state.value}
onChange={e => this.onChange(e)}
/>
);
}
}
自动合并 value
版本: >=1.28.0
在多选(multiple=true)场景中,当我们选中祖先节点时,如果希望 value 不包含它对应的子孙节点,则可以通过
autoMergeValue
来设置,默认为 true。当 autoMergeValue 和 leafOnly 同时开启时,后者优先级更高。import React, { useState } from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const [value, setValue] = useState([]);
const onChange = value => {
console.log(value);
setValue(value);
};
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="autoMergeValue 为 false"
value={value}
multiple
autoMergeValue={false}
onChange={e => onChange(e)}
/>
);
};
仅叶子节点
版本: >=2.2.0
在多选时,可以通过开启 leafOnly 来设置 value 只包含叶子节点,即显示的 Tag 和 onChange 的参数 value 只包含 value。
import React, { useState } from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const [value, setValue] = useState([]);
const onChange = value => {
console.log(value);
setValue(value);
};
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
];
return (
<Cascader
style={{ width: 300 }}
treeData={treeData}
placeholder="开启 leafOnly"
value={value}
multiple
leafOnly
onChange={e => onChange(e)}
/>
);
};
动态更新数据
import React from 'react';
import { Cascader, Button } from '@douyinfe/semi-ui';
class Demo extends React.Component {
constructor() {
super();
this.state = {
treeData: [],
};
this.add = this.add.bind(this);
}
add() {
let itemLength = Math.floor(Math.random() * 3) + 1;
let treeData = new Array(itemLength).fill(0).map((v, i) => {
let length = Math.floor(Math.random() * 3);
let children = new Array(length).fill(0).map((cv, ci) => {
let child = {
key: `${i}-${ci}`,
label: `Item-${i}-${ci}`,
value: `${i}-${ci}`
};
return child;
});
let item = {
key: `${i}`,
label: `Item-${i}`,
value: `${i}`,
children
};
return item;
});
this.setState({ treeData });
}
render() {
return (
<>
<Cascader
style={{ width: 300 }}
treeData={this.state.treeData}
placeholder="请选择"
/>
<br/>
<br/>
<Button onClick={this.add}>
动态改变数据
</Button>
</>
);
}
}
异步加载数据
可以使用 loadData 实现异步加载数据
v>=1.8.0
不能与搜索同时使用
不能与搜索同时使用
import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const initialData = [
{
label: 'Node1',
value: '0-0',
},
{
label: 'Node2',
value: '0-1',
},
{
label: 'Node3',
value: '0-2',
isLeaf: true
},
];
const [data, setData] = useState(initialData);
const updateTreeData = (list, value, children) => {
return list.map(node => {
if (node.value === value) {
return { ...node, children };
}
if (node.children) {
return { ...node, children: updateTreeData(node.children, value, children) };
}
return node;
});
};
const onLoadData = selectedOpt => {
const targetOpt = selectedOpt[selectedOpt.length - 1];
const { label, value } = targetOpt;
return new Promise(resolve => {
if (targetOpt.children) {
resolve();
return;
}
setTimeout(() => {
setData(origin =>
updateTreeData(origin, value, [
{
label: `${label} - 1`,
value: `${label}-1`,
isLeaf: selectedOpt.length > 1
},
{
label: `${label} - 2`,
value: `${label}-2`,
isLeaf: selectedOpt.length > 1
},
]),
);
resolve();
}, 1000);
});
};
return (
<Cascader
style={{ width: 300 }}
treeData={data}
loadData={onLoadData}
placeholder="Please select"
/>
);
};
超长列表
当你的数据结构层级特别深时,Cascader下拉菜单可能会超出屏幕,此时我们建议为下拉菜单设置 overflow-x: auto 以及一个合适的 width 宽度( 建议以N+0.5列的宽度为准,最右侧显示半列,以给用户一种右侧尚有待展开项,可以水平方向滚动的视觉暗示)
import React from 'react';
import { Cascader } from '@douyinfe/semi-ui';
() => {
const treeData = [
{
label: 'A',
value: 'A',
children: [
{
label: 'B',
value: 'B',
children: [
{
label: 'C',
value: 'C',
children: [
{
label: 'D',
value: 'D',
children: [
{
label: 'E',
value: 'E',
children: [
{
label: 'F',
value: 'F',
}
]
}
]
}
]
}
],
}
]
}
];
return (
<Cascader
dropdownClassName='components-cascader-demo'
style={{ width: 300 }}
treeData={treeData}
placeholder="请选择所在地区"
/>
);
};
.components-cascader-demo { .semi-cascader-option-lists { max-width: 510px; overflow-x: auto; } }
自定义 Trigger
如果默认的触发器样式满足不了你的需求,可以用
triggerRender
自定义选择框的展示triggerRender 入参如下
interface TriggerRenderProps { /* Cascader 的 props */ componentProps: CascaderProps; /* 是否禁用 Cascader */ disabled: boolean; /** * 已选中的 node 在 treeData 中的层级位置,如下例子, * 当选中浙江省-杭州市-萧山区时,此处 value 为 '0-0-1' */ value?: string | Set<string>; /* 当前 Input 框的输入值 */ inputValue: string; /** * 用于更新 input 框值的函数,当你在 triggerRender 自定义的 * Input 组件值更新时,你应该调用该函数,用于向 Cascader 内部 * 同步状态, 使用时需要设置 filterTreeNode 参数非 false */ onSearch: (inputValue: string) => void; /* 用于清空值的函数 */ onClear: () => void; /* Placeholder */ placeholder?: string; /* 用于删除单个 item , 入参为 value */ onRemove: (value) => void }
import React, { useState, useCallback, useMemo } from 'react';
import { Cascader, Button, Tag, TagInput } from '@douyinfe/semi-ui';
import { IconClose, IconChevronDown } from '@douyinfe/semi-icons';
function Demo() {
const treeData = useMemo(() => [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
{
label: '江北区',
value: 'jiangbei',
}
]
},
],
}
], []);
const closeIcon = useCallback((value, onClear) => {
return value ? <IconClose onClick={onClear} /> : <IconChevronDown />;
}, []);
const triggerRenderSingle = ({ value, placeholder, onClear, ...rest }) => {
return (
<Button theme={'light'} icon={closeIcon(value, onClear)} iconPosition={'right'}>
{value && value.length > 0 ? getLabelFromValue(value) : placeholder}
</Button>
);
};
const getLabelFromValue = useCallback((value) => {
const valueArr = value.split('-').map(item => Number(item));
let resultData = treeData;
valueArr.forEach((item, index) => {
resultData = index === 0 ? resultData[item] : resultData.children[item];
});
return resultData.label;
}, [treeData]);
const triggerRenderMultiple = useCallback((props) => {
const { value, onSearch, onRemove } = props;
const onCloseTag = (value, e, tagKey) => {
onRemove(tagKey);
};
const renderTagItem = (value) => {
const label = getLabelFromValue(value);
return <Tag tagKey={value} key={value} closable onClose={onCloseTag} style={{ marginLeft: 2 }}>{label}</Tag>;
};
return (
<TagInput
value={Array.from(value)}
onInputChange={onSearch}
renderTagItem={renderTagItem}
/>
);
}, []);
return (
<>
<Cascader
treeData={treeData}
placeholder='Custom Trigger'
triggerRender={triggerRenderSingle}
/>
<br />
<Cascader
triggerRender={triggerRenderMultiple}
multiple
filterTreeNode
treeData={treeData}
style={{ width: 300 }}
placeholder='Custom Trigger'
/>
</>
);
}
API 参考
Cascader
属性 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
arrowIcon | 自定义右侧下拉箭头 Icon,当 showClear 开关打开且当前有选中值时,hover 会优先显示 clear icon | ReactNode | - | 1.15.0 |
autoAdjustOverflow | 是否自动调整下拉框展开方向,用于边缘遮挡时自动调整展开方向 | boolean | true | - |
autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将不包括该节点的子孙节点。不支持动态切换 | boolean | true | 1.28.0 |
bottomSlot | 底部插槽 | ReactNode | - | 1.27.0 |
borderless | 无边框模式 >=2.33.0 | boolean | ||
changeOnSelect | 是否允许选择非叶子节点 | boolean | false | - |
className | 选择框的 className 属性 | string | - | - |
clearIcon | 可用于自定义清除按钮, showClear为true时有效 | ReactNode | - | 2.25.0 |
defaultOpen | 设置是否默认打开下拉菜单 | boolean | false | - |
defaultValue | 指定默认选中的条目 | string|number|CascaderData|(string|number|CascaderData)[] | - | - |
disabled | 是否禁用 | boolean | false | - |
displayProp | 设置回填选项显示的属性值 | string | label | - |
displayRender | 设置回填格式 | (selected: string[] | Entity, idx?: number) => ReactNode | selected => selected.join('/') | - |
dropdownMargin | 下拉菜单计算溢出时的增加的冗余值,详见issue#549,作用同 Tooltip margin | object|number | - | 2.25.0 |
dropdownClassName | 下拉菜单的 className 属性 | string | - | - |
dropdownStyle | 下拉菜单的样式 | object | - | - |
emptyContent | 当搜索无结果时展示的内容 | ReactNode | 暂无数据 | - |
filterLeafOnly | 搜索结果是否只展示叶子结点路径 | boolean | true | 1.26.0 |
filterRender | 自定义渲染筛选后的选项 | (props: FilterRenderProps) => ReactNode; | - | 2.28.0 |
filterSorter | 对筛选后的选项进行排序 | (first: CascaderData, second: CascaderData, inputValue: string) => number | - | 2.28.0 |
filterTreeNode | 设置筛选,默认用 treeNodeFilterProp 的值作为要筛选的 TreeNode 的属性值, data 参数自 v2.28.0 开始提供 | ((inputValue: string, treeNodeString: string, data?: CascaderData) => boolean) | boolean | false | - |
getPopupContainer | 指定父级 DOM,下拉框将会渲染至该 DOM 中,自定义需要设置 position: relative 这会改变浮层 DOM 树位置,但不会改变视图渲染位置。 | () => HTMLElement | () => document.body | - |
insetLabel | 前缀标签别名,主要用于 Form | ReactNode | - | 0.28.0 |
leafOnly | 多选时设置 value 只包含叶子节点,即显示的 Tag 和 onChange 的 value 参数只包含叶子节点。不支持动态切换 | boolean | false | 2.2.0 |
loadData | 异步加载数据,需要返回一个Promise | (selectOptions: CascaderData[]) => Promise< void > | - | 1.8.0 |
max | 多选时,限制多选选中的数量,超出 max 后将触发 onExceed 回调 | number | - | 1.28.0 |
maxTagCount | 多选时,标签的最大展示数量,超出后将以 +N 形式展示 | number | - | 1.28.0 |
motion | 设置下拉框弹出的动画 | boolean | true | - |
mouseEnterDelay | 鼠标移入后,延迟显示下拉框的时间,单位毫秒 | number | 50 | - |
mouseLeaveDelay | 鼠标移出后,延迟消失下拉框的时间,单位毫秒 | number | 50 | - |
multiple | 设置多选 | boolean | false | 1.28.0 |
placeholder | 选择框默认文字 | string | - | - |
position | 方向,可选值:top ,topLeft ,topRight ,left ,leftTop ,leftBottom ,right ,rightTop ,rightBottom ,bottom ,bottomLeft ,bottomRight | string | bottom | 2.16.0 |
prefix | 前缀标签 | ReactNode | - | 0.28.0 |
preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法 | boolean | - | 2.15.0 |
restTagsPopoverProps | Popover 的配置属性,可以控制 position、zIndex、trigger 等,具体参考Popover | PopoverProps | {} | 1.28.0 |
searchPlaceholder | 搜索框默认文字 | string | - | - |
searchPosition | 设置搜索框的位置,可选: trigger 、custom | string | trigger | 2.54.0 |
separator | 自定义分隔符,包括:搜索时显示在下拉框的内容以及单选时回显到 Trigger 的内容的分隔符 | string | / | 2.2.0 |
showClear | 是否展示清除按钮 | boolean | false | 0.35.0 |
showNext | 设置展开 Dropdown 子菜单的方式,可选: click 、hover | string | click | 1.29.0 |
showRestTagsPopover | 当超过 maxTagCount,hover 到 +N 时,是否通过 Popover 显示剩余内容 | boolean | false | 1.28.0 |
size | 选择框大小,可选 large ,small ,default | string | default | - |
stopPropagation | 是否阻止下拉框上的点击事件冒泡 | boolean | true | - |
disableStrictly | 设置是否开启严格禁用。开启后,当节点是 disabled 的时候,则不能通过子级或者父级的关系改变选中状态 | boolean | false | 1.32.0 |
style | 选择框的样式 | CSSProperties | - | - |
suffix | 后缀标签 | ReactNode | - | 0.28.0 |
topSlot | 顶部插槽 | ReactNode | - | 1.27.0 |
treeData | 展示数据,具体属性参考 CascaderData | CascaderData[] | [] | - |
treeNodeFilterProp | 搜索时输入项过滤对应的 CascaderData 属性 | string | label | - |
triggerRender | 自定义触发器渲染方法 | (props: TriggerRenderProps) => ReactNode | - | 0.34.0 |
validateStatus | trigger 的校验状态,仅影响展示样式。可选: default、error、warning | string | default | - |
value | (受控)选中的条目 | string|number|CascaderData|(string|number|CascaderData)[] | - | - |
virtualizeInSearch | 搜索列表虚拟化,用于大量树节点的情况,由 height, width, itemSize 组成 | Object | - | - |
zIndex | 下拉菜单的 zIndex | number | 1030 | - |
enableLeafClick | 多选时,是否启动点击叶子节点选项触发勾选 | boolean | false | 2.2.0 |
onBlur | 失焦 Cascader 的回调 | (e: MouseEvent) => void | - | - |
onChange | 选中树节点时调用此函数,默认返回选中项 path 的 value 数组 | (value: string|number| CascaderData | (string|number|CascaderData)[]) => void | - |
onChangeWithObject | 是否将选中项 option 的其他属性作为回调。设为 true 时,onChange 的入参类型会从 string/number 变为 TreeNode。此时如果是受控,也需要把 value 设置成 CascaderData 类型,且必须含有 value 的键值,defaultValue 同理 | boolean | false | 1.16.0 |
onClear | showClear 为 true 时,点击清空按钮触发的回调 | () => void | - | 1.29.0 |
onDropdownVisibleChange | 下拉框切换时的回调 | (visible: boolean) => void | - | 0.35.0 |
onExceed | 多选时,超出 max 后触发的回调 | (checkedItem: Entity[]) => void | - | 1.28.0 |
onFocus | 聚焦 Cascader 的回调 | (e: MouseEvent) => void | - | - |
onListScroll | 下拉面板滚动的回调 | (e: React.Event, panel: { panelIndex: number; activeNode: CascaderData; } ) => void | - | 1.15.0 |
onLoad | 节点加载完毕时触发的回调 | (newLoadedKeys: Set< string >, data: CascaderData) => void | - | 1.8.0 |
onSearch | 文本框值变化时回调 | (value: string) => void | - | - |
onSelect | 被选中时调用,返回选中项的 value | (value: string | number | (string | number)[]) => void | - | - |
CascaderData
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
children | 子节点 | CascaderData[] | - |
disabled | 不可选状态 >=0.35.0 | boolean | - |
isLeaf | 叶子节点 | boolean | - |
label | 展示的文本(必填) | ReactNode | - |
loading | 正在加载 | boolean | - |
value | 属性值(必填) | string|number | - |
Methods
绑定在组件实例上的方法,可以通过 ref 调用实现某些特殊交互
方法 | 说明 | 版本 |
---|---|---|
close | 调用时可以手动关闭下拉列表 | v2.30.0 |
open | 调用时可以手动展开下拉列表 | v2.30.0 |
focus | 调用时可以手动聚焦 | v2.34.0 |
blur | 调用时可以手动失焦 | v2.34.0 |
search(value: string) | 手动触发搜索,需同时设置 filterTreeNode 开启搜索,searchPosition 为 custom 自定义展示搜素框 | v2.54.0 |
Accessibility
ARIA
- Cascader 支持传入
aria-label
、aria-describedby
、aria-errormessage
、aria-invalid
、aria-labelledby
、aria-required
来表示该 Cascader 的相关信息; - Cascader 支持通过按下 Enter 键来选中选项、清空选项、展开下拉框
文案规范
- 选择器选项
- 如果没有默认选项,就使用“Select”做占位文案
- 选项要按首字母顺序或者其他有逻辑的排列顺序,使用户更好地找到选项
- 使用语句书写规范(首字母大写,其余小写),避免在句尾使用逗号和分号
- 清晰表达出选项所表示的选择目的