Cascader 级联
如果项目中使用的是 0.x 版本的基础组件(@icedesign/base, @ali/ice, @alife/next),请在左侧导航顶部切换组件版本。
安装方法
- 在命令行中执行以下命令
npm install @alifd/next@latest -S
开发指南
何时使用
适用于从一组具有关联性的数据集合中进行选择的交互方式。
由于子集目录隐藏,级联是一种节约屏幕空间的有效方法。
级别数因业务需求而定,建议不超过5级。
级联多用于表单场景,可以独立在页面中使用,也可以与其他元素组合使用,如级联选择。
API
Cascader
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
dataSource | 数据源,结构可参考下方说明 | Array<Object> | [] |
defaultValue | (非受控)默认值 | String/Array<String> | null |
value | (受控)当前值 | String/Array<String> | - |
onChange | 选中值改变时触发的回调函数签名:Function(value: String/Array, data: Object/Array, extra: Object) => void参数:value: {String/Array} 选中的值,单选时返回单个值,多选时返回数组data: {Object/Array} 选中的数据,包括 value 和 label,单选时返回单个值,多选时返回数组,父子节点选中关联时,同时选中,只返回父节点extra: {Object} 额外参数extra.selectedPath: {Array} 单选时选中的数据的路径extra.checked: {Boolean} 多选时当前的操作是选中还是取消选中extra.currentData: {Object} 多选时当前操作的数据extra.checkedData: {Array} 多选时所有被选中的数据extra.indeterminateData: {Array} 多选时半选的数据 | Function | - |
defaultExpandedValue | (非受控)默认展开值,如果不设置,组件内部会根据 defaultValue/value 进行自动设置 | Array<String> | - |
expandedValue | (受控)当前展开值 | Array<String> | - |
expandTriggerType | 展开触发的方式可选值:'click', 'hover' | Enum | 'click' |
onExpand | 展开时触发的回调函数签名:Function(expandedValue: Array) => void参数:expandedValue: {Array} 各列展开值的数组 | Function | - |
useVirtual | 是否开启虚拟滚动 | Boolean | false |
multiple | 是否多选 | Boolean | false |
canOnlySelectLeaf | 单选时是否只能选中叶子节点 | Boolean | false |
canOnlyCheckLeaf | 多选时是否只能选中叶子节点 | Boolean | false |
checkStrictly | 父子节点是否选中不关联 | Boolean | false |
listStyle | 每列列表样式对象 | Object | - |
listClassName | 每列列表类名 | String | - |
itemRender | 每列列表项渲染函数签名:Function(data: Object) => ReactNode参数:data: {Object} 数据返回值:{ReactNode} 列表项内容 | Function | item => item.label |
loadData | 异步加载数据函数签名:Function(data: Object, source: Object) => void参数:data: {Object} 当前点击异步加载的数据source: {Object} 当前点击数据 | Function | - |
数组中 Item 的自定义属性也会被透传到 onChange 函数的 data 参数中。<!— api-extra-end —>## ARIA and KeyBoard#
const dataSource = [{ value: '2974', label: '西安', children: [ { value: '2975', label: '西安市', disabled: true }, { value: '2976', label: '高陵县', checkboxDisabled: true }, { value: '2977', label: '蓝田县' }, { value: '2978', label: '户县' }, { value: '2979', label: '周至县' }, { value: '4208', label: '灞桥区' }, { value: '4209', label: '长安区' }, { value: '4210', label: '莲湖区' }, { value: '4211', label: '临潼区' }, { value: '4212', label: '未央区' }, { value: '4213', label: '新城区' }, { value: '4214', label: '阎良区' }, { value: '4215', label: '雁塔区' }, { value: '4388', label: '碑林区' }, { value: '610127', label: '其它区' } ]}];
按键 | 说明 |
---|---|
Left Arrow | 获取同级当前项前一项焦点 |
Right Arrow | 获取同级当前项后一项焦点 |
Tab | 进入当前项的子元素,并获取第一个子元素为焦点 |
Esc | 返回当前项的父元素并获取焦点 |
SPACE | 选择当前项 |
代码示例
展示基本的单选用法。
查看源码在线预览
import { Cascader } from '@alifd/next';
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
label: '',
data: []
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
fetch('https://os.alipayobjects.com/rmsportal/ODDwqcDFTLAguOvWEolX.json')
.then(response => response.json())
.then(data => {
data[1].disabled = true;
this.setState({ data });
})
.catch(e => console.log(e));
}
handleChange(value, data, extra) {
console.log(value, data, extra);
this.setState({
label: extra.selectedPath.map(d => d.label).join(' / ')
});
}
render() {
return (
<div>
<div className="cascader-value">Select: {this.state.label}</div>
<Cascader dataSource={this.state.data} onChange={this.handleChange} />
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
.cascader-value {
margin-bottom: 10px;
font-size: 14px;
color: #666;
}
展示可通过expandTriggerType
来设置不同的展开触发行为,支持click
和hover
,默认值为click
。
查看源码在线预览
import { Radio, Cascader } from '@alifd/next';
const RadioGroup = Radio.Group;
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
triggerType: 'click',
data: []
};
this.handleChange = this.handleChange.bind(this);
this.handleTriggerTypeChange = this.handleTriggerTypeChange.bind(this);
}
componentDidMount() {
fetch('https://os.alipayobjects.com/rmsportal/ODDwqcDFTLAguOvWEolX.json')
.then(response => response.json())
.then(data => this.setState({ data }))
.catch(e => console.log(e));
}
handleChange(value, data, extra) {
console.log(value, data, extra);
}
handleTriggerTypeChange(triggerType) {
this.setState({
triggerType
});
}
render() {
return (
<div>
<div className="trigger-check">
Expand trigger type:
<RadioGroup dataSource={['click', 'hover']} value={this.state.triggerType} onChange={this.handleTriggerTypeChange} />
</div>
<Cascader expandTriggerType={this.state.triggerType} dataSource={this.state.data} onChange={this.handleChange} />
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
.trigger-check {
margin-bottom: 10px;
font-size: 14px;
color: #666;
}
.trigger-check .next-radio-group {
margin-left: 10px;
}
展示基本的多选用法。
查看源码在线预览
import { Cascader } from '@alifd/next';
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
label: '',
data: []
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
fetch('https://os.alipayobjects.com/rmsportal/ODDwqcDFTLAguOvWEolX.json')
.then(response => response.json())
.then(data => {
data[1].disabled = true;
data[2].checkboxDisabled = true;
this.setState({ data });
})
.catch(e => console.log(e));
}
handleChange(value, data, extra) {
console.log(value, data, extra);
this.setState({
label: data.map(d => d.label).join(', ')
});
}
render() {
return (
<div>
<div className="cascader-value">Select: {this.state.label}</div>
<Cascader multiple dataSource={this.state.data} onChange={this.handleChange} />
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
.cascader-value {
width: 500px;
margin-bottom: 10px;
font-size: 14px;
color: #666;
}
展示受控单选以及是否只能选择叶子项的用法。
查看源码在线预览
import { Checkbox, Cascader } from '@alifd/next';
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
canOnlySelectLeaf: false,
data: []
};
this.handleCheck = this.handleCheck.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
fetch('https://os.alipayobjects.com/rmsportal/ODDwqcDFTLAguOvWEolX.json')
.then(response => response.json())
.then(data => this.setState({ data, value: '2975' }))
.catch(e => console.log(e));
}
handleCheck() {
this.setState({
canOnlySelectLeaf: !this.state.canOnlySelectLeaf,
value: null
});
}
handleChange(value, data, extra) {
console.log(value, data, extra);
this.setState({
value
});
}
render() {
return (
<div className="control-single-demo">
<label className="leaf-check">
<Checkbox value={this.state.canOnlySelectLeaf} onChange={this.handleCheck} />
<span className="leaf-text">Enable canOnlySelectLeaf</span>
</label>
<Cascader canOnlySelectLeaf={this.state.canOnlySelectLeaf} value={this.state.value} dataSource={this.state.data} onChange={this.handleChange} />
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
.control-single-demo .leaf-check {
display: block;
margin-bottom: 10px;
}
.control-single-demo .leaf-text {
display: inline-block;
margin-left: 10px;
vertical-align: middle;
color: #666;
font-size: 14px;
}
展示受控多选以及是否开启严格受控父子节点选中不再关联的用法。
查看源码在线预览
import { Checkbox, Cascader } from '@alifd/next';
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
value: [],
data: [],
checkStrictly: false
};
this.handleCheck = this.handleCheck.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
fetch('https://os.alipayobjects.com/rmsportal/ODDwqcDFTLAguOvWEolX.json')
.then(response => response.json())
.then(data => this.setState({ data, value: ['2975'] }))
.catch(e => console.log(e));
}
handleCheck() {
this.setState({
checkStrictly: !this.state.checkStrictly,
value: []
});
}
handleChange(value, data, extra) {
console.log(value, data, extra);
this.setState({
value
});
}
render() {
return (
<div className="control-multiple-demo">
<label className="strictly-check">
<Checkbox value={this.state.checkStrictly} onChange={this.handleCheck} />
<span className="strictly-text">Enable checkStrictly</span>
</label>
<Cascader multiple checkStrictly={this.state.checkStrictly} value={this.state.value} dataSource={this.state.data} onChange={this.handleChange} />
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
.control-multiple-demo .strictly-check {
display: block;
margin-bottom: 10px;
}
.control-multiple-demo .strictly-text {
display: inline-block;
margin-left: 10px;
vertical-align: middle;
color: #666;
font-size: 14px;
}
可以通过listStyle
,listClassName
来定制组件宽高。
查看源码在线预览
import { Cascader } from '@alifd/next';
const dataSource = [{
value: '2973',
label: '陕西',
children: [{
value: '2974',
label: '西安',
children: [
{ value: '2975', label: '西安市' },
{ value: '2976', label: '高陵县' }
]
}, {
value: '2980',
label: '铜川',
children: [
{ value: '2981', label: '铜川市' },
{ value: '2982', label: '宜君县' }
]
}]
}, {
value: '3371',
label: '新疆',
children: [{
value: '3430',
label: '巴音郭楞蒙古自治州',
children: [
{ value: '3431', label: '库尔勒市' },
{ value: '3432', label: '和静县' }
]
}]
}];
ReactDOM.render(<Cascader defaultValue="3439" defaultExpandedValue={['3371', '3430']} listStyle={{ width: '200px', height: '256px' }} dataSource={dataSource} />, mountNode);
展示动态获取数据的用法。
查看源码在线预览
import { Cascader } from '@alifd/next';
const dataSource = [{
value: '2973',
label: '陕西'
}];
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource
};
this.onLoadData = this.onLoadData.bind(this);
}
onLoadData(data) {
console.log(data);
return new Promise(resolve => {
setTimeout(() => {
this.setState({
dataSource: [{
value: '2973',
label: '陕西',
children: [{
value: '2974',
label: '西安',
children: [
{ value: '2975', label: '西安市', isLeaf: true },
{ value: '2976', label: '高陵县', isLeaf: true }
]
}, {
value: '2980',
label: '铜川',
children: [
{ value: '2981', label: '铜川市', isLeaf: true },
{ value: '2982', label: '宜君县', isLeaf: true }
]
}]
}]
}, resolve);
}, 500);
});
}
render() {
return <Cascader canOnlySelectLeaf dataSource={this.state.dataSource} loadData={this.onLoadData} />;
}
}
ReactDOM.render(<Demo />, mountNode);
.cascader-value {
margin-bottom: 10px;
font-size: 14px;
color: #666;
}
展示动态获取数据的用法。
查看源码在线预览
import { Cascader } from '@alifd/next';
const dataSource = [{
value: '2974',
label: '西安'
}, {
value: '2980',
label: '铜川'
}];
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource
};
this.onLoadData = this.onLoadData.bind(this);
}
onLoadData(data, source) {
console.log(data, source);
return new Promise(resolve => {
source.children = source.value === '2974' ? [
{ value: '2975', label: '西安市', isLeaf: true },
{ value: '2976', label: '高陵县', isLeaf: true }
] : [
{ value: '2981', label: '铜川市', isLeaf: true },
{ value: '2982', label: '宜君县', isLeaf: true }
];
setTimeout(() => {
this.setState({
dataSource: this.state.dataSource
}, resolve);
}, 500);
});
}
render() {
return <Cascader canOnlySelectLeaf dataSource={this.state.dataSource} loadData={this.onLoadData} />;
}
}
ReactDOM.render(<Demo />, mountNode);
.cascader-value {
margin-bottom: 10px;
font-size: 14px;
color: #666;
}