Cascader级联选择

级联选择框。

何时使用#

  • 需要从一组相关联的数据集合进行选择,例如省市区,公司层级,事物分类等。

  • 从一个较大的数据集合中进行选择时,用多级分类进行分隔,方便选择。

  • 比起 Select 组件,可以在同一个浮层中完成选择,有较好的体验。

代码演示

Please select

省市区级联。

expand codeexpand code
import { Cascader } from 'sld';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua Men',
          },
        ],
      },
    ],
  },
];

function onChange(value) {
  console.log(value);
}

ReactDOM.render(
  <Cascader options={options} onChange={onChange} placeholder="Please select" />,
  mountNode,
);
Unselect Change city

切换按钮和结果分开。

expand codeexpand code
import { Cascader } from 'sld';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
      },
    ],
  },
];

class CitySwitcher extends React.Component {
  state = {
    text: 'Unselect',
  };

  onChange = (value, selectedOptions) => {
    this.setState({
      text: selectedOptions.map(o => o.label).join(', '),
    });
  };

  render() {
    return (
      <span>
        {this.state.text}
        &nbsp;
        <Cascader options={options} onChange={this.onChange}>
          <a href="#">Change city</a>
        </Cascader>
      </span>
    );
  }
}

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

通过指定 options 里的 disabled 字段。

expand codeexpand code
import { Cascader } from 'sld';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    disabled: true,
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua Men',
          },
        ],
      },
    ],
  },
];

function onChange(value) {
  console.log(value);
}

ReactDOM.render(<Cascader options={options} onChange={onChange} />, mountNode);

一次性选择多个选项。

expand codeexpand code
import { Cascader } from 'sld';

const options = [
  {
    label: 'Light',
    value: 'light',
    children: new Array(20)
      .fill(null)
      .map((_, index) => ({ label: `Number ${index}`, value: index })),
  },
  {
    label: 'Bamboo',
    value: 'bamboo',
    children: [
      {
        label: 'Little',
        value: 'little',
        children: [
          {
            label: 'Toy Fish',
            value: 'fish',
          },
          {
            label: 'Toy Cards',
            value: 'cards',
          },
          {
            label: 'Toy Bird',
            value: 'bird',
          },
        ],
      },
    ],
  },
];

const App = () => {
  const onChange = value => {
    console.log(value);
  };
  return (
    <Cascader
      style={{ width: '100%' }}
      options={options}
      onChange={onChange}
      multiple
      maxTagCount="responsive"
    />
  );
};

ReactDOM.render(<App />, mountNode);
Zhejiang / Hangzhou / West Lake (752100)

例如给最后一项加上邮编链接。

expand codeexpand code
import { Cascader } from 'sld';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
            code: 752100,
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua Men',
            code: 453400,
          },
        ],
      },
    ],
  },
];

function handleAreaClick(e, label, option) {
  e.stopPropagation();
  console.log('clicked', label, option);
}

const displayRender = (labels, selectedOptions) =>
  labels.map((label, i) => {
    const option = selectedOptions[i];
    if (i === labels.length - 1) {
      return (
        <span key={option.value}>
          {label} (<a onClick={e => handleAreaClick(e, label, option)}>{option.code}</a>)
        </span>
      );
    }
    return <span key={option.value}>{label} / </span>;
  });

ReactDOM.render(
  <Cascader
    options={options}
    defaultValue={['zhejiang', 'hangzhou', 'xihu']}
    displayRender={displayRender}
    style={{ width: '100%' }}
  />,
  mountNode,
);

使用 loadData 实现动态加载选项。

注意:loadDatashowSearch 无法一起使用。

expand codeexpand code
import { Cascader } from 'sld';

const optionLists = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    isLeaf: false,
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    isLeaf: false,
  },
];

