首页 > 编程语言 >antd-design源码学习系列-Cascader

antd-design源码学习系列-Cascader

时间:2022-12-01 18:13:27浏览次数:41  
标签:const newChildProps label React 源码 design Cascader return children

开发过程中经常会有联级选择的场景,利用antd的组件可以方便的实现相关功能。知其然更要知其所以然,这边来分析一下相关源码,了解一下实现过程。

实现的功能如下:(应用也很方便,相关传参见antd官网https://3x.ant.design/components/cascader-cn/)

 

 

 源码分析如下:

const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<CascaderRef>) => {
   return (
    // 返回RcCascader组件
    <RcCascader
      prefixCls={prefixCls}
      className={classNames(
        !customizePrefixCls && cascaderPrefixCls,
        {
          [`${prefixCls}-lg`]: mergedSize === 'large',
          [`${prefixCls}-sm`]: mergedSize === 'small',
          [`${prefixCls}-rtl`]: isRtl,
          [`${prefixCls}-borderless`]: !bordered,
          [`${prefixCls}-in-form-item`]: isFormItemInput,
        },
        getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
        className,
      )}
     .......
    />
})

 

// import RcCascader from 'rc-cascader';

下载地址: https://github.com/react-component/cascader

// https://github.com/react-component/cascader/

const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, ref) => {
  const {
    // MISC
    id,
    prefixCls = 'rc-cascader',
    fieldNames,

    // Value
    defaultValue,
    value,
    changeOnSelect,
    onChange,
    displayRender,
    checkable,

    // Search
    searchValue,
    onSearch,
    showSearch,

    // Trigger
    expandTrigger,

    // Options
    options,
    dropdownPrefixCls,
    loadData,

    // Open
    popupVisible,
    open,

    popupClassName,
    dropdownClassName,
    dropdownMenuColumnStyle,

    popupPlacement,
    placement,

    onDropdownVisibleChange,
    onPopupVisibleChange,

    // Icon
    expandIcon = '>',
    loadingIcon,

    // Children
    children,
    dropdownMatchSelectWidth = false,
    showCheckedStrategy = SHOW_PARENT,
    ...restProps
  } = props;
 ..............
return (
    <CascaderContext.Provider value={cascaderContext}>
   // 返回的BaseSelect的select类型
      <BaseSelect
        {...restProps}
        // MISC
        ref={ref as any}
        id={mergedId}
 .............
        getRawInputElement={() => children}
      />
    </CascaderContext.Provider>
}

其中的一些细节处理:

showSearch配置:

const [mergedShowSearch, searchConfig] = useSearchConfig(showSearch);
// 返回的mergedShowSearch表示showSearch表示是否可查询
// showSearch为对象时,用于筛选后的选项配置 (filter, limit, matchInputWidth, render, sort)
// useSearchConfig
export default function useSearchConfig(showSearch?: CascaderProps['showSearch']) {
  return React.useMemo<[boolean, ShowSearchType]>(() => {
    if (!showSearch) {
    // 没配置时 返回显示默认值
      return [false, {}];
    }

    let searchConfig: ShowSearchType = {
      matchInputWidth: true,
      limit: 50, //
    };

    if (showSearch && typeof showSearch === 'object') {
      searchConfig = {
        ...searchConfig,
        ...showSearch,
      };
    }
    }
 // 返回配置
    return [true, searchConfig];
  }, [showSearch]);
}

 

searchOptions选项配置:
const searchOptions = useSearchOptions(
    mergedSearchValue,
    mergedOptions, // 默认的options选项
    mergedFieldNames, // 自定义options中label name children的字段{ label: 'label', value: 'value', children: 'children' }
    dropdownPrefixCls || prefixCls,
    searchConfig, // showSearch的配置选项
    changeOnSelect, // props-> changeOnSelect
  );

// mergedFieldNames
const mergedFieldNames = React.useMemo(
    () => fillFieldNames(fieldNames),
    [JSON.stringify(fieldNames)],
  );
export function fillFieldNames(fieldNames?: FieldNames): InternalFieldNames {
  const { label, value, children } = fieldNames || {};
  const val = value || 'value';
  return { // 替换原值
    label: label || 'label',
    value: val,
    key: val,
    children: children || 'children',
  };
}

// useSearchOptions
return React.useMemo(() => {
    const filteredOptions: DefaultOptionType[] = [];
    if (!search) {
      return [];
    }

    function dig(list: DefaultOptionType[], pathOptions: DefaultOptionType[]) {
      list.forEach(option => {
        // Perf saving when `sort` is disabled and `limit` is provided
        if (!sort && limit > 0 && filteredOptions.length >= limit) {
          return;
        }

        const connectedPathOptions = [...pathOptions, option];
        const children = option[fieldNames.children];

        // If current option is filterable
        if (
          // If is leaf option
          !children ||
          children.length === 0 ||
          // If is changeOnSelect
          changeOnSelect
        ) {
//filter
// const defaultFilter: ShowSearchType['filter'] = (search, options, { label }) =>
//   options.some(opt => String(opt[label]).toLowerCase().includes(search.toLowerCase()));
          if (filter(search, connectedPathOptions, { label: fieldNames.label })) {
            filteredOptions.push({
              ...option,
              [fieldNames.label as 'label']: render( //默认渲染
                search,
                connectedPathOptions,
                prefixCls,
                fieldNames,
              ),
              [SEARCH_MARK]: connectedPathOptions,
            });
          }
        }

        if (children) {
   //if children 递归循环
          dig(option[fieldNames.children] as DefaultOptionType[], connectedPathOptions);
        }
      });
    }

    dig(options, []);

    // Do sort
    if (sort) {
      filteredOptions.sort((a, b) => {
        return sort(a[SEARCH_MARK], b[SEARCH_MARK], search, fieldNames);
      });
    }

    return limit > 0 ? filteredOptions.slice(0, limit as number) : filteredOptions;
  }, [search, options, fieldNames, prefixCls, render, changeOnSelect, filter, sort, limit]);

 

 // https://github.com/react-component/select

 <SelectContext.Provider value={selectContext}>
        <BaseSelect
          {...restProps}
          // >>> MISC
          id={mergedId}
          prefixCls={prefixCls}
          ref={ref}
          omitDomProps={OMIT_DOM_PROPS}
          mode={mode}
          // >>> Values
          displayValues={displayValues}
          onDisplayValuesChange={onDisplayValuesChange}
          // >>> Search
          searchValue={mergedSearchValue}
          onSearch={onInternalSearch}
          autoClearSearchValue={autoClearSearchValue}
          onSearchSplit={onInternalSearchSplit}
          dropdownMatchSelectWidth={dropdownMatchSelectWidth}
          // >>> OptionList
          OptionList={OptionList}
          emptyOptions={!displayOptions.length}
          // >>> Accessibility
          activeValue={activeValue}
          activeDescendantId={`${mergedId}_list_${accessibilityIndex}`}
        />
      </SelectContext.Provider>

 

节点的渲染:

  if (customizeRawInputElement) {
    renderNode = selectorNode;
  } else {
    renderNode = (
      <div
        className={mergedClassName}
        {...domProps}
        ref={containerRef}
        onm ouseDown={onInternalMouseDown}
        onKeyDown={onInternalKeyDown}
        onKeyUp={onInternalKeyUp}
        onFocus={onContainerFocus}
        onBlur={onContainerBlur}
      >
        {mockFocused && !mergedOpen && (
          <span
            style={{
              width: 0,
              height: 0,
              position: 'absolute',
              overflow: 'hidden',
              opacity: 0,
            }}
            aria-live="polite"
          >
            {/* Merge into one string to make screen reader work as expect */}
            {`${displayValues
              .map(({ label, value }) =>
   // select的值是number或string类型?
                ['number', 'string'].includes(typeof label) ? label : value,
              )
              .join(', ')}`}
          </span>
        )}
        {selectorNode}

        {arrowNode}
        {clearNode}
      </div>
    );
  }


  // >>> Selector (selectorNode )
  const selectorNode = (
    <SelectTrigger
..........
    >
      {customizeRawInputElement ? (
        React.cloneElement(customizeRawInputElement, {
          ref: customizeRawInputRef,
        })
      ) : (
        <Selector
          {...props}
         ..........
        />
      )}
    </SelectTrigger>
  );

