国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

React封裝CustomSelect組件思路詳解

3403771864 / 401人閱讀

  前提:一個通過Popover彈出框里自定義渲染內(nèi)容的組件要進(jìn)行封裝,目前要求實(shí)現(xiàn)有: 單選框, 復(fù)選框。我們需要考慮封裝組件時要權(quán)衡組件的靈活性, 拓展性以及代碼的優(yōu)雅規(guī)范,現(xiàn)在和大家一起分享。

  思路和前提

  在層級較多,組件較為多的情況下,為了方便使用了React.createContext + useContext作為參數(shù)向下傳遞的方式。

 我們要先確定要antd的Popover組件是繼承自Tooltip組件的,而CustomSelect組件是繼承自Popover組件的。對于要對某個組件進(jìn)行二次封裝,其props類型一般有兩種方式處理: 繼承, 合并。

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

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

  對于Select, Checkbox這種表單控件來說,對齊二次封裝,不少時候都要對采用'受控組件'的方案,通過'value' + 'onChange'的方式"接管"其數(shù)據(jù)的輸入和輸出。注意value不是必傳的,在使用組件時只獲取操作的數(shù)據(jù),傳入value更多是做的一個初始值。onChange也是唯一的出口數(shù)值,這是很有必要,不然你怎么獲取的到操作的數(shù)據(jù)呢?是吧。

  說一個要點(diǎn): 既然表單控件時單選框,復(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'];
  // 這里有兩個額外的狀態(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, 不對外暴露. 統(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): 如果通過點(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 };
  對content的封裝和拆分: DispatchRender, Controls

  先說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ā)對應(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 接受單個數(shù)據(jù)
  onChange={({ target }) => {
  // 更新數(shù)據(jù)
  if (target.value) {
  setShadowValue?.([target.value]);
  } else {
  setShadowValue?.([]);
  }
  }}
  >
  <Space direction="vertical" size={size ?? 6}>
  {options?.map((item) => (</p>
  <p>
  <Radio key={item.id} value={item.id}>
  {item.name}
  </Radio>
  ))}
  </Space>
  </Radio.Group>
  );
  };
  export default memo(Index);

  個人總結(jié)

  typescript作為組件設(shè)計(jì)和一點(diǎn)點(diǎn)推進(jìn)的好助,可以實(shí)現(xiàn):繼承,合并,, 類型別名,類型映射(Omit, Pick, Record), never分支完整性檢查等.通常每個組件多帶帶有個types.ts文件統(tǒng)一管理所有的類型,組件入口props有很大的考慮余地,這是整個組件設(shè)計(jì)的根本要素之一,至于后續(xù)傳導(dǎo)什么參數(shù),是否好用都是在再考量。

  還有一個要點(diǎn)就是數(shù)據(jù)流: 組件內(nèi)部的數(shù)據(jù)流如何清晰而方便的控制,也要考量如何與外層調(diào)用組件交互,這樣就直接決定了組件的復(fù)雜度。

  經(jīng)驗(yàn)分享:比如復(fù)雜的核心方法在里面可以使用柯里化根據(jù)參數(shù)重要性分層傳入;對于復(fù)雜的多類別的子組件看使用分發(fā)模式解耦;簡單些的考慮用高內(nèi)聚低耦合等靈活應(yīng)用這些理論知識。


文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/127736.html

相關(guān)文章

  • react進(jìn)階系列:高階組件詳解(一)

    摘要:創(chuàng)建一個普通函數(shù)因?yàn)榈拇嬖谒宰兂蓸?gòu)造函數(shù)創(chuàng)建一個方法在方法中,創(chuàng)建一個中間實(shí)例對中間實(shí)例經(jīng)過邏輯處理之后返回使用方法創(chuàng)建實(shí)例而恰好,高階組件的創(chuàng)建邏輯與使用,與這里的方法完全一致。因?yàn)榉椒ㄆ鋵?shí)就是構(gòu)造函數(shù)的高階組件。 很多人寫文章喜歡把問題復(fù)雜化,因此當(dāng)我學(xué)習(xí)高階組件的時候,查閱到的很多文章都給人一種高階組件高深莫測的感覺。但是事實(shí)上卻未必。 有一個詞叫做封裝。相信寫代碼這么久了,大...

    NervosNetwork 評論0 收藏0
  • 詳解react、redux、react-redux之間的關(guān)系

    摘要:或者兄弟組件之間想要共享某些數(shù)據(jù),也不是很方便傳遞獲取等。后面要講到的就是通過讓各個子組件拿到中的數(shù)據(jù)的。所以,確實(shí)和沒有什么本質(zhì)關(guān)系,可以結(jié)合其他庫正常使用。 本文介紹了react、redux、react-redux之間的關(guān)系,分享給大家,也給自己留個筆記,具體如下: React 一些小型項(xiàng)目,只使用 React 完全夠用了,數(shù)據(jù)管理使用props、state即可,那什么時候需要引入...

    xioqua 評論0 收藏0
  • react進(jìn)階系列 - 高階組件詳解四:高階組件的嵌套使用

    摘要:前面有講到過很多頁面會在初始時驗(yàn)證登錄狀態(tài)與用戶角色。這個時候就涉及到一個高階組件的嵌套使用。而每一個高階組件函數(shù)執(zhí)行之后中所返回的組件,剛好可以作為下一個高階組件的參數(shù)繼續(xù)執(zhí)行,而并不會影響基礎(chǔ)組件中所獲得的新能力。 前面有講到過很多頁面會在初始時驗(yàn)證登錄狀態(tài)與用戶角色。我們可以使用高階組件來封裝這部分驗(yàn)證邏輯。封裝好之后我們在使用的時候就可以如下: export default w...

    LMou 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<