欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React封裝CustomSelect組件思路詳解

 更新時(shí)間:2022年07月14日 08:50:15   作者:阿榮9490  
小編需要封裝一個(gè)通過(guò)Popover彈出框里可以自定義渲染內(nèi)容的組件,渲染內(nèi)容暫時(shí)有: 單選框, 復(fù)選框,接下來(lái)通過(guò)本文給大家分享React封裝CustomSelect組件思路,需要的朋友可以參考下

由來(lái): 需要封裝一個(gè)通過(guò)Popover彈出框里可以自定義渲染內(nèi)容的組件,渲染內(nèi)容暫時(shí)有: 單選框, 復(fù)選框。在封裝組件時(shí)我們需要權(quán)衡組件的靈活性, 拓展性以及代碼的優(yōu)雅規(guī)范,總結(jié)分享少許經(jīng)驗(yàn)。

思路和前提

由于考慮組件拆分得比較細(xì),層級(jí)比較多,為了方便使用了React.createContext + useContext作為參數(shù)向下傳遞的方式。

首先需要知道antd的Popover組件是繼承自Tooltip組件的,而我們的CustomSelect組件是繼承自Popover組件的。對(duì)于這種基于某個(gè)組件的二次封裝,其props類型一般有兩種方式處理: 繼承, 合并。

interface IProps extends XXX;
type IProps = Omit<TooltipProps, 'overlay'> & {...};

對(duì)于Popover有個(gè)很重要的觸發(fā)類型: trigger,默認(rèn)有四種"hover" "focus" "click" "contextMenu", 并且可以使用數(shù)組設(shè)置多個(gè)觸發(fā)行為。但是我們的需求只需要"hover"和"click", 所以需要對(duì)該字段進(jìn)行覆蓋。

對(duì)于Select, Checkbox這種表單控件來(lái)說(shuō),對(duì)齊二次封裝,很多時(shí)候需要進(jìn)行采用'受控組件'的方案,通過(guò)'value' + 'onChange'的方式"接管"其數(shù)據(jù)的輸入和輸出。并且value不是必傳的,使用組件時(shí)可以單純的只獲取操作的數(shù)據(jù),傳入value更多是做的一個(gè)初始值。而onChange是數(shù)據(jù)的唯一出口,我覺(jué)得應(yīng)該是必傳的,不然你怎么獲取的到操作的數(shù)據(jù)呢?對(duì)吧。

有一個(gè)注意點(diǎn): 既然表單控件時(shí)單選框,復(fù)選框, 那我們的輸入一邊是string, 一邊是string[],既大大增加了編碼的復(fù)雜度,也增加了使用的心智成本。所以我這里的想法是統(tǒng)一使用string[], 而再單選的交互就是用value[0]等方式完成單選值與數(shù)組的轉(zhuǎn)換。

編碼與實(shí)現(xiàn)

// types.ts
import type { TooltipProps } from 'antd';

interface OptItem {
    id: string;
    name: string;
    disabled: boolean; // 是否不可選
    children?: OptItem[]; // 遞歸嵌套
}
// 組件調(diào)用的props傳參
export type IProps = Omit<TooltipProps, 'overlay' | 'trigger'> & {
    /** 選項(xiàng)類型: 單選, 復(fù)選 */
    type: 'radio' | 'checkbox';
    /** 選項(xiàng)列表 */
    options: OptItem[];
    /** 展示文本 */
    placeholder?: string;
    /** 觸發(fā)行為 */
    trigger?: 'click' | 'hover';
    /** 受控組件: value + onChange 組合 */
    value?: string[];
    onChange?: (v: string[]) => void;
    /** 樣式間隔 */
    size?: number;
}

處理createContext與useContext

import type { Dispatch, MutableRefObj, SetStateAction } from 'react';
import { createContext } from 'react';
import type { IProps } from './types';
export const Ctx = createContext<{
  options: IProps['options'];
  size?: number;
  type: IProps['type'];
  onChange?: IProps['onChange'];
  value?: IProps['value'];
  
  // 這里有兩個(gè)額外的狀態(tài): shadowValue表示內(nèi)部的數(shù)據(jù)狀態(tài)
  shadowValue: string[];
  setShadowValue?: Dispatch<SetStateAction<string[]>>;
  // 操作彈出框
  setVisible?: (value: boolean) => void;
  // 復(fù)選框的引用, 暴露內(nèi)部的reset方法
  checkboxRef?: MutableRefObject<{
    reset: () => void;
  } | null>;
}>({ options: [], shadowValue: [], type: 'radio' });
// index.tsx

/**
 * 自定義下拉選擇框, 包括單選, 多選。
 */
import { FilterOutlined } from '@ant-design/icons';
import { useBoolean } from 'ahooks';
import { Popover } from 'antd';
import classnames from 'classnames';
import { cloneDeep } from 'lodash';
import type { FC, ReactElement } from 'react';
import { memo, useEffect, useRef, useState } from 'react';
import { Ctx } from './config';
import Controls from './Controls';
import DispatchRender from './DispatchRender';
import Styles from './index.less';
import type { IProps } from './types';

const Index: FC<IProps> = ({
  type,
  options,
  placeholder = '篩選文本',
  trigger = 'click',
  value,
  onChange,
  size = 6,
  style,
  className,
  ...rest
}): ReactElement => {
  // 彈窗顯示控制(受控組件)
  const [visible, { set: setVisible }] = useBoolean(false);

  // checkbox專用, 用于獲取暴露的reset方法
  const checkboxRef = useRef<{ reset: () => void } | null>(null);

  // 內(nèi)部維護(hù)的value, 不對(duì)外暴露. 統(tǒng)一為數(shù)組形式
  const [shadowValue, setShadowValue] = useState<string[]>([]);

  // value同步到中間狀態(tài)
  useEffect(() => {
    if (value && value?.length) {
      setShadowValue(cloneDeep(value));
    } else {
      setShadowValue([]);
    }
  }, [value]);

  return (
    <Ctx.Provider
      value={{
        options,
        shadowValue,
        setShadowValue,
        onChange,
        setVisible,
        value,
        size,
        type,
        checkboxRef,
      }}
    >
      <Popover
        visible={visible}
        onVisibleChange={(vis) => {
          setVisible(vis);
          // 這里是理解難點(diǎn): 如果通過(guò)點(diǎn)擊空白處關(guān)閉了彈出框, 而不是點(diǎn)擊確定關(guān)閉, 需要額外觸發(fā)onChange, 更新數(shù)據(jù)。
          if (vis === false && onChange) {
            onChange(shadowValue);
          }
        }}
        placement="bottom"
        trigger={trigger}
        content={
          <div className={Styles.content}>
            {/* 分發(fā)自定義的子組件內(nèi)容 */}
            <DispatchRender type={type} />
            {/* 控制行 */}
            <Controls />
          </div>
        }
        {...rest}
      >
        <span className={classnames(Styles.popoverClass, className)} style={style}>
          {placeholder ?? '篩選文本'}
          <FilterOutlined style={{ marginTop: 4, marginLeft: 3 }} />
        </span>
      </Popover>
    </Ctx.Provider>
  );
};

const CustomSelect = memo(Index);

export { CustomSelect };
export type { IProps };

對(duì)content的封裝和拆分: DispatchRender, Controls

先說(shuō)Controls, 包含控制行: 重置, 確定

/** 控制按鈕行: "重置", "確定" */
import { Button } from 'antd';
import { cloneDeep } from 'lodash';
import type { FC } from 'react';
import { useContext } from 'react';
import { Ctx } from './config';
import Styles from './index.less';

const Index: FC = () => {
  const { onChange, shadowValue, setShadowValue, checkboxRef, setVisible, value, type } =
    useContext(Ctx);
  return (
    <div className={Styles.btnsLine}>
      <Button
        type="primary"
        ghost
        size="small"
        onClick={() => {
          // radio: 直接重置為value
          if (type === 'radio') {
            if (value && value?.length) {
              setShadowValue?.(cloneDeep(value));
            } else {
              setShadowValue?.([]);
            }
          }
          // checkbox: 因?yàn)檫€需要處理全選, 需要交給內(nèi)部處理
          if (type === 'checkbox') {
            checkboxRef?.current?.reset();
          }
        }}
      >
        重置
      </Button>
      <Button
        type="primary"
        size="small"
        onClick={() => {
          if (onChange) {
            onChange(shadowValue); // 點(diǎn)擊確定才觸發(fā)onChange事件, 暴露內(nèi)部數(shù)據(jù)給外層組件
          }
          setVisible?.(false); // 關(guān)閉彈窗
        }}
      >
        確定
      </Button>
    </div>
  );
};

export default Index;

DispatchRender 用于根據(jù)type分發(fā)對(duì)應(yīng)的render子組件,這是一種編程思想,在次可以保證父子很大程度的解耦,再往下子組件不再考慮type是什么,父組件不需要考慮子組件有什么。

/** 分發(fā)詳情的組件,保留其可拓展性 */
import type { FC, ReactElement } from 'react';
import CheckboxRender from './CheckboxRender';
import RadioRender from './RadioRender';
import type { IProps } from './types';

const Index: FC<{ type: IProps['type'] }> = ({ type }): ReactElement => {
  let res: ReactElement = <></>;
  switch (type) {
    case 'radio':
      res = <RadioRender />;
      break;
    case 'checkbox':
      res = <CheckboxRender />;
      break;
    default:
      // never作用于分支的完整性檢查
      ((t) => {
        throw new Error(`Unexpected type: ${t}!`);
      })(type);
  }
  return res;
};

export default Index;

