react?Table準(zhǔn)備Spin?Empty?ConfigProvider組件實(shí)現(xiàn)
前言
繼續(xù)搞react組件庫,該寫table了,學(xué)習(xí)了arco design的table的運(yùn)行流程,發(fā)現(xiàn)準(zhǔn)備工作還是挺多的,我們就先解決以下問題吧!
- 實(shí)現(xiàn)ConfigProvider

比如你要配置國際化,組件庫的所有組件都要共享當(dāng)前語言的變量,比如是中文,還是英文,這樣組件才能渲染對應(yīng)國家的字符串。
也就是說,你自己的組件庫有什么想全局共享的變量,就寫在這個(gè)組件里。
table使用的地方
const {
getPrefixCls, // 獲取css前綴
loadingElement, // loading顯示的組件
size: ctxSize, // size默認(rèn)值
renderEmpty, // 空數(shù)據(jù)時(shí)Empty組件顯示的內(nèi)容
componentConfig, // 全局component的config
} = useContext(ConfigContext);
我簡單解釋一下,getPrefixCls獲取了組件的css前綴,比如arco deisgn 的前綴自然是arco了,他們的組件的所有css都會加上這個(gè)前綴,現(xiàn)在組件庫都這么玩。
其他的就不詳細(xì)描述了,比如table請求數(shù)據(jù)有l(wèi)oading,你想自定義loading樣式可以在loadingElement屬性上配置等等,也就是說全局你自定義的loading組件,所有組件都會共享,不用你一個(gè)一個(gè)去配置了。
而這里的 useContext(ConfigContext) ConfigContext就是ConfigProvider組件創(chuàng)建的context,類似這樣(細(xì)節(jié)不用糾結(jié),后面我們會實(shí)現(xiàn)這個(gè)組件):
export const ConfigContext = createContext<ConfigProviderProps>({
getPrefixCls: (componentName: string, customPrefix?: string) => `${customPrefix || defaultProps.prefixCls}-${componentName}`,
...defaultProps,
});
<ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>;
Spin組件就是顯示loading態(tài)的組件,這里改造了arco的Spin組件,主要添加了樣式層,我認(rèn)可將樣式層和js控制的html,也就是jsx分層
主要體現(xiàn)在,組件里新增getClassnames和getStyles兩個(gè)函數(shù),配合css,收斂所有組件的樣式。
在復(fù)雜組件里,我還會嘗試收斂數(shù)據(jù)層和渲染層,但是spin組件和后面的empty組件太簡單了,就沒有做這步

在table中這樣使用
<Spin element={loadingElement} {...loading}>
{renderTable()}
</Spin>
table組件沒有數(shù)據(jù)的時(shí)候就會顯示它

這篇基本全是代碼,大家簡單看看就好,重點(diǎn)是下一篇將table組件,這里主要是做個(gè)記錄
目錄結(jié)構(gòu)
├── ConfigProvider
│ ├── config // 配置文件
│ │ ├── constants.tsx // 常量
│ │ └── utils_fns // 工具函數(shù)文件夾
│ ├── index.tsx
│ └── interface.ts // ts定義文件
├── Empty
│ ├── config // 配置文件
│ │ ├── constants.ts
│ │ └── utils_fns // 工具函數(shù)文件夾
│ │ ├── getDesDefault.ts
│ │ ├── xxx
│ │ └── index.ts
│ ├── index.tsx
│ ├── interface.ts // ts定義文件
│ └── style // 樣式文件
│ ├── index.less
│ └── index.ts
├── Icon // Icon是單獨(dú)一個(gè)項(xiàng)目,自動化生成Icon,還有點(diǎn)復(fù)雜度的,這個(gè)后面組件庫詳細(xì)講吧
│ ├── index.tsx
│ └── style
│ └── index.less
├── Spin
│ ├── config
│ │ ├── hooks // 自定義hook
│ │ └── utils_fns
│ ├── index.tsx
│ ├── interface.ts
│ └── style
│ ├── index.less
│ └── index.ts
├── Table
│ ├── config
│ │ └── util_fns
│ └── table.tsx
├── config // 公共配置文件
│ ├── index.ts
│ └── util_fns
│ ├── index.ts
│ └── pickDataAttributes.ts
├── index.ts
├── locale // 國際化文件夾
│ ├── default.tsx
│ ├── en-US.tsx
│ ├── interface.tsx
│ └── zh-CN.tsx
└── style // 樣式文件夾
├── base.less
├── common.less
├── index.less
├── normalize.less
└── theme
開搞ConfigProvider
index.tsx,詳情見注釋
import React, { createContext, useCallback, useMemo } from 'react';
// omit相當(dāng)于lodash里的omit,不過自己寫的性能更好,因?yàn)闆]有那么多兼容性,很簡單
// useMergeProps是合并外界傳入的props,和默認(rèn)props還有組件全局props的hook
import { omit, useMergeProps } from '@mx-design/utils';
// 國際化文件,默認(rèn)是中文
import defaultLocale from '../locale/default';
// 接口
import type { ConfigProviderProps } from './interface';
// componentConfig是空對象
// PREFIX_CLS是你想自定義的css樣式前綴
import { componentConfig, PREFIX_CLS } from './config/constants';
// 渲染空數(shù)據(jù)的組件
import { renderEmpty } from './config/utils_fns';
// 默認(rèn)參數(shù)
const defaultProps: ConfigProviderProps = {
locale: defaultLocale,
prefixCls: PREFIX_CLS,
getPopupContainer: () => document.body,
size: 'default',
renderEmpty,
};
// 默認(rèn)參數(shù)
export const ConfigContext = createContext<ConfigProviderProps>({
...defaultProps,
});
function ConfigProvider(baseProps: ConfigProviderProps) {
// 合并props,baseProps也就是用戶傳入的props優(yōu)先級最高
const props = useMergeProps<ConfigProviderProps>(baseProps, defaultProps, componentConfig);
const { prefixCls, children } = props;
// 獲取css前綴名的函數(shù)
const getPrefixCls = useCallback(
(componentName: string, customPrefix?: string) => {
return `${customPrefix || prefixCls || defaultProps.prefixCls}-${componentName}`;
},
[prefixCls]
);
// 傳遞給所有子組件的數(shù)據(jù)
const config: ConfigProviderProps = useMemo(
() => ({
...omit(props, ['children']),
getPrefixCls,
}),
[getPrefixCls, props]
);
// 使用context實(shí)現(xiàn)全局變量傳遞給子組件的目的
return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>;
}
ConfigProvider.displayName = 'ConfigProvider';
export default ConfigProvider;
export type { ConfigProviderProps };
注意在default中,有個(gè)renderEmpty函數(shù),實(shí)現(xiàn)如下:
export function renderEmpty() {
return <Empty />;
}
所以,我們接著看Empty組件如何實(shí)現(xiàn)
這里順便貼一下ConfigProvider中的類型定義,因?yàn)槌跗诮M件比較少,參數(shù)不多,大多數(shù)從arco deisgn源碼copy的
import { ReactNode } from 'react';
import { Locale } from '../locale/interface';
import type { EmptyProps } from '../Empty/interface';
import type { SpinProps } from '../Spin/interface';
export type ComponentConfig = {
Empty: EmptyProps;
Spin: SpinProps;
};
/**
* @title ConfigProvider
*/
export interface ConfigProviderProps {
/**
* @zh 用于全局配置所有組件的默認(rèn)參數(shù)
* @en Default parameters for global configuration of all components
* @version 2.23.0
*/
componentConfig?: ComponentConfig;
/**
* @zh 設(shè)置語言包
* @en Language package setting
*/
locale?: Locale;
/**
* @zh 配置組件的默認(rèn)尺寸,只會對支持`size`屬性的組件生效。
* @en Configure the default size of the component, which will only take effect for components that support the `size` property.
* @defaultValue default
*/
size?: 'mini' | 'small' | 'default' | 'large';
/**
* @zh 全局組件類名前綴
* @en Global ClassName prefix
* @defaultValue arco
*/
prefixCls?: string;
getPrefixCls?: (componentName: string, customPrefix?: string) => string;
/**
* @zh 全局彈出框掛載的父級節(jié)點(diǎn)。
* @en The parent node of the global popup.
* @defaultValue () => document.body
*/
getPopupContainer?: (node: HTMLElement) => Element;
/**
* @zh 全局的加載中圖標(biāo),作用于所有組件。
* @en Global loading icon.
*/
loadingElement?: ReactNode;
/**
* @zh 全局配置組件內(nèi)的空組件。
* @en Empty component in component.
* @version 2.10.0
*/
renderEmpty?: (componentName?: string) => ReactNode;
zIndex?: number;
children?: ReactNode;
}
Empty組件實(shí)現(xiàn)
index.tsx
import React, { memo, useContext, forwardRef } from 'react';
import { useMergeProps } from '@mx-design/utils';
import { ConfigContext } from '../ConfigProvider';
import type { EmptyProps } from './interface';
import { emptyImage, getDesDefault } from './config/utils_fns';
import { useClassNames } from './config/hooks';
function Empty(baseProps: EmptyProps, ref) {
// 獲取全局參數(shù)
const { getPrefixCls, locale: globalLocale, componentConfig } = useContext(ConfigContext);
// 合并props
const props = useMergeProps<EmptyProps>({}, componentConfig?.Empty, baseProps);
const { style, className, description, icon, imgSrc } = props;
// 獲取國際化的 noData字符串
const { noData } = globalLocale.Empty;
// class樣式層
const { containerCls, wrapperCls, imageCls, descriptionCls } = useClassNames({ getPrefixCls, className });
// 獲取描述信息
const alt = getDesDefault(description);
return (
<div ref={ref} className={containerCls} style={style}>
<div className={wrapperCls}>
<div className={imageCls}>{emptyImage({ imgSrc, alt, icon })}</div>
<div className={descriptionCls}>{description || noData}</div>
</div>
</div>
);
}
const EmptyComponent = forwardRef(Empty);
EmptyComponent.displayName = 'Empty';
export default memo(EmptyComponent);
export type { EmptyProps };
useClassNames,主要是通過useMemo緩存所有的className,一般情況下,這些className都不會變
import { cs } from '@mx-design/utils';
import { useMemo } from 'react';
import { ConfigProviderProps } from '../../../ConfigProvider';
import { EmptyProps } from '../..';
interface getClassNamesProps {
getPrefixCls: ConfigProviderProps['getPrefixCls'];
className: EmptyProps['className'];
}
export function useClassNames(props: getClassNamesProps) {
const { getPrefixCls, className } = props;
const prefixCls = getPrefixCls('empty');
const classNames = cs(prefixCls, className);
return useMemo(
() => ({
containerCls: classNames,
wrapperCls: `${prefixCls}-wrapper`,
imageCls: `${prefixCls}-image`,
descriptionCls: `${prefixCls}-description`,
}),
[classNames, prefixCls]
);
}
getDesDefault,
import { DEFAULT_DES } from '../constants';
export function getDesDefault(description) {
return typeof description === 'string' ? description : DEFAULT_DES;
}
getEmptyImage
import { IconEmpty } from '@mx-design/icon';
import React from 'react';
import { IEmptyImage } from '../../interface';
export const emptyImage: IEmptyImage = ({ imgSrc, alt, icon }) => {
return imgSrc ? <img alt={alt} src={imgSrc} /> : icon || <IconEmpty />;
};
Spin組件
也很簡單,值得一提的是,你知道寫一個(gè)debounce函數(shù)怎么寫嗎,很多網(wǎng)上的人寫的簡陋不堪,起碼還是有個(gè)cancel方法,好吧,要不你useEffect想在組件卸載的時(shí)候,清理debounce的定時(shí)器都沒辦法。
debounce實(shí)現(xiàn)
interface IDebounced<T extends (...args: any) => any> {
cancel: () => void;
(...args: any[]): ReturnType<T>;
}
export function debounce<T extends (...args: any) => any>(func: T, wait: number, immediate?: boolean): IDebounced<T> {
let timeout: number | null;
let result: any;
const debounced: IDebounced<T> = function (...args) {
const context = this;
if (timeout) clearTimeout(timeout);
if (immediate) {
let callNow = !timeout;
timeout = window.setTimeout(function () {
timeout = null;
}, wait);
if (callNow) result = func.apply(context, args);
} else {
timeout = window.setTimeout(function () {
result = func.apply(context, args);
}, wait);
}
// Only the first time you can get the result, that is, immediate is true
// if not,result has little meaning
return result;
};
debounced.cancel = function () {
clearTimeout(timeout!);
timeout = null;
};
return debounced;
}
順便我們在寫一個(gè)useDebounce的hook吧,項(xiàng)目中也要用
import { debounce } from '@mx-design/utils';
import { useCallback, useEffect, useState } from 'react';
import type { SpinProps } from '../../interface';
interface debounceLoadingProps {
delay: SpinProps['delay'];
propLoading: SpinProps['loading'];
}
export const useDebounceLoading = function (props: debounceLoadingProps): [boolean] {
const { delay, propLoading } = props;
const [loading, setLoading] = useState<boolean>(delay ? false : propLoading);
const debouncedSetLoading = useCallback(debounce(setLoading, delay), [delay]);
const getLoading = delay ? loading : propLoading;
useEffect(() => {
delay && debouncedSetLoading(propLoading);
return () => {
debouncedSetLoading?.cancel();
};
}, [debouncedSetLoading, delay, propLoading]);
return [getLoading];
};
index.tsx
import React, { useContext } from 'react';
import { useMergeProps } from '@mx-design/utils';
import { ConfigContext } from '../ConfigProvider';
import type { SpinProps } from './interface';
import InnerLoading from './InnerLoading';
import { useClassNames, useDebounceLoading } from './config/hooks';
function Spin(baseProps: SpinProps, ref) {
const { getPrefixCls, componentConfig } = useContext(ConfigContext);
const props = useMergeProps<SpinProps>(baseProps, {}, componentConfig?.Spin);
const { style, className, children, loading: propLoading, size, icon, element, tip, delay, block = true } = props;
const [loading] = useDebounceLoading({ delay, propLoading });
const { prefixCls, wrapperCls, childrenWrapperCls, loadingLayerCls, loadingLayerInnerCls, tipCls } = useClassNames({
getPrefixCls,
block,
loading,
tip,
children,
className,
});
return (
<div ref={ref} className={wrapperCls} style={style}>
{children ? (
<>
<div className={childrenWrapperCls}>{children}</div>
{loading && (
<div className={loadingLayerCls} style={{ fontSize: size }}>
<span className={loadingLayerInnerCls}>
<InnerLoading prefixCls={prefixCls} icon={icon} size={size} element={element} tipCls={tipCls} tip={tip} />
</span>
</div>
)}
</>
) : (
<InnerLoading prefixCls={prefixCls} icon={icon} size={size} element={element} tipCls={tipCls} tip={tip} />
)}
</div>
);
}
const SpinComponent = React.forwardRef<unknown, SpinProps>(Spin);
SpinComponent.displayName = 'Spin';
export default SpinComponent;
export { SpinProps };
LoadingIcon.tsx
import { IconLoading } from '@mx-design/icon';
import { cs } from '@mx-design/utils';
import React, { FC, ReactElement } from 'react';
import { ConfigProviderProps } from '../../../ConfigProvider';
import type { SpinProps } from '../../interface';
interface loadingIconProps {
prefixCls: ConfigProviderProps['prefixCls'];
icon: SpinProps['icon'];
size: SpinProps['size'];
element: SpinProps['element'];
}
export const LoadingIcon: FC<loadingIconProps> = function (props) {
const { prefixCls, icon, size, element } = props;
return (
<span className={`${prefixCls}-icon`}>
{icon
? // 這里可以讓傳入的icon自動旋轉(zhuǎn)
React.cloneElement(icon as ReactElement, {
className: `${prefixCls}-icon-loading`,
style: {
fontSize: size,
},
})
: element || <IconLoading className={`${prefixCls}-icon-loading`} style={{ fontSize: size }} />}
</span>
);
};以上就是react Table準(zhǔn)備Spin Empty ConfigProvider組件實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于react Table組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解react native頁面間傳遞數(shù)據(jù)的幾種方式
這篇文章主要介紹了詳解react native頁面間傳遞數(shù)據(jù)的幾種方式,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-11-11
Rect Intersection判斷兩個(gè)矩形是否相交
這篇文章主要為大家介紹了Rect Intersection判斷兩個(gè)矩形是否相交的算法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
jsoneditor二次封裝實(shí)時(shí)預(yù)覽json編輯器組件react版
這篇文章主要為大家介紹了jsoneditor二次封裝實(shí)時(shí)預(yù)覽json編輯器組件react版示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
React實(shí)現(xiàn)點(diǎn)擊切換組件效果
這篇文章主要為大家詳細(xì)介紹了如何基于React實(shí)現(xiàn)點(diǎn)擊切換組件效果,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以學(xué)習(xí)一下2023-08-08
React-Native左右聯(lián)動List的示例代碼
本篇文章主要介紹了React-Native左右聯(lián)動List的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
react native實(shí)現(xiàn)往服務(wù)器上傳網(wǎng)絡(luò)圖片的實(shí)例
下面小編就為大家?guī)硪黄猺eact native實(shí)現(xiàn)往服務(wù)器上傳網(wǎng)絡(luò)圖片的實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08

