React組件二次包裝的具體實現

原生組件是對公共場景的抽象,若要契合實際業務往往需要對其進行二次包裝。
對組件進行二次包裝一般需要進行包括不限於以下的步驟:

  • 組件類型類型聲明
  • 自定義組件渲染邏輯
  • 向原生組件透傳屬性

1. 類型聲明

類型聲明是組件二次包裝過程中的第一步,項目中常見的方案是直接進行類型聲明,例如通過Button組件包裝個自定義的Button:

import React, { CSSProperties } from "react";
import { Button } from "@arco-design/web-react";

type IProps = {
  style?: CSSProperties;
  className?: string | string[];
  type?: "default" | "primary" | "secondary" | "dashed" | "text" | "outline";
  // ...其他Button屬性
};

const MyButton: React.FC<IProps> = ({ style, className, type, children }) => {
  // 此處為自定義邏輯

  return (
    <Button style={style} className={className} type={type}>
      {children}
    </Button>
  );
};

export default MyButton;

直接類型聲明簡單粗暴,用到什麼屬性就添加什麼,但存在一個致命的,其限定瞭原生組件提供的能力,降低瞭二次包裝的自定義組件的可用性。
因此更為好的方案是類型繼承,不僅繼承類型,也繼承屬性:

import React from "react";
import { Button, ButtonProps } from "@arco-design/web-react";

// 繼承類型
type IProps = ButtonProps & {};

const MyButton: React.FC<IProps> = (props) => {
  // 此處為自定義邏輯

  // 繼承屬性
  return <Button {...props} />;
};

export default MyButton;

2. 默認屬性

開源組件庫提供的組件滿足瞭日常項目的開發需求,但開源項目畢竟是面對公共場景,其組件默認值並不能匹配實際的業務場景,因此我們通常需要在二次包裝自定義組件的時候重置默認值。
常見的重置方案也是直接重置:

import React from "react";
import { Button, ButtonProps } from "@arco-design/web-react";

type IProps = ButtonProps & {};

const MyButton: React.FC<IProps> = (props) => {
  const { size = "large", type = "primary" } = props;
  return <Button size={size} type={type} {...props} />;
};

export default MyButton;

這種方式很直觀,也沒有任何問題。但追求極致的各位大佬們,肯定在重置多個自定義組件的過程中厭煩瞭這種重復的寫法。
因此更好的方式是使用高階組件,首先先定義一個通用的Hoc:

import React from "react";

/**
 * 組件默認屬性Hoc
 * @param defaultProps 默認屬性
 * @returns
 */
export function withDefaultProps<P = {}>(defaultProps?: Partial<P>) {
  return function (Component: any) {
    const WrappedComponent: React.FC<P> = (props) => {
      return React.createElement(Component, {
        ...defaultProps,
        ...props,
      });
    };

    WrappedComponent.displayName = `withDefaultProps(${getDisplayName(
      Component
    )})`;

    return WrappedComponent;
  };
}

這時候屬性重置就會變得有億點點優雅瞭:

import React from "react";
import { Button, ButtonProps } from "@arco-design/web-react";

type IProps = ButtonProps & {};

export default withDefaultProps<IProps>({
  size: "large",
  type: "primary",
})(Button);

通過組件默認屬性Hoc可以有效抽離包裝業務組件,優化代碼結構。

3. 自定義屬性與屬性透傳

二次包裝組件的當然不僅僅隻包含原生組件的屬性,還有新增的自定義屬性,例如下面定義的組件:

import React, { useEffect } from "react";
import { Button, ButtonProps } from "@arco-design/web-react";

type IProps = ButtonProps & {
  hello?: string;
};

const MyButton: React.FC<IProps> = (props) => {
  useEffect(() => console.log(props.hello), [props.hello]);

  return <Button {...props} />;
};

export default withDefaultProps<IProps>({
  hello: "world",
})(MyButton);

上述代碼中未過濾自定義屬性,可能會導致原生組件接收非聲明的屬性導致渲染異常(例如Antd組件會將未聲明屬性渲染為文檔標簽屬性,輸出控制臺錯誤)。
因此往往需要做自定義屬性過濾後再進行透傳,過濾方式包括不限於以下這些:

  • 通過拓展運算符
  • 通過omit函數

改造後代碼如下:

import React, { useEffect } from "react";
import { Button, ButtonProps } from "@arco-design/web-react";

type IProps = ButtonProps & {
  hello?: string;
};

/// 這裡通過拓展運算符過濾原生組件未聲明屬性
const MyButton: React.FC<IProps> = ({ hello, ...restProps }) => {
  useEffect(() => console.log(hello), [hello]);

  return <Button {...restProps} />;
};

export default withDefaultProps<IProps>({
  hello: "world",
})(MyButton);

到此這篇關於React組件二次包裝的具體實現的文章就介紹到這瞭,更多相關React組件二次包裝裝內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: