AntD Select + AutoComplete

简单介绍一下 AntD Select 的用法

mode: undefined, tag, multiple

  • undefine: 默认值,就是dropdown,
  • tag: 跟multple 唯一的区别就是,输入的值不在list 内,敲回车,自动插入.
  • multiple, 就是多值选择。

一些约定

  • showSearch: 看这段代码 const mergedShowSearch = showSearch !== undefined ? showSearch : isMultiple || mode === 'combobox'; 结论: 如果显示设置,优先级最高,隐含的其次,也就是 tags, multiple, combobox 也是可以showSearch.
    • 类似的开关还有很多,例如: showArrow 在其他的组件中,这个约定也是类似的。当然这些开关只是第一层,开关,默认有这个开关就可以打开,但是也有一些相关的属性进一步控制这个开关,例如 filterOption=false 就可以关闭search的功能,这个在SearchBox 或者Email 这种组件中使用。例如以下代码,尽管,我们打开了showSearch 开关,但是我们通过filterOption={false}关闭了该功能,我们通过onSearchevent来刷新options
const options = this.state.data.map(d => <Option key={d.value}>{d.text}</Option>);
<Select
        showSearch
        value={this.state.value}
        placeholder={this.props.placeholder}
        showArrow={false}
        filterOption={false}
        onSearch={this.handleSearch}
        onChange={this.handleChange}
        notFoundContent={null}
      >
        {options}
      </Select>

谈谈 Select 里面的animation.

首先,我们可以通过transitionName 参数来提供一个transtionName, 看这段代码:transitionName={getTransitionName(rootPrefixCls, 'slide-up', props.transitionName)}, 如果不提供,默认就是slide-up 这个transitionName,
第一个问题,这个transtionName 有神马用途,或者,用它会发生什么,首先它会传递个CSSMotion ,如果有同学对CSSMotion 不理解,参考CSSMotion.
简单说一下,下来菜单在展开或者关闭的过程,className 会如此变化。

  • 展开
    • .ant-slide-up-enter(Dom 已经存在) 或者 .ant-slide-up-appear(Dom 刚刚创建)
    • .ant-slide-up-enter-(prepare/start/active) 依次发生变化, 一般我们只关心start 与active
    • 最后动画结束,与动画相关的类名全部移除。
  • 关闭
    • .ant-slide-leave
    • .ant-slide-up-leave-(parepare/start/active) 依次发生,同样,我们只关心start 跟active
    • 最后动画结束,与动画相关的类名全部移除,附加上ant-select-dropdown-hidden 类.

下面是slide-up 相关的css ,变化的就是opacitytransform 两个属性

.motion-common(@duration: @animation-duration-base) {
  animation-duration: @duration;
  animation-fill-mode: both;
}

.motion-common-leave(@duration: @animation-duration-base) {
  animation-duration: @duration;
  animation-fill-mode: both;
}

.make-motion(@className, @keyframeName, @duration: @animation-duration-base) {
  .@{className}-enter,
  .@{className}-appear {
    .motion-common(@duration);

    animation-play-state: paused;
  }
  .@{className}-leave {
    .motion-common-leave(@duration);

    animation-play-state: paused;
  }
  .@{className}-enter.@{className}-enter-active,
  .@{className}-appear.@{className}-appear-active {
    animation-name: ~'@{keyframeName}In';
    animation-play-state: running;
  }
  .@{className}-leave.@{className}-leave-active {
    animation-name: ~'@{keyframeName}Out';
    animation-play-state: running;
    pointer-events: none;
  }
}


.slide-motion(@className, @keyframeName) {
  @name: ~'@{ant-prefix}-@{className}';
  .make-motion(@name, @keyframeName);
  .@{name}-enter,
  .@{name}-appear {
    opacity: 0;
    animation-timing-function: @ease-out-quint;
  }
  .@{name}-leave {
    animation-timing-function: @ease-in-quint;
  }
}

.slide-motion(slide-up, antSlideUp);
@keyframes antSlideUpIn {
  0% {
    transform: scaleY(0.8);
    transform-origin: 0% 0%;
    opacity: 0;
  }
  100% {
    transform: scaleY(1);
    transform-origin: 0% 0%;
    opacity: 1;
  }
}

谈谈Select 里面的Option OptionGroup

首先,Rc-Select 里面的Option 类型是 export type OptionsType = (OptionData | OptionGroupData)[]; 我们来看一下代码片段。

export interface OptionCoreData {
  key?: Key;
  disabled?: boolean;
  value: Key;
  title?: string;
  className?: string;
  style?: React.CSSProperties;
  label?: React.ReactNode;
  /** @deprecated Only works when use `children` as option data */
  children?: React.ReactNode;
}

export interface OptionData extends OptionCoreData {
  /** Save for customize data */
  [prop: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}

export interface OptionGroupData {
  key?: Key;
  label?: React.ReactNode;
  options: OptionData[];
  className?: string;
  style?: React.CSSProperties;

  /** Save for customize data */
  [prop: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}

export type OptionsType = (OptionData | OptionGroupData)[];

同时Select 暴露出了Option, OptGroup 来用于提供Options, 常用于AntD Select. 看一下OptGroup类型定义. 它没有Options 属性。Options 直接来源于children.

export interface OptGroupProps extends Omit<OptionGroupData, 'options'> {
  children?: React.ReactNode;
}

export interface OptionGroupFC extends React.FC<OptGroupProps> {
  /** Legacy for check if is a Option Group */
  isSelectOptGroup: boolean;
}

const OptGroup: OptionGroupFC = () => null;
OptGroup.isSelectOptGroup = true;

export default OptGroup;

这个是Option的定义,它没有label这个属性,label 直接来源于children.

export interface OptionProps extends Omit<OptionCoreData, 'label'> {
  children: React.ReactNode;

  /** Save for customize data */
  [prop: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}

export interface OptionFC extends React.FC<OptionProps> {
  /** Legacy for check if is a Option Group */
  isSelectOption: boolean;
}

/** This is a placeholder, not real render in dom */
const Option: OptionFC = () => null;
Option.isSelectOption = true;

export default Option;

对于AutoComplete 这个组件,它也是基于Select 来做的,它的Options 是通过Options这个属性来提供的,它的代码片段如下

export interface DataSourceItemObject {
  value: string;
  text: string;
}
export type DataSourceItemType = DataSourceItemObject | React.ReactNode;

export interface AutoCompleteProps
  extends Omit<
    InternalSelectProps<string>,
    'inputIcon' | 'loading' | 'mode' | 'optionLabelProp' | 'labelInValue'
  > {
  dataSource?: DataSourceItemType[];
}

那么对于分组这种情况,它的Options 应该张这样

[
    {label:xxx,options:[xxx,xxx,...]},
    {label:xxx,options:[xxx,xxx,...]},
    {label:xxx,options:[xxx,xxx,...]},
    {label:xxx,options:[xxx,xxx,...]},
]

对于未分组的情况,它的Options 应该长这样

[
    {label:xx,value:xxx,key:xxx},
    {label:xx,value:xxx,key:xxx},
    {label:xx,value:xxx,key:xxx},
    {label:xx,value:xxx,key:xxx},
    ...
]

在Rc-Select/utils/valueUtil.tsx 里面有这个函数flattenOptions 来解析以上这些数据结构.

export function flattenOptions(options: SelectOptionsType): FlattenOptionData[] {
  const flattenList: FlattenOptionData[] = [];

  function dig(list: SelectOptionsType, isGroupOption: boolean) {
    list.forEach((data) => {
      if (isGroupOption || !('options' in data)) {
        // Option
        flattenList.push({
          key: getKey(data, flattenList.length),
          groupOption: isGroupOption,
          data,
        });
      } else {
        // Option Group
        flattenList.push({
          key: getKey(data, flattenList.length),
          group: true,
          data,
        });

        dig(data.options, true);
      }
    });
  }

  dig(options, false);

  return flattenList;
}
posted @ 2021-06-10 10:42  kongshu  阅读(2058)  评论(0编辑  收藏  举报