// SelectTrigger
<Trigger // import Trigger from 'rc-trigger';
      {...restProps}
    ............
    >
      {children}
    </Trigger>

// Selector
  const selectNode = // 多选
    mode === 'multiple' || mode === 'tags' ? (
      <MultipleSelector {...props} {...sharedProps} />
    ) : ( // 单选
      <SingleSelector {...props} {...sharedProps} />
    );

  return (
    <div
      ref={domRef}
      className={`${prefixCls}-selector`}
      onClick={onClick}
      onm ouseDown={onMouseDown}
    >
      {selectNode}
    </div>
  );

SingleSelector(单选选择框)

<>
      <span className={`${prefixCls}-selection-search`}>
  // input上的输入框
        <Input // 另外的封装
          ref={inputRef}
          prefixCls={prefixCls}
          id={id}
          open={open}
          inputElement={inputElement}
          disabled={disabled} //是否可编辑,mode === 'combobox' || showSearch
          autoFocus={autoFocus}
          autoComplete={autoComplete}
          editable={inputEditable}
          activeDescendantId={activeDescendantId}
          value={inputValue}
          onKeyDown={onInputKeyDown}
          onm ouseDown={onInputMouseDown}
          onChange={(e) => {
            setInputChanged(true);
            onInputChange(e as any); // props的onInputChange
          }}
          onPaste={onInputPaste}
    // onCompositionStart和onCompositionEnd 主要避免中英文输入问题
          onCompositionStart={onInputCompositionStart}
          onCompositionEnd={onInputCompositionEnd}
          tabIndex={tabIndex}
          attrs={pickAttrs(props, true)}
          maxLength={combobox ? maxLength : undefined}
        />
      </span>

      {/* Display value */}
// 显示展示的值
      {!combobox && item && !hasTextInput && (
        <span className={`${prefixCls}-selection-item`} title={title}>
          {item.label}
        </span>
      )}

      {/* Display placeholder */}
// 显示placeholder
      {renderPlaceholder()}
    </>

其中 SingleSelector 和 MultipleSelector都是上面的输入框

SelectTrigger是外面的包裹层(代码分析如下:)

<Trigger
      {...restProps}
...................
    >
      {children}
    </Trigger>

// Trigger

 

// 分析下面的Trigger代码(https://github.com/react-component/trigger.git)

