开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天, 点击查看活动详情

最近公司业务不是那么多,就抽空写了下组件库的东西,然后看了很多组件库的源码,由于我这里封装的主要是 taro 移动端的组件,所以主要是参考了 antd-mobile , react-vant , tard 等组件库。

然后根据他们的源码,整合出一套自己比较喜欢的组件封装写法,分享给大家。

文末附上组件快捷代码片段

例:封装一个计数器组件

react 中没有像 vue 那样的 scope ,首先一个组件需要防止类名和其他的重复。

定义一个类名前缀 这里可以统一命名一个自己喜欢的开头,我这里就叫 com

const classPrefix = `com-count`;

html 中可以这样使用,withNativeProps 的详细描述放后面了(可先忽略)

  return withNativeProps(
    <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/>

完整案例使用

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(
    <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>;
    
    /** 删除一个对象中的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;
    

    配置用户代码片段

    这里输入名称 typescriptreact 创建就可以了。

    往里面加入以下 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"
    

    如果有大佬看到这里,希望能给点意见和改进方法。

  •