CascaderSelect  级联选择

开发指南#

何时使用#

级联选择由选择器和级联组成。把级联组件以弹层的方式隐藏,多用于表单场景。

API#

import { CascaderSelect } from '@alifd/next';

class Demo extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            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);
    }

    render() {
        return <CascaderSelect style={{ width: '302px' }} dataSource={this.state.data} onChange={this.handleChange} />;
    }
}

ReactDOM.render(<Demo />, mountNode);

展示基本的单选用法。

code collapse
import { Radio, CascaderSelect } 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>

                <CascaderSelect style={{ width: '302px' }} 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;
}

展示可通过 expandTriggerType 来设置不同的展开触发行为,支持 click 和 hover,默认值为 click。

code collapse
import { CascaderSelect } from '@alifd/next';

class Demo extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            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);
    }

    render() {
        return <CascaderSelect style={{ width: '302px' }} multiple dataSource={this.state.data} onChange={this.handleChange} />;
    }
}

ReactDOM.render(<Demo />, mountNode);
.cascader-value {
    width: 500px;
    margin-bottom: 10px;
    font-size: 14px;
    color: #666;
}

展示基本的多选用法。

code collapse
import { Checkbox, CascaderSelect } from '@alifd/next';

class Demo extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            value: null,
            changeOnSelect: 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({
            changeOnSelect: !this.state.changeOnSelect,
            value: null
        });
    }

    handleChange(value, data, extra) {
        console.log(value, data, extra);

        this.setState({
            value
        });
    }

    render() {
        return (
            <div className="control-single-demo">
                <label className="change-check">
                    <Checkbox value={!this.state.changeOnSelect} onChange={this.handleCheck} />
                    <span className="change-text">Enable changeOnSelect</span>
                </label>
                <CascaderSelect style={{ width: '302px' }} changeOnSelect={this.state.changeOnSelect} value={this.state.value} dataSource={this.state.data} onChange={this.handleChange} />
            </div>
        );
    }
}

ReactDOM.render(<Demo />, mountNode);
.control-single-demo .change-check {
    display: block;
    margin-bottom: 10px;
}

.control-single-demo .change-text {
    display: inline-block;
    margin-left: 10px;
    vertical-align: middle;
    color: #666;
    font-size: 14px;
}

展示受控单选以及是否选择即改变。

code collapse
import { Checkbox, CascaderSelect } 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>
                <CascaderSelect style={{ width: '302px' }} 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;
}

展示受控多选以及是否开启严格受控父子节点选中不再关联的用法。

code collapse
import { Checkbox, CascaderSelect } from '@alifd/next';

class Demo extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            value: [],
            data: [],
            multiple: 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({
            multiple: !this.state.multiple,
            value: []
        });
    }

    handleChange(value, data, extra) {
        console.log(value, data, extra);

        this.setState({
            value
        });
    }

    render() {
        return (
            <div className="search-demo">
                <label className="multiple-check">
                    <Checkbox value={this.state.multiple} onChange={this.handleCheck} />
                    <span className="multiple-text">Multiple select</span>
                </label>
                <CascaderSelect style={{ width: '302px' }} showSearch multiple={this.state.multiple} value={this.state.value} dataSource={this.state.data} onChange={this.handleChange} />
            </div>
        );
    }
}

ReactDOM.render(<Demo />, mountNode);
.search-demo .multiple-check {
    display: block;
    margin-bottom: 10px;
}

.search-demo .multiple-text {
    display: inline-block;
    margin-left: 10px;
    vertical-align: middle;
    color: #666;
    font-size: 14px;
}

通过设置 showSearch 为 true,可以开启组件的搜索功能。

code collapse
import { CascaderSelect, Icon } 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: '和静县' }
        ]
    }]
}];

const itemRender = item => {
    return (
        <span>
            <Icon type="account" size="xs" /> {item.label}
        </span>
    );
};

ReactDOM.render(<CascaderSelect style={{ width: '452px'}} listStyle={{ width: '150px', height: '160px' }} displayRender={labels => labels[labels.length - 1]} defaultValue="3431" dataSource={dataSource} itemRender={itemRender} />, mountNode);

可以通过 displayRender 来定制单选时展示的结果,可以通过 listStyle,listClassName 来定制组件宽高。

code collapse
import { CascaderSelect } 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 <CascaderSelect style={{ width: '302px' }} dataSource={this.state.dataSource} loadData={this.onLoadData} />;
    }
}

ReactDOM.render(<Demo />, mountNode);

展示动态获取数据的用法。

code collapse
import { CascaderSelect } from '@alifd/next';

class Demo extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            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);
    }

    valueRender = (item) => {
        if (item.label) {
            return item.label; // 正常的item
        }

        // value在 dataSouce里不存在时渲染。
        return item.value === '432988' ? '不存在的' : item.value;
    };

    render() {
        return <CascaderSelect valueRender={this.valueRender} defaultValue="432988" style={{ width: '302px' }} dataSource={this.state.data} onChange={this.handleChange} />;
    }
}
ReactDOM.render(<Demo />, mountNode);
code collapse
import { CascaderSelect } from '@alifd/next';