單選框的render子組件的具體實(shí)現(xiàn)

import { Radio, Space } from 'antd';
import type { FC, ReactElement } from 'react';
import { memo, useContext } from 'react';
import { Ctx } from './config';

const Index: FC = (): ReactElement => {
  const { size, options, shadowValue, setShadowValue } = useContext(Ctx);

  return (
    <Radio.Group
      value={shadowValue?.[0]} // Radio 接受單個(gè)數(shù)據(jù)
      onChange={({ target }) => {
        // 更新數(shù)據(jù)
        if (target.value) {
          setShadowValue?.([target.value]);
        } else {
          setShadowValue?.([]);
        }
      }}
    >
      <Space direction="vertical" size={size ?? 6}>
        {options?.map((item) => (
          <Radio key={item.id} value={item.id}>
            {item.name}
          </Radio>
        ))}
      </Space>
    </Radio.Group>
  );
};

export default memo(Index);

個(gè)人總結(jié)

  • 用好typescript作為你組件設(shè)計(jì)和一點(diǎn)點(diǎn)推進(jìn)的好助手,用好:繼承,合并,, 類型別名,類型映射(Omit, Pick, Record), never分支完整性檢查等. 一般每個(gè)組件單獨(dú)有個(gè)types.ts文件統(tǒng)一管理所有的類型
  • 組件入口props有很大的考慮余地,是整個(gè)組件設(shè)計(jì)的根本要素之一,傳什么參數(shù)決定了你后續(xù)的設(shè)計(jì),以及這個(gè)組件是否顯得"很傻",是否簡(jiǎn)單好用,以及后續(xù)如果想添加功能是否只能重構(gòu)
  • 另一個(gè)核心要素是數(shù)據(jù)流: 組件內(nèi)部的數(shù)據(jù)流如何清晰而方便的控制,又如何與外層調(diào)用組件交互,也直接決定了組件的復(fù)雜度。
  • 一些組件封裝的經(jīng)驗(yàn)和模式:比如復(fù)雜的核心方法可以考慮使用柯里化根據(jù)參數(shù)重要性分層傳入;復(fù)雜的多類別的子組件可以用分發(fā)模式解耦;以及一些像單一職責(zé),高內(nèi)聚低耦合等靈活應(yīng)用這些理論知識(shí)。

到此這篇關(guān)于React封裝CustomSelect組件思路的文章就介紹到這了,更多相關(guān)React封裝CustomSelect組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • react為什么不推薦使用index作為key

    react為什么不推薦使用index作為key

    本文主要介紹了react為什么不推薦使用index作為key,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • 提高React界面性能的十個(gè)技巧

    提高React界面性能的十個(gè)技巧

    眾所周知,性能是Web應(yīng)用界面的關(guān)鍵方面,它直接影響到用戶的使用體驗(yàn)。本文將向您展示十種提高React UI性能的特定技術(shù)和一般方法。
    2021-05-05
  • 淺談React useDebounce 防抖原理

    淺談React useDebounce 防抖原理

    本文主要介紹了淺談React useDebounce 防抖原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • 淺談react路由傳參的幾種方式

    淺談react路由傳參的幾種方式

    這篇文章主要介紹了淺談react路由傳參的幾種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • react hooks實(shí)現(xiàn)原理解析

    react hooks實(shí)現(xiàn)原理解析

    這篇文章主要介紹了react hooks實(shí)現(xiàn)原理,文中給大家介紹了useState dispatch 函數(shù)如何與其使用的 Function Component 進(jìn)行綁定,節(jié)后實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • react中的watch監(jiān)視屬性-useEffect介紹

    react中的watch監(jiān)視屬性-useEffect介紹

    這篇文章主要介紹了react中的watch監(jiān)視屬性-useEffect使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • React 如何使用時(shí)間戳計(jì)算得到開始和結(jié)束時(shí)間戳

    React 如何使用時(shí)間戳計(jì)算得到開始和結(jié)束時(shí)間戳

    這篇文章主要介紹了React 如何拿時(shí)間戳計(jì)算得到開始和結(jié)束時(shí)間戳,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • ReactQuery?渲染優(yōu)化示例詳解

    ReactQuery?渲染優(yōu)化示例詳解

    這篇文章主要為大家介紹了ReactQuery?渲染優(yōu)化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • ReactQuery系列之?dāng)?shù)據(jù)轉(zhuǎn)換示例詳解

    ReactQuery系列之?dāng)?shù)據(jù)轉(zhuǎn)換示例詳解

    這篇文章主要為大家介紹了ReactQuery系列之?dāng)?shù)據(jù)轉(zhuǎn)換示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Rect Intersection判斷兩個(gè)矩形是否相交

    Rect Intersection判斷兩個(gè)矩形是否相交

    這篇文章主要為大家介紹了Rect Intersection判斷兩個(gè)矩形是否相交的算法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06

最新評(píng)論