• Transfer穿梭框
    • 何时使用
    • 代码演示
    • API
      • Transfer
      • Render Props
        • 参考示例
    • 注意

    Transfer穿梭框

    双栏穿梭选择框。

    何时使用

    • 需要在多个可选项中进行多选时。

    • 比起 Select 和 TreeSelect,穿梭框占据更大的空间,可以展示可选项的更多信息。

    穿梭选择框用直观的方式在两栏中移动元素,完成选择行为。

    选择一个或以上的选项后,点击对应的方向键,可以把选中的选项移动到另一栏。其中,左边一栏为 source,右边一栏为 target,API 的设计也反映了这两个概念。

    代码演示

    Transfer 穿梭框 - 图1

    基本用法

    最基本的用法,展示了 dataSourcetargetKeys、每行的渲染函数 render 以及回调函数 onChange onSelectChange onScroll 的用法。

    1. import { Transfer, Switch } from 'antd';
    2. const mockData = [];
    3. for (let i = 0; i < 20; i++) {
    4. mockData.push({
    5. key: i.toString(),
    6. title: `content${i + 1}`,
    7. description: `description of content${i + 1}`,
    8. disabled: i % 3 < 1,
    9. });
    10. }
    11. const oriTargetKeys = mockData.filter(item => +item.key % 3 > 1).map(item => item.key);
    12. class App extends React.Component {
    13. state = {
    14. targetKeys: oriTargetKeys,
    15. selectedKeys: [],
    16. disabled: false,
    17. };
    18. handleChange = (nextTargetKeys, direction, moveKeys) => {
    19. this.setState({ targetKeys: nextTargetKeys });
    20. console.log('targetKeys: ', nextTargetKeys);
    21. console.log('direction: ', direction);
    22. console.log('moveKeys: ', moveKeys);
    23. };
    24. handleSelectChange = (sourceSelectedKeys, targetSelectedKeys) => {
    25. this.setState({ selectedKeys: [...sourceSelectedKeys, ...targetSelectedKeys] });
    26. console.log('sourceSelectedKeys: ', sourceSelectedKeys);
    27. console.log('targetSelectedKeys: ', targetSelectedKeys);
    28. };
    29. handleScroll = (direction, e) => {
    30. console.log('direction:', direction);
    31. console.log('target:', e.target);
    32. };
    33. handleDisable = disabled => {
    34. this.setState({ disabled });
    35. };
    36. render() {
    37. const { targetKeys, selectedKeys, disabled } = this.state;
    38. return (
    39. <div>
    40. <Transfer
    41. dataSource={mockData}
    42. titles={['Source', 'Target']}
    43. targetKeys={targetKeys}
    44. selectedKeys={selectedKeys}
    45. onChange={this.handleChange}
    46. onSelectChange={this.handleSelectChange}
    47. onScroll={this.handleScroll}
    48. render={item => item.title}
    49. disabled={disabled}
    50. />
    51. <Switch
    52. unCheckedChildren="disabled"
    53. checkedChildren="disabled"
    54. checked={disabled}
    55. onChange={this.handleDisable}
    56. style={{ marginTop: 16 }}
    57. />
    58. </div>
    59. );
    60. }
    61. }
    62. ReactDOM.render(<App />, mountNode);

    Transfer 穿梭框 - 图2

    带搜索框

    带搜索框的穿梭框,可以自定义搜索函数。

    1. import { Transfer } from 'antd';
    2. class App extends React.Component {
    3. state = {
    4. mockData: [],
    5. targetKeys: [],
    6. };
    7. componentDidMount() {
    8. this.getMock();
    9. }
    10. getMock = () => {
    11. const targetKeys = [];
    12. const mockData = [];
    13. for (let i = 0; i < 20; i++) {
    14. const data = {
    15. key: i.toString(),
    16. title: `content${i + 1}`,
    17. description: `description of content${i + 1}`,
    18. chosen: Math.random() * 2 > 1,
    19. };
    20. if (data.chosen) {
    21. targetKeys.push(data.key);
    22. }
    23. mockData.push(data);
    24. }
    25. this.setState({ mockData, targetKeys });
    26. };
    27. filterOption = (inputValue, option) => option.description.indexOf(inputValue) > -1;
    28. handleChange = targetKeys => {
    29. this.setState({ targetKeys });
    30. };
    31. handleSearch = (dir, value) => {
    32. console.log('search:', dir, value);
    33. };
    34. render() {
    35. return (
    36. <Transfer
    37. dataSource={this.state.mockData}
    38. showSearch
    39. filterOption={this.filterOption}
    40. targetKeys={this.state.targetKeys}
    41. onChange={this.handleChange}
    42. onSearch={this.handleSearch}
    43. render={item => item.title}
    44. />
    45. );
    46. }
    47. }
    48. ReactDOM.render(<App />, mountNode);

    Transfer 穿梭框 - 图3

    高级用法

    穿梭框高级用法,可配置操作文案,可定制宽高,可对底部进行自定义渲染。

    1. import { Transfer, Button } from 'antd';
    2. class App extends React.Component {
    3. state = {
    4. mockData: [],
    5. targetKeys: [],
    6. };
    7. componentDidMount() {
    8. this.getMock();
    9. }
    10. getMock = () => {
    11. const targetKeys = [];
    12. const mockData = [];
    13. for (let i = 0; i < 20; i++) {
    14. const data = {
    15. key: i.toString(),
    16. title: `content${i + 1}`,
    17. description: `description of content${i + 1}`,
    18. chosen: Math.random() * 2 > 1,
    19. };
    20. if (data.chosen) {
    21. targetKeys.push(data.key);
    22. }
    23. mockData.push(data);
    24. }
    25. this.setState({ mockData, targetKeys });
    26. };
    27. handleChange = targetKeys => {
    28. this.setState({ targetKeys });
    29. };
    30. renderFooter = () => (
    31. <Button size="small" style={{ float: 'right', margin: 5 }} onClick={this.getMock}>
    32. reload
    33. </Button>
    34. );
    35. render() {
    36. return (
    37. <Transfer
    38. dataSource={this.state.mockData}
    39. showSearch
    40. listStyle={{
    41. width: 250,
    42. height: 300,
    43. }}
    44. operations={['to right', 'to left']}
    45. targetKeys={this.state.targetKeys}
    46. onChange={this.handleChange}
    47. render={item => `${item.title}-${item.description}`}
    48. footer={this.renderFooter}
    49. />
    50. );
    51. }
    52. }
    53. ReactDOM.render(<App />, mountNode);

    Transfer 穿梭框 - 图4

    自定义渲染行数据

    自定义渲染每一个 Transfer Item,可用于渲染复杂数据。

    1. import { Transfer } from 'antd';
    2. class App extends React.Component {
    3. state = {
    4. mockData: [],
    5. targetKeys: [],
    6. };
    7. componentDidMount() {
    8. this.getMock();
    9. }
    10. getMock = () => {
    11. const targetKeys = [];
    12. const mockData = [];
    13. for (let i = 0; i < 20; i++) {
    14. const data = {
    15. key: i.toString(),
    16. title: `content${i + 1}`,
    17. description: `description of content${i + 1}`,
    18. chosen: Math.random() * 2 > 1,
    19. };
    20. if (data.chosen) {
    21. targetKeys.push(data.key);
    22. }
    23. mockData.push(data);
    24. }
    25. this.setState({ mockData, targetKeys });
    26. };
    27. handleChange = (targetKeys, direction, moveKeys) => {
    28. console.log(targetKeys, direction, moveKeys);
    29. this.setState({ targetKeys });
    30. };
    31. renderItem = item => {
    32. const customLabel = (
    33. <span className="custom-item">
    34. {item.title} - {item.description}
    35. </span>
    36. );
    37. return {
    38. label: customLabel, // for displayed item
    39. value: item.title, // for title and filter matching
    40. };
    41. };
    42. render() {
    43. return (
    44. <Transfer
    45. dataSource={this.state.mockData}
    46. listStyle={{
    47. width: 300,
    48. height: 300,
    49. }}
    50. targetKeys={this.state.targetKeys}
    51. onChange={this.handleChange}
    52. render={this.renderItem}
    53. />
    54. );
    55. }
    56. }
    57. ReactDOM.render(<App />, mountNode);

    Transfer 穿梭框 - 图5

    表格穿梭框

    使用 Table 组件作为自定义渲染列表。

    1. import { Transfer, Switch, Table, Tag } from 'antd';
    2. import difference from 'lodash/difference';
    3. // Customize Table Transfer
    4. const TableTransfer = ({ leftColumns, rightColumns, ...restProps }) => (
    5. <Transfer {...restProps} showSelectAll={false}>
    6. {({
    7. direction,
    8. filteredItems,
    9. onItemSelectAll,
    10. onItemSelect,
    11. selectedKeys: listSelectedKeys,
    12. disabled: listDisabled,
    13. }) => {
    14. const columns = direction === 'left' ? leftColumns : rightColumns;
    15. const rowSelection = {
    16. getCheckboxProps: item => ({ disabled: listDisabled || item.disabled }),
    17. onSelectAll(selected, selectedRows) {
    18. const treeSelectedKeys = selectedRows
    19. .filter(item => !item.disabled)
    20. .map(({ key }) => key);
    21. const diffKeys = selected
    22. ? difference(treeSelectedKeys, listSelectedKeys)
    23. : difference(listSelectedKeys, treeSelectedKeys);
    24. onItemSelectAll(diffKeys, selected);
    25. },
    26. onSelect({ key }, selected) {
    27. onItemSelect(key, selected);
    28. },
    29. selectedRowKeys: listSelectedKeys,
    30. };
    31. return (
    32. <Table
    33. rowSelection={rowSelection}
    34. columns={columns}
    35. dataSource={filteredItems}
    36. size="small"
    37. style={{ pointerEvents: listDisabled ? 'none' : null }}
    38. onRow={({ key, disabled: itemDisabled }) => ({
    39. onClick: () => {
    40. if (itemDisabled || listDisabled) return;
    41. onItemSelect(key, !listSelectedKeys.includes(key));
    42. },
    43. })}
    44. />
    45. );
    46. }}
    47. </Transfer>
    48. );
    49. const mockTags = ['cat', 'dog', 'bird'];
    50. const mockData = [];
    51. for (let i = 0; i < 20; i++) {
    52. mockData.push({
    53. key: i.toString(),
    54. title: `content${i + 1}`,
    55. description: `description of content${i + 1}`,
    56. disabled: i % 4 === 0,
    57. tag: mockTags[i % 3],
    58. });
    59. }
    60. const originTargetKeys = mockData.filter(item => +item.key % 3 > 1).map(item => item.key);
    61. const leftTableColumns = [
    62. {
    63. dataIndex: 'title',
    64. title: 'Name',
    65. },
    66. {
    67. dataIndex: 'tag',
    68. title: 'Tag',
    69. render: tag => <Tag>{tag}</Tag>,
    70. },
    71. {
    72. dataIndex: 'description',
    73. title: 'Description',
    74. },
    75. ];
    76. const rightTableColumns = [
    77. {
    78. dataIndex: 'title',
    79. title: 'Name',
    80. },
    81. ];
    82. class App extends React.Component {
    83. state = {
    84. targetKeys: originTargetKeys,
    85. disabled: false,
    86. showSearch: false,
    87. };
    88. onChange = nextTargetKeys => {
    89. this.setState({ targetKeys: nextTargetKeys });
    90. };
    91. triggerDisable = disabled => {
    92. this.setState({ disabled });
    93. };
    94. triggerShowSearch = showSearch => {
    95. this.setState({ showSearch });
    96. };
    97. render() {
    98. const { targetKeys, disabled, showSearch } = this.state;
    99. return (
    100. <div>
    101. <TableTransfer
    102. dataSource={mockData}
    103. targetKeys={targetKeys}
    104. disabled={disabled}
    105. showSearch={showSearch}
    106. onChange={this.onChange}
    107. filterOption={(inputValue, item) =>
    108. item.title.indexOf(inputValue) !== -1 || item.tag.indexOf(inputValue) !== -1
    109. }
    110. leftColumns={leftTableColumns}
    111. rightColumns={rightTableColumns}
    112. />
    113. <Switch
    114. unCheckedChildren="disabled"
    115. checkedChildren="disabled"
    116. checked={disabled}
    117. onChange={this.triggerDisable}
    118. style={{ marginTop: 16 }}
    119. />
    120. <Switch
    121. unCheckedChildren="showSearch"
    122. checkedChildren="showSearch"
    123. checked={showSearch}
    124. onChange={this.triggerShowSearch}
    125. style={{ marginTop: 16 }}
    126. />
    127. </div>
    128. );
    129. }
    130. }
    131. ReactDOM.render(<App />, mountNode);

    Transfer 穿梭框 - 图6

    树穿梭框

    使用 Tree 组件作为自定义渲染列表。

    1. import { Transfer, Tree } from 'antd';
    2. const { TreeNode } = Tree;
    3. // Customize Table Transfer
    4. const isChecked = (selectedKeys, eventKey) => {
    5. return selectedKeys.indexOf(eventKey) !== -1;
    6. };
    7. const generateTree = (treeNodes = [], checkedKeys = []) => {
    8. return treeNodes.map(({ children, ...props }) => (
    9. <TreeNode {...props} disabled={checkedKeys.includes(props.key)}>
    10. {generateTree(children, checkedKeys)}
    11. </TreeNode>
    12. ));
    13. };
    14. const TreeTransfer = ({ dataSource, targetKeys, ...restProps }) => {
    15. const transferDataSource = [];
    16. function flatten(list = []) {
    17. list.forEach(item => {
    18. transferDataSource.push(item);
    19. flatten(item.children);
    20. });
    21. }
    22. flatten(dataSource);
    23. return (
    24. <Transfer
    25. {...restProps}
    26. targetKeys={targetKeys}
    27. dataSource={transferDataSource}
    28. className="tree-transfer"
    29. render={item => item.title}
    30. showSelectAll={false}
    31. >
    32. {({ direction, onItemSelect, selectedKeys }) => {
    33. if (direction === 'left') {
    34. const checkedKeys = [...selectedKeys, ...targetKeys];
    35. return (
    36. <Tree
    37. blockNode
    38. checkable
    39. checkStrictly
    40. defaultExpandAll
    41. checkedKeys={checkedKeys}
    42. onCheck={(
    43. _,
    44. {
    45. node: {
    46. props: { eventKey },
    47. },
    48. },
    49. ) => {
    50. onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
    51. }}
    52. onSelect={(
    53. _,
    54. {
    55. node: {
    56. props: { eventKey },
    57. },
    58. },
    59. ) => {
    60. onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
    61. }}
    62. >
    63. {generateTree(dataSource, targetKeys)}
    64. </Tree>
    65. );
    66. }
    67. }}
    68. </Transfer>
    69. );
    70. };
    71. const treeData = [
    72. { key: '0-0', title: '0-0' },
    73. {
    74. key: '0-1',
    75. title: '0-1',
    76. children: [{ key: '0-1-0', title: '0-1-0' }, { key: '0-1-1', title: '0-1-1' }],
    77. },
    78. { key: '0-2', title: '0-3' },
    79. ];
    80. class App extends React.Component {
    81. state = {
    82. targetKeys: [],
    83. };
    84. onChange = targetKeys => {
    85. console.log('Target Keys:', targetKeys);
    86. this.setState({ targetKeys });
    87. };
    88. render() {
    89. const { targetKeys } = this.state;
    90. return (
    91. <div>
    92. <TreeTransfer dataSource={treeData} targetKeys={targetKeys} onChange={this.onChange} />
    93. </div>
    94. );
    95. }
    96. }
    97. ReactDOM.render(<App />, mountNode);

    API

    Transfer

    参数说明类型默认值版本
    className自定义类string
    dataSource数据源,其中的数据将会被渲染到左边一栏中,targetKeys 中指定的除外。TransferItem[][]
    disabled是否禁用booleanfalse
    filterOption接收 inputValue option 两个参数,当 option 符合筛选条件时,应返回 true,反之则返回 false(inputValue, option): boolean
    footer底部渲染函数(props): ReactNode
    lazyTransfer 使用了 react-lazy-load 优化性能,这里可以设置相关参数。设为 false 可以关闭懒加载。object|boolean{ height: 32, offset: 32 }
    listStyle两个穿梭框的自定义样式object
    locale各种语言object{ itemUnit: '项', itemsUnit: '项', notFoundContent: '列表为空', searchPlaceholder: '请输入搜索内容' }
    operations操作文案集合,顺序从上至下string[]['>', '<']
    render每行数据渲染函数,该函数的入参为 dataSource 中的项,返回值为 ReactElement。或者返回一个普通对象,其中 label 字段为 ReactElement,value 字段为 titleFunction(record)
    selectedKeys设置哪些项应该被选中string[][]
    showSearch是否显示搜索框booleanfalse
    showSelectAll是否展示全选勾选框booleantrue3.18.0
    targetKeys显示在右侧框数据的 key 集合string[][]
    titles标题集合,顺序从左至右string[]['', '']
    onChange选项在两栏之间转移时的回调函数(targetKeys, direction, moveKeys): void
    onScroll选项列表滚动时的回调函数(direction, event): void
    onSearch搜索框内容时改变时的回调函数(direction: 'left'|'right', value: string): void-
    onSelectChange选中项发生改变时的回调函数(sourceSelectedKeys, targetSelectedKeys): void

    Render Props

    3.18.0 新增。Transfer 支持接收 children 自定义渲染列表,并返回以下参数:

    参数说明类型版本
    direction渲染列表的方向'left' | 'right'3.18.0
    disabled是否禁用列表boolean3.18.0
    filteredItems过滤后的数据TransferItem[]3.18.0
    onItemSelect勾选条目(key: string, selected: boolean)3.18.0
    onItemSelectAll勾选一组条目(keys: string[], selected: boolean)3.18.0
    selectedKeys选中的条目string[]3.18.0

    参考示例

    1. <Transfer {...props}>{listProps => <YourComponent {...listProps} />}</Transfer>

    注意

    按照 React 的规范,所有的组件数组必须绑定 key。在 Transfer 中,dataSource里的数据值需要指定 key 值。对于 dataSource 默认将每列数据的 key 属性作为唯一的标识。

    如果你的数据没有这个属性,务必使用 rowKey 来指定数据列的主键。

    1. // 比如你的数据主键是 uid
    2. return <Transfer rowKey={record => record.uid} />;