const data = [{
    value: '0100',
    label: '金庸',
    children: [
        { value: '0101', label: '飞狐外传' },
        { value: '0102', label: '雪山飞狐' },
        { value: '0103', label: '连城诀' },
        { value: '0104', label: '天龙八部' },
        { value: '0105', label: '射雕英雄传' },
        { value: '0106', label: '白马啸西风' },
        { value: '0107', label: '鹿鼎记' },
        { value: '0108', label: '笑傲江湖' },
        { value: '0109', label: '书剑恩仇录' },
        { value: '0110', label: '神雕侠侣' },
        { value: '0111', label: '侠客行' },
        { value: '0112', label: '倚天屠龙记' },
        { value: '0113', label: '碧血剑' },
        { value: '0114', label: '鸳鸯刀' }
    ]
}, {
    value: '0200',
    label: '古龙',
    children: [
        { value: '0201', label: '小李飞刀' },
        { value: '0202', label: '绝代双骄' },
        { value: '0203', label: '大旗英雄传' },
        { value: '0204', label: '英雄无泪' },
        { value: '0205', label: '边城浪子' },
        { value: '0206', label: '楚留香传奇' },
    ],
}, {
    children: [
        { value: '0301', label: '白发魔女传' },
        { value: '0302', label: '七剑下天山' },
        { value: '0303', label: '云海玉弓缘' }
    ],
    value: '0300',
    label: '梁羽生'
}];

class Demo extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            label: '',
            data: []
        };
        this.handleChange = this.handleChange.bind(this);
    }
    componentDidMount() {
        this.setState({ data });
    }
    handleChange(value, data, extra) {
        console.log(value, data, extra);

        this.setState({
            label: extra.selectedPath.map(d => d.label).join(' / '),       
        });
    }
    render() {
        return (
            <div>
                <div id="a11y-cascader-select" >CascaderSelect: </div>
                <CascaderSelect  dataSource={this.state.data} onChange={this.handleChange} listStyle={{ width: '200px', height: '256px' }} aria-labelledby="a11y-cascader-select"/>
            </div>
        );
    }
}
ReactDOM.render(<Demo />, mountNode);
.next-menu-item:focus {
    background-color: #F2F3F7;
}

当聚焦在组件上时,通过aria-labelledby对组件进行描述。关于键盘操作请参考ARIA and KeyBoard

code collapse

# API

CascaderSelect#

参数 说明 类型 默认值
size 选择框大小

可选值:
'small', 'medium', 'large'
Enum 'medium'
placeholder 选择框占位符 String -
disabled 是否禁用 Boolean false
hasArrow 是否有下拉箭头 Boolean true
hasBorder 是否有边框 Boolean true
hasClear 是否有清除按钮 Boolean false
label 自定义内联 label ReactNode -
readOnly 是否只读,只读模式下可以展开弹层但不能选 Boolean -
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'
useVirtual 是否开启虚拟滚动 Boolean false
multiple 是否多选 Boolean false
changeOnSelect 是否选中即发生改变, 该属性仅在单选模式下有效 Boolean false
canOnlyCheckLeaf 是否只能勾选叶子项的checkbox,该属性仅在多选模式下有效 Boolean false
checkStrictly 父子节点是否选中不关联 Boolean false
listStyle 每列列表样式对象 Object -
listClassName 每列列表类名 String -
displayRender 选择框单选时展示结果的自定义渲染函数

签名:
Function(label: Array) => ReactNode
参数:
label: {Array} 选中路径的文本数组
返回值:
{ReactNode} 渲染在选择框中的内容
Function 单选时:labelPath => labelPath.join(' / ');多选时:labelPath => labelPath[labelPath.length - 1]
itemRender 渲染 item 内容的方法

签名:
Function(item: Object) => ReactNode
参数:
item: {Object} 渲染节点的item
返回值:
{ReactNode} item node
Function -
showSearch 是否显示搜索框 Boolean false
filter 自定义搜索函数

签名:
Function(searchValue: String, path: Array) => Boolean
参数:
searchValue: {String} 搜索的关键字
path: {Array} 节点路径
返回值:
{Boolean} 是否匹配
Function 根据路径所有节点的文本值模糊匹配
resultRender 搜索结果自定义渲染函数

签名:
Function(searchValue: String, path: Array) => ReactNode
参数:
searchValue: {String} 搜索的关键字
path: {Array} 匹配到的节点路径
返回值:
{ReactNode} 渲染的内容
Function 按照节点文本 a / b / c 的模式渲染
resultAutoWidth 搜索结果列表是否和选择框等宽 Boolean true
notFoundContent 无数据时显示内容 ReactNode 'Not Found'
loadData 异步加载数据函数

签名:
Function(data: Object) => void
参数:
data: {Object} 当前点击异步加载的数据
Function -
header 自定义下拉框头部 ReactNode -
footer 自定义下拉框底部 ReactNode -
defaultVisible 初始下拉框是否显示 Boolean false
visible 当前下拉框是否显示 Boolean -
onVisibleChange 下拉框显示或关闭时触发事件的回调函数

签名:
Function(visible: Boolean, type: String) => void
参数:
visible: {Boolean} 是否显示
type: {String} 触发显示关闭的操作类型, fromTrigger 表示由trigger的点击触发; docClick 表示由document的点击触发
Function () => {}
popupStyle 下拉框自定义样式对象 Object -
popupClassName 下拉框样式自定义类名 String -
popupContainer 下拉框挂载的容器节点 any -
popupProps 透传到 Popup 的属性对象 Object {}
followTrigger 是否跟随滚动 Boolean -
isPreview 是否为预览态 Boolean -
renderPreview 预览态模式下渲染的内容

签名:
Function(value: Array) => void
参数:
value: {Array} 选择值 { label: , value:}
Function -

dataSource数据结构#

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: '其它区' }
    ]
}];

数组中 Item 的自定义属性也会被透传到 onChange 函数的 data 参数中。

ARIA and KeyBoard#

按键 说明
Up Arrow 获取同级当前项前一项焦点
Down Arrow 获取同级当前项后一项焦点
Left Arrow 进入当前项的子元素,并获取第一个子元素为焦点
Right Arrow 返回当前项的父元素并获取焦点
Enter 打开目录或选择当前项
Esc 关闭目录
SPACE 选择当前项