const LazyOptions = () => {
  const [options, setOptions] = React.useState(optionLists);

  const onChange = (value, selectedOptions) => {
    console.log(value, selectedOptions);
  };

  const loadData = selectedOptions => {
    const targetOption = selectedOptions[selectedOptions.length - 1];
    targetOption.loading = true;

    // load options lazily
    setTimeout(() => {
      targetOption.loading = false;
      targetOption.children = [
        {
          label: `${targetOption.label} Dynamic 1`,
          value: 'dynamic1',
        },
        {
          label: `${targetOption.label} Dynamic 2`,
          value: 'dynamic2',
        },
      ];
      setOptions([...options]);
    }, 1000);
  };

  return <Cascader options={options} loadData={loadData} onChange={onChange} changeOnSelect />;
};

ReactDOM.render(<LazyOptions />, mountNode);
Please select

使用 dropdownRender 对下拉菜单进行自由扩展。

expand codeexpand code
import { Cascader, Divider } from 'sld';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua Men',
          },
        ],
      },
    ],
  },
];

function dropdownRender(menus) {
  return (
    <div>
      {menus}
      <Divider style={{ margin: 0 }} />
      <div style={{ padding: 8 }}>The footer is not very short.</div>
    </div>
  );
}

ReactDOM.render(
  <Cascader options={options} dropdownRender={dropdownRender} placeholder="Please select" />,
  mountNode,
);
Error
Warning multiple

使用 status 为 Cascader 添加状态,可选 error 或者 warning

expand codeexpand code
import { Cascader, Space } from 'sld';

const Validation: React.FC = () => (
  <Space direction="vertical">
    <Cascader status="error" placeholder="Error" />
    <Cascader status="warning" multiple placeholder="Warning multiple" />
  </Space>
);

ReactDOM.render(<Validation />, mountNode);
Zhejiang / Hangzhou / West Lake

默认值通过数组的方式指定。

expand codeexpand code
import { Cascader } from 'sld';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua Men',
          },
        ],
      },
    ],
  },
];

function onChange(value) {
  console.log(value);
}

ReactDOM.render(
  <Cascader
    defaultValue={['zhejiang', 'hangzhou', 'xihu']}
    options={options}
    onChange={onChange}
  />,
  mountNode,
);

通过移入展开下级菜单,点击完成选择。

expand codeexpand code
import { Cascader } from 'sld';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua Men',
          },
        ],
      },
    ],
  },
];

function onChange(value) {
  console.log(value);
}

// Just show the latest item.
function displayRender(label) {
  return label[label.length - 1];
}

ReactDOM.render(
  <Cascader
    options={options}
    expandTrigger="hover"
    displayRender={displayRender}
    onChange={onChange}
  />,
  mountNode,
);

这种交互允许只选中父级选项。

expand codeexpand code
import { Cascader } from 'sld';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hanzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua Men',
          },
        ],
      },
    ],
  },
];

function onChange(value) {
  console.log(value);
}

ReactDOM.render(<Cascader options={options} onChange={onChange} changeOnSelect />, mountNode);






不同大小的级联选择器。

expand codeexpand code
import { Cascader } from 'sld';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua Men',
          },
        ],
      },
    ],
  },
];

function onChange(value) {
  console.log(value);
}

ReactDOM.render(
  <>
    <Cascader size="large" options={options} onChange={onChange} />
    <br />
    <br />
    <Cascader options={options} onChange={onChange} />
    <br />
    <br />
    <Cascader size="small" options={options} onChange={onChange} />
    <br />
    <br />
  </>,
  mountNode,
);
Please select

自定义字段名。

expand codeexpand code
import { Cascader } from 'sld';

const options = [
  {
    code: 'zhejiang',
    name: 'Zhejiang',
    items: [
      {
        code: 'hangzhou',
        name: 'Hangzhou',
        items: [
          {
            code: 'xihu',
            name: 'West Lake',
          },
        ],
      },
    ],
  },
  {
    code: 'jiangsu',
    name: 'Jiangsu',
    items: [
      {
        code: 'nanjing',
        name: 'Nanjing',
        items: [
          {
            code: 'zhonghuamen',
            name: 'Zhong Hua Men',
          },
        ],
      },
    ],
  },
];

function onChange(value) {
  console.log(value);
}

ReactDOM.render(
  <Cascader
    fieldNames={{ label: 'name', value: 'code', children: 'items' }}
    options={options}
    onChange={onChange}
    placeholder="Please select"
  />,
  mountNode,
);


