react?Table準(zhǔn)備Spin?Empty?ConfigProvider組件實(shí)現(xiàn)
前言
繼續(xù)搞react組件庫(kù),該寫(xiě)table了,學(xué)習(xí)了arco design的table的運(yùn)行流程,發(fā)現(xiàn)準(zhǔn)備工作還是挺多的,我們就先解決以下問(wèn)題吧!
- 實(shí)現(xiàn)ConfigProvider
比如你要配置國(guó)際化,組件庫(kù)的所有組件都要共享當(dāng)前語(yǔ)言的變量,比如是中文,還是英文,這樣組件才能渲染對(duì)應(yīng)國(guó)家的字符串。
也就是說(shuō),你自己的組件庫(kù)有什么想全局共享的變量,就寫(xiě)在這個(gè)組件里。
table使用的地方
const { getPrefixCls, // 獲取css前綴 loadingElement, // loading顯示的組件 size: ctxSize, // size默認(rèn)值 renderEmpty, // 空數(shù)據(jù)時(shí)Empty組件顯示的內(nèi)容 componentConfig, // 全局component的config } = useContext(ConfigContext);
我簡(jiǎn)單解釋一下,getPrefixCls獲取了組件的css前綴,比如arco deisgn 的前綴自然是arco了,他們的組件的所有css都會(huì)加上這個(gè)前綴,現(xiàn)在組件庫(kù)都這么玩。
其他的就不詳細(xì)描述了,比如table請(qǐng)求數(shù)據(jù)有l(wèi)oading,你想自定義loading樣式可以在loadingElement屬性上配置等等,也就是說(shuō)全局你自定義的loading組件,所有組件都會(huì)共享,不用你一個(gè)一個(gè)去配置了。
而這里的 useContext(ConfigContext)
ConfigContext就是ConfigProvider組件創(chuàng)建的context,類(lèi)似這樣(細(xì)節(jié)不用糾結(jié),后面我們會(huì)實(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ù)雜組件里,我還會(huì)嘗試收斂數(shù)據(jù)層和渲染層,但是spin組件和后面的empty組件太簡(jiǎn)單了,就沒(méi)有做這步
在table中這樣使用
<Spin element={loadingElement} {...loading}> {renderTable()} </Spin>
table組件沒(méi)有數(shù)據(jù)的時(shí)候就會(huì)顯示它
這篇基本全是代碼,大家簡(jiǎn)單看看就好,重點(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)目,自動(dòng)化生成Icon,還有點(diǎn)復(fù)雜度的,這個(gè)后面組件庫(kù)詳細(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 // 國(guó)際化文件夾 │ ├── default.tsx │ ├── en-US.tsx │ ├── interface.tsx │ └── zh-CN.tsx └── style // 樣式文件夾 ├── base.less ├── common.less ├── index.less ├── normalize.less └── theme
開(kāi)搞ConfigProvider
index.tsx,詳情見(jiàn)注釋
import React, { createContext, useCallback, useMemo } from 'react'; // omit相當(dāng)于lodash里的omit,不過(guò)自己寫(xiě)的性能更好,因?yàn)闆](méi)有那么多兼容性,很簡(jiǎn)單 // useMergeProps是合并外界傳入的props,和默認(rèn)props還有組件全局props的hook import { omit, useMergeProps } from '@mx-design/utils'; // 國(guó)際化文件,默認(rèn)是中文 import defaultLocale from '../locale/default'; // 接口 import type { ConfigProviderProps } from './interface'; // componentConfig是空對(duì)象 // 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)先級(jí)最高 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中的類(lèi)型定義,因?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è)置語(yǔ)言包 * @en Language package setting */ locale?: Locale; /** * @zh 配置組件的默認(rèn)尺寸,只會(huì)對(duì)支持`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 全局組件類(lèi)名前綴 * @en Global ClassName prefix * @defaultValue arco */ prefixCls?: string; getPrefixCls?: (componentName: string, customPrefix?: string) => string; /** * @zh 全局彈出框掛載的父級(jí)節(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; // 獲取國(guó)際化的 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,主要是通過(guò)useMemo緩存所有的className,一般情況下,這些className都不會(huì)變
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組件
也很簡(jiǎn)單,值得一提的是,你知道寫(xiě)一個(gè)debounce函數(shù)怎么寫(xiě)嗎,很多網(wǎng)上的人寫(xiě)的簡(jiǎn)陋不堪,起碼還是有個(gè)cancel方法,好吧,要不你useEffect想在組件卸載的時(shí)候,清理debounce的定時(shí)器都沒(méi)辦法。
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; }
順便我們?cè)趯?xiě)一個(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自動(dòng)旋轉(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組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解react native頁(yè)面間傳遞數(shù)據(jù)的幾種方式
這篇文章主要介紹了詳解react native頁(yè)面間傳遞數(shù)據(jù)的幾種方式,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11Rect Intersection判斷兩個(gè)矩形是否相交
這篇文章主要為大家介紹了Rect Intersection判斷兩個(gè)矩形是否相交的算法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06jsoneditor二次封裝實(shí)時(shí)預(yù)覽json編輯器組件react版
這篇文章主要為大家介紹了jsoneditor二次封裝實(shí)時(shí)預(yù)覽json編輯器組件react版示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10React實(shí)現(xiàn)點(diǎn)擊切換組件效果
這篇文章主要為大家詳細(xì)介紹了如何基于React實(shí)現(xiàn)點(diǎn)擊切換組件效果,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以學(xué)習(xí)一下2023-08-08React-Native左右聯(lián)動(dòng)List的示例代碼
本篇文章主要介紹了React-Native左右聯(lián)動(dòng)List的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09react native實(shí)現(xiàn)往服務(wù)器上傳網(wǎng)絡(luò)圖片的實(shí)例
下面小編就為大家?guī)?lái)一篇react native實(shí)現(xiàn)往服務(wù)器上傳網(wǎng)絡(luò)圖片的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08React之useEffect缺少依賴警告問(wèn)題及解決
這篇文章主要介紹了React之useEffect缺少依賴警告問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12