天天看點

React 封裝元件的一些心得(一)

起因

最近公司業務不是那麼多,就抽空寫了下元件庫的東西,然後看了很多元件庫的源碼,由于我這裡封裝的主要是

taro

移動端的元件,是以主要是參考了

antd-mobile

,

react-vant

,

tard

等元件庫。

然後根據他們的源碼,整合出一套自己比較喜歡的元件封裝寫法,分享給大家。

文末附上元件快捷代碼片段

例:封裝一個計數器元件

樣式

react

中沒有像

vue

那樣的

scope

,首先一個元件需要防止類名和其他的重複。

定義一個類名字首 這裡可以統一命名一個自己喜歡的開頭,我這裡就叫

com

html

中可以這樣使用,

withNativeProps

的較長的描述放後面了(可先忽略)

return withNativeProps(
    ret,
    <div className={classPrefix}>
      <h4 className={`${classPrefix}-title`}>{title}</h4>
      <div className={`${classPrefix}-count`}>{c}</div>
      <button onClick={() => {setC(c => ++c)}}>+1</button>
    </div>
  )
           

index.less

檔案中

// @import '@/style/index.less'; // 這裡可以引入全局的一些樣式
@class-prefix: ~'com-count';
.@{class-prefix} {
  width: 100px;
  background-color: #f2f2f2;
  &-title {
    font-weight: bold;
  }
  &-count {
    color: skyblue;
  }
}
           

props

生成元件的

props

類型,

NativeProps

類型的較長的描述放後面了(可先忽略)

export type CountProps = { 
  count: number
  title?: string
} & NativeProps
           

定義元件的預設值

const defaultProps = {
  title: '計數器',
}
type RequireType = keyof typeof defaultProps
           

props

的使用,

useMergeProps

就是用來合并

props

預設值的,較長的描述放後面了

const Count = (comProps: CountProps) => {
  const props = useMergeProps<CountProps, RequireType>(comProps, defaultProps)
  
  const { title, ...ret } = props
  
  return <div>{title}<div/>
}
           

完整案例使用

  • demo
import { useState } from "react";
import Count from ".."
export default () => {
  const [count, setCount] = useState(0);
  return (
    <Count count={count} className='count' style={{background: '#f2f2f2'}} />
  )
}
           
  • 元件
import React, { useState, useEffect } from 'react';
import './index.less';
import { NativeProps, withNativeProps } from '@/utils/native-props';
import useMergeProps from '@/hooks/use-merge-props';

const classPrefix = `com-count`;

// 元件 props
export type CountProps = { 
  count: number
  title?: string
} & NativeProps

// props 預設值
const defaultProps = {
  title: '計數器',
  count: 0,
}
type RequireType = keyof typeof defaultProps

const Count = (comProps: CountProps) => {
  // 合并 props
  const props = useMergeProps<CountProps, RequireType>(comProps, defaultProps)
  const { count, title, ...ret } = props

  const [c, setC] = useState(count);

  useEffect(() => {
    setC(count)
  }, [count])
  
  // withNativeProps 可以用來合并傳入的 classname 和 styles 等
  return withNativeProps(
    ret,
    <div className={classPrefix}>
      <h4 className={`${classPrefix}-title`}>{title}</h4>
      <div className={`${classPrefix}-count`}>{c}</div>
      <button onClick={() => {setC(c => ++c)}}>+1</button>
    </div>
  )
}

export default Count
           

utils 和 hooks 等的引入方式

NativeProps 和 withNativeProps

該方法的從

antd

元件庫的源碼中借鑒過來使用的。

import React from 'react';
import type { CSSProperties, ReactElement } from 'react';
import classNames from 'classnames';

// style 和 className 的類型,根據需要可以加其他東西,如 onClick 等
export type NativeProps<S extends string = never> = {
  className?: string;
  style?: CSSProperties & Partial<Record<S, string>>;
}