Please select

可以通过 placement 手动指定弹出的位置。

expand codeexpand code
import { Cascader, Radio } from 'sld';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua Men',
          },
        ],
      },
    ],
  },
];

const SetPlacementDemo = () => {
  const [placement, SetPlacement] = React.useState('topLeft');

  const placementChange = e => {
    SetPlacement(e.target.value);
  };

  return (
    <>
      <Radio.Group value={placement} onChange={placementChange}>
        <Radio.Button value="topLeft">topLeft</Radio.Button>
        <Radio.Button value="topRight">topRight</Radio.Button>
        <Radio.Button value="bottomLeft">bottomLeft</Radio.Button>
        <Radio.Button value="bottomRight">bottomRight</Radio.Button>
      </Radio.Group>
      <br />
      <br />
      <Cascader options={options} placeholder="Please select" placement={placement} />
    </>
  );
};

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

API#

<Cascader options={options} onChange={onChange} />
参数说明类型默认值版本
allowClear是否支持清除booleantrue
autoFocus自动获取焦点booleanfalse
bordered是否有边框booleantrue
changeOnSelect(单选时生效)当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示booleanfalse
className自定义类名string-
defaultValue默认的选中项string[] | number[][]
disabled禁用booleanfalse
displayRender选择后展示的渲染函数(label, selectedOptions) => ReactNodelabel => label.join(/)-
dropdownClassName自定义浮层类名string--
dropdownRender自定义下拉框内容(menus: ReactNode) => ReactNode--
expandIcon自定义次级菜单展开图标ReactNode--
expandTrigger次级菜单的展开方式,可选 'click' 和 'hover'stringclick
fieldNames自定义 options 中 label value children 的字段object{ label: label, value: value, children: children }
getPopupContainer菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。function(triggerNode)() => document.body
loadData用于动态加载选项,无法与 showSearch 一起使用(selectedOptions) => void-
maxTagCount最多显示多少个 tag,响应式模式会对性能产生损耗number | responsive--
maxTagPlaceholder隐藏 tag 时显示的内容ReactNode | function(omittedValues)--
notFoundContent当下拉列表为空时显示的内容stringNot Found
open控制浮层显隐boolean--
options可选项数据源Option[]-
placeholder输入框占位文本string请选择
placement浮层预设位置bottomLeft bottomRight topLeft topRightbottomLeft-
showSearch在选择框中显示搜索框boolean | Objectfalse
size输入框大小large | middle | small-
status设置校验状态'error' | 'warning'--
style自定义样式CSSProperties-
suffixIcon自定义的选择框后缀图标ReactNode-
tagRender自定义 tag 内容,多选时生效(props) => ReactNode--
value指定选中项string[] | number[]-
onChange选择完成后的回调(value, selectedOptions) => void-
onDropdownVisibleChange显示/隐藏浮层的回调(value) => void--
multiple支持多选节点boolean--
searchValue设置搜索的值,需要与 showSearch 配合使用string--
onSearch监听搜索,返回输入的值(search: string) => void--
dropdownMenuColumnStyle下拉菜单列的样式CSSProperties-

showSearch#

showSearch 为对象时,其中的字段:

参数说明类型默认值版本
filter接收 inputValue path 两个参数,当 path 符合筛选条件时,应返回 true,反之则返回 falsefunction(inputValue, path): boolean-
limit搜索结果展示数量number | false50
matchInputWidth搜索结果列表是否与输入框同宽-booleantrue
render用于渲染 filter 后的选项function(inputValue, path): ReactNode-
sort用于排序 filter 后的选项function(a, b, inputValue)-

Option#

interface Option {
  value: string | number;
  label?: React.ReactNode;
  disabled?: boolean;
  children?: Option[];
  // 标记是否为叶子节点,设置了 `loadData` 时有效
  // 设为 `false` 时会强制标记为父节点,即使当前节点没有 children,也会显示展开图标
  isLeaf?: boolean;
}

方法#

名称描述版本
blur()移除焦点
focus()获取焦点

注意,如果需要获得中国省市区数据,可以参考 china-division