@d-matrix/utils v1.22.0
@d-matrix/utils
A dozen of utils for Front-End Development
API
clipboard
writeImage(element: HTMLImageElement | null | string): Promise<void>
复制图片到剪贴板
writeText(text: string): Promise<void>
复制文本到剪切板
react
render<P>(element: ReactElement<P>): Promise<string>
渲染React
组件,返回HTML字符串。
cleanup(): void
清理函数,需要在调用render()
函数后调用。
useDisableContextMenu(target: ContextMenuTarget = defaultContextMenuTarget): void
在target
函数返回的元素上禁用右键菜单,默认的target
是() => document
例1:在id
是test
的元素上禁用右键菜单
import { react } from '@d-matrix/utils';
const TestComp = () => {
react.useDisableContextMenu(() => document.getElementById('test'));
return (
<div>
<div id='test'>此元素的右键菜单被禁用</div>
</div>
)
}
例2:在document
上禁用右键菜单
const TestComp = () => {
react.useDisableContextMenu();
return <div>内容</div>
}
useStateCallback<T>(initialState: T): [T, (state: T, cb?: (state: T) => void) => void]
返回值setState()
函数类似类组件中的setState(updater[, callback])
,可以在callback
中获取更新后的state
useIsMounted(): () => boolean
获取当前组件是否已挂载的 Hook
const Test = () => {
const isMounted = useIsMounted();
useEffect(() => {
if (isMounted()) {
console.log('component mounted')
}
}, [isMounted]);
return null
};
useCopyToClipboard(props?: UseCopyToClipboardProps)
复制文本到剪切板, 用法见测试
EnhancedComponent.prototype.setStateAsync(state)
setState()
方法的同步版本
import { react } from '@d-matrix/utils';
class TestComponent extends EnhancedComponent<unknown, { pageIndex: number }> {
state = {
pageIndex: 1,
};
async onClick() {
await this.setStateAsync({ pageIndex: 2 });
console.log(this.state.pageIndex); // 2
}
render() {
return (
<button data-cy="test-button" onClick={() => this.onClick()}>
click
</button>
);
}
}
useDeepCompareRef(deps: DependencyList): React.MutableRefObject<number>
深比较deps
。返回ref
,ref.current
是一个自增数字,每次deps
变化,ref.current
加1
。用法见测试
InferRef<T>
推导子组件的ref
类型,适用于组件没有导出其ref
类型的场景, 更多用法见测试
interface ChildRefProps {
prop1: () => void;
prop2: () => void;
}
interface ChildProps {
otherProp: string;
}
const Child = React.forwardRef<ChildRefProps, ChildProps>((props, ref) => {
React.useImperativeHandle(
ref,
() => ({
prop1() {},
prop2() {},
}),
[],
);
return null;
});
type InferredChildRef = InferRef<typeof Child>; // 等价于ChildRefProps
const Parent = () => {
const childRef = React.useRef<InferredChildRef>(null);
return <Child ref={childRef} otherProp="a" />;
};
useForwardRef = <T>(ref: ForwardedRef<T>, initialValue: any = null): React.MutableRefObject<T>
解决使用React.forwardRef
后,在调用ref.current.someMethod()
时, 出现Property 'current' does not exist on type '(instance: HTMLInputElement | null) => void'
TS类型错误,具体问题见这里
const Input = React.forwardRef<HTMLInputElement, React.ComponentPropsWithRef<'input'>>((props, ref) => {
const forwardRef = useForwardRef<HTMLInputElement>(ref);
useEffect(() => {
forwardRef.current.focus();
});
return <input type="text" ref={forwardRef} value={props.value} />;
});
useMediaQuery(query, options?): boolean
使用Match Media API 检测当前document是否匹配media query
import { useMediaQuery } from '@d-matrix/utils/react'
export default function Component() {
const matches = useMediaQuery('(min-width: 768px)')
return (
<div>
{`The view port is ${matches ? 'at least' : 'less than'} 768 pixels wide`}
</div>
)
}
dom
scrollToTop(element: Element | null | undefined): void
元素滚动条滚动到顶部,对老旧浏览器做了兼容,见浏览器兼容性。
strip(html: string): string
从字符串中去除 HTML 标签并返回纯文本内容。
import { dom } from '@d-matrix/utils';
dom.strip('测试<em>高亮</em>测试'); // '测试高亮测试'
date
rangeOfYears(start: number, end: number = new Date().getFullYear()): number[]
创建start
和end
之间的年份数组。
getYears()
export interface YearOption {
label: string;
value: number;
}
export enum YearOptionKind {
Numbers,
Objects,
}
export type GetYearsOptions = {
// 开始年份
startYear?: number;
// 最近几年
recentYears?: number;
// 截止年份
endYear?: number;
// 后缀,默认为'年'
suffix?: string;
};
export function getYears(options: GetYearsOptions & { type: YearOptionKind.Numbers }): number[];
export function getYears(options: GetYearsOptions & { type: YearOptionKind.Objects }): YearOption[];
export function getYears(options: GetYearsOptions & { type: YearOptionKind }): number[] | YearOption[]
获取n年,type
传YearOptionKind.Numbers
,返回[2023, 2022, 2021]
数字数组;type
传YearOptionKind.Objects
,返回如下的对象数组
[
{ value: 2023, label: '2023年' },
{ value: 2022, label: '2022年' },
{ value: 2021, label: '2021年' },
]
更多用法,见测试用例
dayOfWeek(num: number, lang: keyof typeof i18n = 'zh'): string
返回星期几, lang
仅支持zh
和en
, num
必须为正整数,否则报错
dayOfWeek(0) // "日"
types
WithOptional<T, K extends keyof T>
type A = { a: number; b: number; c: number; };
type T0 = WithOptional<A, 'b' | 'c'>; // { a: number; b?: number; c?: number }
FunctionPropertyNames<T>
获取对象中的方法名称,返回union type
class A {
add() {}
minus() {}
div() {}
public result: number = 0;
}
type T0 = FunctionPropertyNames<A>; // 'add' | 'minus' | 'div'
const t1 = {
add() {},
minus() {},
div() {},
result: 0,
};
type T1 = FunctionPropertyNames<typeof t1>; // 'add' | 'minus' | 'div'
NonFunctionPropertyNames<T>
获取对象中非函数属性名称,返回union type
class A {
add() {}
minus() {}
div() {}
public result: number = 0;
}
type T0 = FunctionPropertyNames<A>; // 'result'
const t1 = {
add() {},
minus() {},
div() {},
result: 0,
};
type T1 = FunctionPropertyNames<typeof t1>; // 'result'
ValueOf<T>
获取对象中key
的值,返回由这些值组成的union type
const map = {
0: '0m',
1: '1m',
2: '2m',
3: '3m',
4: '4m',
5: '5m',
6: '6m',
} as const;
type T0 = ValueOf<typeof map>; // '0m' | '1m' | '2m' | '3m' | '4m' | '5m' | '6m'
WithRequired<T, K extends keyof T>
指定属性变为必选
type Input = {
a: number;
b?: string;
};
type Output = WithRequired<Input, 'b'> // { a: number; b: string }
algorithm
moveMulti<T extends unknown>(arr: T[], indexes: number[], start: number): T[]
移动多个元素到数组中指定的位置,用法,见测试用例
file
toImage(file: BlobPart | FileURL, options?: BlobPropertyBag): Promise<HTMLImageElement>
转换BlobPart或者文件地址为图片对象
validateImageSize(file: BlobPart | FileURL, limitSize: { width: number; height: number }, options?: BlobPropertyBag): Promise<ImageSizeValidationResult>
返回值:
interface ImageSizeValidationResult {
isOk: boolean;
width: number;
height: number;
}
图片宽,高校验
isImageExists(src: string, img: HTMLImageElement = new Image()): Promise<boolean>
检测图片地址是否可用
import { file } from '@d-matrix/utils';
const url = 'https://picsum.photos/200/300';
const res = await file.isImageExists(url);
传入HTML中已经存在的img
元素
import { file } from '@d-matrix/utils';
const $img = document.getElementById('img');
const res = await file.isImageExists(url, $img);
getFilenameFromContentDispositionHeader(header: { ['content-disposition']: string }): string
从Content-Disposition
response header中获取filename
import { file } from '@d-matrix/utils';
const header = {
'content-disposition': 'attachment;filename=%E5%A4%A7%E8%A1%8C%E6%8C%87%E5%AF%BC2024-06-27-2024-06-28.xlsx'
};
const filename = file.getFilenameFromContentDispositionHeader(header);
// '大行指导2024-06-27-2024-06-28.xlsx'
download(source: string | Blob, fileName = '', target?: HyperLinkTarget): void
文件下载,source
是文件地址或blob
对象。
type HyperLinkTarget = "_self" | "_blank" | "_parent" | "_top"
downloadFileByIframe(source: string): boolean
通过创建iframe
进行文件下载
support
isBrowserEnv(): boolean
是否是浏览器环境
isWebSocket(): boolean
是否支持WebSocket
isSharedWorker(): boolean
是否支持SharedWorker
timer
sleep(ms?: number): Promise<unknown>
使用setTimeout
与Promise
实现,暂停执行ms
毫秒
await sleep(3000); // 暂停3秒
console.log('continue'); // 继续执行
operator
trueTypeOf = (obj: unknown): string
检查数据类型
trueTypeOf([]); // array
trueTypeOf({}); // object
trueTypeOf(''); // string
trueTypeOf(new Date()); // date
trueTypeOf(1); // number
trueTypeOf(function () {}); // function
trueTypeOf(/test/i); // regexp
trueTypeOf(true); // boolean
trueTypeOf(null); // null
trueTypeOf(undefined); // undefined
decimal
format(value: number | string | undefined | null, options?: FormatOptions): string
格式化数字,默认保留3位小数,可添加前缀,后缀,默认值为'--',用法见测试
type FormatOptions = {
decimalPlaces?: number | false;
suffix?: string;
prefix?: string;
defaultValue?: string;
operation?: {
operator: 'add' | 'sub' | 'mul' | 'div' | 'toDecimalPlaces';
value: number;
}[];
};
object
removeZeroValueKeys = <T extends Record<string, any>>(obj: T, zeroValues = ZeroValues): T
移除零值的键, 默认的零值是:undefined
、null
, ''
, NaN
, []
, {}
removeZeroValueKeys({ a: '', b: 'abc', c: undefined, d: null, e: NaN, f: -1, g: [], h: {} })
// { b: 'abc', f: -1 }
typedKeys(obj: T): Array<keyof T>
返回tuple
,而不是string[]
const obj = { a: 1, b: '2' };
Object.keys(obj) // string[]
object.typedKeys({ a: 1, b: '2' }) // ('a' | 'b')[]
array
moveImmutable<T>(array: T[], fromIndex: number, toIndex: number): T[]
import { array } from '@d-matrix/utils';
const input = ['a', 'b', 'c'];
const array1 = array.moveImmutable(input, 1, 2);
console.log(array1);
//=> ['a', 'c', 'b']
const array2 = array.moveImmutable(input, -1, 0);
console.log(array2);
//=> ['c', 'a', 'b']
const array3 = array.moveImmutable(input, -2, -3);
console.log(array3);
//=> ['b', 'a', 'c']
moveMutable<T>(array: T[], fromIndex: number, toIndex: number): void
moveToStart<T>(array: T[], predicate: (item: T) => boolean): T[]
移动元素到数组首位,不会修改原数组
import { array } from '@d-matrix/utils';
const list = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }];
const newList = array.moveToStart(list, (item) => item.id === 4);
// [{ id: 4 }, { id: 1 }, { id: 2 }, { id: 3 }, { id: 5 }]
number
randomInt(min: number, max: number): number
返回min
, max
之间的随机整数
echarts
mergeOption(defaults: EChartsOption, overrides: EChartsOption, option?: deepmerge.Options): EChartsOption
deep merge Echarts配置,用法见测试用例
fill<T extends Record<string, any>, XAxisField extends keyof T, YAxisField extends keyof T>(dataSource: T[], xAxisField:XAxisField, yAxisField: YAxisField): T[]
场景:后端接口返回某几个时间点的数据,需求是在接口数据的基础上每隔5分钟补一个点,以达到图中的效果: 折线图。
填充的点的Y轴值为前一个点的值, 时间示例: 9:23, 9:27 => 9:23, 9:25, 9:27, 9:30,更多,见测试用例
calcYAxisRange<T extends Record<string, any>, Key extends keyof T>(data: T[], key: Key, decimalPlaces = 2, splitNumber = 5): { max:number; min:number }
计算echarts YAxis的max和min属性,以达到根据实际数据动态调整,使折线图的波动明显。且第一个点始终在Y轴中间位置,效果图
测试
运行全部组件测试
npm run cy:run -- --component
运行单个组件测试
npm run cy:run -- --component --spec tests/date.cy.ts
运行E2E测试
将src
通过tsc
build到public/dist
目录
npm run build:public
启动一个Web服务器来访问public/index.html
文件,dist
目录的脚本可以通过<script type="module"/>
引入
npm run serve
最后启动cypress GUI客户端,选择E2E测试
npm run cy:open
发布
更新package version:
npm version <minor> or <major>...
构建:
npm build
发布:
npm publish --access public
网络原因导致连接registry服务器超时,可指定proxy
npm --proxy http://127.0.0.1:7890 publish
镜像站查询版本与手动同步:
通过git log
命令获取changelogs,用于填写GitHub Release内容:
git log --oneline --decorate
注意事项
8 months ago
10 months ago
10 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
10 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
1 year ago
10 months ago
10 months ago
11 months ago
11 months ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago