开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
起因
最近公司业务不是那么多,就抽空写了下组件库的东西,然后看了很多组件库的源码,由于我这里封装的主要是 taro
移动端的组件,所以主要是参考了 antd-mobile
, react-vant
, tard
等组件库。
然后根据他们的源码,整合出一套自己比较喜欢的组件封装写法,分享给大家。
文末附上组件快捷代码片段
例:封装一个计数器组件
样式
react
中没有像 vue
那样的 scope
,首先一个组件需要防止类名和其他的重复。
定义一个类名前缀 这里可以统一命名一个自己喜欢的开头,我这里就叫 com
了
const classPrefix = `com-count`;
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;
}
配置用户代码片段
配置位置
这里输入名称 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"
},
结语
如果有大佬看到这里,希望能给点意见和改进方法。