// 可以用來合并傳入的 classname 和 styles 等
export function withNativeProps<P extends NativeProps>(props: P, element: ReactElement) {
  const p = {
    ...element.props,
  };
  if (props.className) {
    p.className = classNames(element.props.className, props.className);
  }
  if (props.style) {
    p.style = {
      ...p.style,
      ...props.style,
    };
  }
  return React.cloneElement(element, p);
}
           
  • index.less
// @import '../style/index.less';
@class-prefix: ~'com-count';
.@{class-prefix} {
  width: 100px;
  &-title {
    font-weight: bold;
  }
  &-count {
    color: skyblue;
  }
}
           

useMergeProps

該鈎子是從

arco-design

借鑒過來改進的。

import { useMemo } from 'react';
import omit from '@/utils/omit';

export type MergePropsOptions = {
  _ignorePropsFromGlobal?: boolean;
};

/** 将某些屬性變為必選 */
type RequireKey<T, K extends keyof T> = Omit<T, K> & { [P in K]-?: T[P] }

export default function useMergeProps<PropsType, K extends keyof PropsType>(
  componentProps: PropsType & MergePropsOptions,
  defaultProps: Partial<PropsType>,
  globalComponentConfig: Partial<PropsType> = {}
): RequireKey<PropsType, K> {
  const { _ignorePropsFromGlobal } = componentProps;
  const _defaultProps = useMemo(() => {
    return { ...defaultProps, ...(_ignorePropsFromGlobal ? {} : globalComponentConfig) };
  }, [defaultProps, globalComponentConfig, _ignorePropsFromGlobal]);

  const props = useMemo(() => {
    const mProps = omit(componentProps, ['_ignorePropsFromGlobal']) as PropsType;

    for (const propName in _defaultProps) {
      if (mProps[propName] === undefined) {
        mProps[propName] = _defaultProps[propName]!;
      }
    }

    return mProps;
  }, [componentProps, _defaultProps]);

  return props as RequireKey<PropsType, K>;
}
           

omit

/** 删除一個對象中的key */
export default function omit<T extends object, K extends keyof T>(
  obj: T,
  keys: Array<K | string> // string 為了某些沒有聲明的屬性被omit
): Omit<T, K> {
  const clone = {
    ...obj,
  };
  keys.forEach((key) => {
    if ((key as K) in clone) {
      delete clone[key as K];
    }
  });
  return clone;
}

           

配置使用者代碼片段

React 封裝元件的一些心得(一)
React 封裝元件的一些心得(一)

配置位置

React 封裝元件的一些心得(一)

這裡輸入名稱 typescriptreact 建立就可以了。

React 封裝元件的一些心得(一)

往裡面加入以下

json

資料

"tsxcomreact": {
  "prefix": "tsxcomreact",
  "body": [
    "import React, { useState, useEffect } from 'react';",
    "import './index.less';",
    "import { NativeProps, withNativeProps } from '@/utils/native-props````
    "import useMergeProps from '@/hooks/use-merge-props';",
    "",
    "const classPrefix = `com${2}-${1}`;",
    "",
    "export type ${1}Props = { ",
    "",
    "} & NativeProps",
    "",
    "const defaultProps = {",
    "  ",
    "}",
    "type RequireType = keyof typeof defaultProps",
    "",
    "const ${1} = (comProps: ${1}Props) => {",
    "  const props = useMergeProps<${1}Props, RequireType>(comProps, defaultProps)",
    "  const { ...ret } = props",
    "  ",
    "  return withNativeProps(",
    "    ret,",
    "    <div className={classPrefix}>",
    "      ",
    "    </div>",
    "  )",
    "}",
    "",
    "export default ${1}"
  ],
  "description": "Log output to console"
},
"cdivclass": {
  "scope": "typescriptreact",
  "prefix": "cdc",
  "body": [
    "<div className={`\\${classPrefix}-${0}`}></div>"
  ],
  "description": "Log output to console"
},
           

結語

如果有大佬看到這裡,希望能給點意見和改進方法。

繼續閱讀