export function generateTrigger(
// 入参: Portal
  PortalComponent: any,
): React.ComponentClass<TriggerProps> {
   class Trigger extends React.Component<TriggerProps, TriggerState> {
   constructor() {
      super(props);
      // 初始化popupVisible,popup的显示与否
      let popupVisible: boolean;
      if ('popupVisible' in props) {
        popupVisible = !!props.popupVisible;
      } else {
        popupVisible = !!props.defaultPopupVisible;
      }

      this.state = {
        prevPopupVisible: popupVisible,
        popupVisible,
      };
      // 注册事件
      ALL_HANDLERS.forEach((h) => {
        this[`fire${h}`] = (e) => {
          this.fireEvents(h, e);
        };
      });
   ////////////////////////////////////////////////////////////////////
   componentDidUpdate() {}
   componentWillUnmount() {
      // 清除一些定时器操作
      this.clearDelayTimer();
      this.clearOutsideHandler();
      clearTimeout(this.mouseDownTimeout);
      raf.cancel(this.attachId);
   }

render() {
      const { popupVisible } = this.state;
      const { children, forceRender, alignPoint, className, autoDestroy } =
        this.props;
      const child = React.Children.only(children) as React.ReactElement;
      const newChildProps: HTMLAttributes<HTMLElement> & { key: string } = {
        key: 'trigger',
      };

      // ============================== Visible Handlers ==============================
      // >>> ContextMenu
      if (this.isContextMenuToShow()) {
        newChildProps.onContextMenu = this.onContextMenu;
      } else {
        newChildProps.onContextMenu = this.createTwoChains('onContextMenu');
      }

      // >>> Click
      if (this.isClickToHide() || this.isClickToShow()) {
        newChildProps.onClick = this.onClick;
        newChildProps.onMouseDown = this.onMouseDown;
        newChildProps.onTouchStart = this.onTouchStart;
      } else {
        newChildProps.onClick = this.createTwoChains('onClick');
        newChildProps.onMouseDown = this.createTwoChains('onMouseDown');
        newChildProps.onTouchStart = this.createTwoChains('onTouchStart');
      }

      // >>> Hover(enter)
      if (this.isMouseEnterToShow()) {
        newChildProps.onMouseEnter = this.onMouseEnter;

        // Point align
        if (alignPoint) {
          newChildProps.onMouseMove = this.onMouseMove;
        }
      } else {
        newChildProps.onMouseEnter = this.createTwoChains('onMouseEnter');
      }

      // >>> Hover(leave)
      if (this.isMouseLeaveToHide()) {
        newChildProps.onMouseLeave = this.onMouseLeave;
      } else {
        newChildProps.onMouseLeave = this.createTwoChains('onMouseLeave');
      }

      // >>> Focus
      if (this.isFocusToShow() || this.isBlurToHide()) {
        newChildProps.onFocus = this.onFocus;
        newChildProps.onBlur = this.onBlur;
      } else {
        newChildProps.onFocus = this.createTwoChains('onFocus');
        newChildProps.onBlur = this.createTwoChains('onBlur');
      }

      // =================================== Render ===================================
      const childrenClassName = classNames(
        child && child.props && child.props.className,
        className,
      );
      if (childrenClassName) {
        newChildProps.className = childrenClassName;
      }

      const cloneProps: any = {
        ...newChildProps,
      };
      if (supportRef(child)) {
        cloneProps.ref = composeRef(this.triggerRef, (child as any).ref);
      }
      const trigger = React.cloneElement(child, cloneProps);

      let portal: React.ReactElement;
      // prevent unmounting after it's rendered
      if (popupVisible || this.popupRef.current || forceRender) {
        portal = (
 //////import Popup from './Popup';
          <PortalComponent
            key="portal"
            getContainer={this.getContainer}
            didUpdate={this.handlePortalUpdate}
          >
            {this.getComponent()}
          </PortalComponent>
        );
      }

      if (!popupVisible && autoDestroy) {
        portal = null;
      }

      return (
        <TriggerContext.Provider value={this.triggerContextValue}>
          {trigger} // React.cloneElement(child, cloneProps)
          {portal}
  // <PortalComponent //传入的参数
          //   key="portal"
          //   getContainer={this.getContainer}
          //   didUpdate={this.handlePortalUpdate}
          // >
          //   {this.getComponent()}
          // </PortalComponent>
        </TriggerContext.Provider>
      );
    }
// {this.getComponent()}

return (
        <Popup
.................
          onClick={onPopupClick}
        >
  // 上面select的popup props 
// let popupNode = popupElement;
  // if (dropdownRender) {
  //   popupNode = dropdownRender(popupElement);
  // }
          {typeof popup === 'function' ? popup() : popup}
        </Popup>
    } 
 }
}



// Popup组件
    const popupNode: React.ReactNode = inMobile ? (
      <MobilePopupInner {...cloneProps} mobile={mobile} ref={ref} />
    ) : (
      <PopupInner {...cloneProps} ref={ref} />
    );

    // We can use fragment directly but this may failed some selector usage. Keep as origin logic
    return (
      <div>
        <Mask {...cloneProps} />
        {popupNode}
    // const popupNode: React.ReactNode = inMobile ? (
    //   <MobilePopupInner {...cloneProps} mobile={mobile} ref={ref} />
    // ) : (
    //   <PopupInner {...cloneProps} ref={ref} />
    // );
      </div>
    );

//Mask (没扒了,盲猜就是遮罩框,应该就是点击外部就可以关闭)
return (
    <CSSMotion {...motion} visible={visible} removeOnLeave>
      {({ className }) => (
        <div
          style={{ zIndex }}
          className={classNames(`${prefixCls}-mask`, className)}
        />
      )}
    </CSSMotion>
  );

算了,就扒到这里吧,全是层层嵌套的引入,明天有时间自己来实现一下吧。

 

标签:const,newChildProps,label,React,源码,design,Cascader,return,children
From: https://www.cnblogs.com/best-mll/p/16893332.html

相关文章

  • SpringSecurity表单登录流程源码分析
    先看看这种核心流程图这张图是SpringSecurity认证涉及到的核心类让应用Debug启动点击表单登录进入到这个就是上图中的绿色过滤器,这个类中首先进入attemptAuthenticatio......
  • SpringSecurityOAuth2授权流程源码分析(自定义验证码模式)
    前言周末闲来无事,谢谢自己的项目,然后想把老的授权模式改造一下,老的是基于SpringSecurity的实现,想升级为SpringSecurityOAuth2模式,于是看了下之前搭建的SpringSecurityO......
  • 《安富莱嵌入式周报》第293期:SEGGER开源其C/C++库源码emRun,丰富EMC电磁兼容资,OTA开源
    往期周报汇总地址:http://www.armbbs.cn/forum.php?mod=forumdisplay&fid=12&filter=typeid&typeid=104 视频版:https://www.bilibili.com/video/BV1ND4y1v7ik/ 1、......
  • PowerDesigner从Excel导入表(批量)
    PowerDesigner要导入Excel,需要使用到VB语法,同时PowerDesigner集成了访问Excel的方法,VB代码如下:'开始OptionExplicitDimmdl'thecurrentmodelSetmdl=ActiveMo......
  • 直播app系统源码,简单易上手的进度条
    直播app系统源码,简单易上手的进度条第一步:安装NProgress$npminstall--savenprogress ​第二步:在main.js文件中导入NProgress包对应的JS和CSS//导入NProgress......
  • Android5.0及Material Design
    Android5.0是在2014年10月16日发布的。1、Android1.0一切的开始2008年10月发布的。第一款安卓手机是htc的G1,它是一款全键盘的手机。没有虚拟键盘的。2、Android1.5增......
  • OCC gp_Ax2源码记录
    gp_Ax2代表一个三维右手坐标系。坐标系包括以下内容:-原点(Locationpoint)-3个正交的单位向量,在OCC中分别称为"XDirection"(后文统称X),"YDirection"(后文统称Y)......
  • 在CentOS编译Git源码
    ​​Git​​​是一个​​免费的开源​​分布式版本控制系统,旨在处理从小到小到的所有内容具有速度和效率的超大型项目。Git​​易于学习​​​,​​占用空间很小,性能快如闪......
  • OCC gp_Ax1源码阅读记录
    gp_Ax1描述了一个三维轴。轴包含以下内容:-原点(Locationpoint)-单位向量(称作"Direction"或"mainDirection")轴通常有以下用途:-描述三维几何体(例如:旋转体的轴)......
  • 实战 | OpenCV带掩码(mask)的模板匹配使用技巧与演示(附源码)
    导读本文将重点介绍OpenCV带掩码(mask)的模板匹配使用技巧与演示。(公众号:OpenCV与AI深度学习) 背景介绍  在使用模板匹配时,一些特定情况中我们并不需要将整个模板图......