react?Table準備Spin?Empty?ConfigProvider組件實現(xiàn)
前言
繼續(xù)搞react組件庫,該寫table了,學(xué)習(xí)了arco design的table的運行流程,發(fā)現(xiàn)準備工作還是挺多的,我們就先解決以下問題吧!
- 實現(xiàn)ConfigProvider
比如你要配置國際化,組件庫的所有組件都要共享當前語言的變量,比如是中文,還是英文,這樣組件才能渲染對應(yīng)國家的字符串。
也就是說,你自己的組件庫有什么想全局共享的變量,就寫在這個組件里。
table使用的地方
const { getPrefixCls, // 獲取css前綴 loadingElement, // loading顯示的組件 size: ctxSize, // size默認值 renderEmpty, // 空數(shù)據(jù)時Empty組件顯示的內(nèi)容 componentConfig, // 全局component的config } = useContext(ConfigContext);
我簡單解釋一下,getPrefixCls獲取了組件的css前綴,比如arco deisgn 的前綴自然是arco了,他們的組件的所有css都會加上這個前綴,現(xiàn)在組件庫都這么玩。
其他的就不詳細描述了,比如table請求數(shù)據(jù)有l(wèi)oading,你想自定義loading樣式可以在loadingElement屬性上配置等等,也就是說全局你自定義的loading組件,所有組件都會共享,不用你一個一個去配置了。
而這里的 useContext(ConfigContext)
ConfigContext就是ConfigProvider組件創(chuàng)建的context,類似這樣(細節(jié)不用糾結(jié),后面我們會實現(xiàn)這個組件):
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組件,主要添加了樣式層,我認可將樣式層和js控制的html,也就是jsx分層
主要體現(xiàn)在,組件里新增getClassnames和getStyles兩個函數(shù),配合css,收斂所有組件的樣式。
在復(fù)雜組件里,我還會嘗試收斂數(shù)據(jù)層和渲染層,但是spin組件和后面的empty組件太簡單了,就沒有做這步
在table中這樣使用
<Spin element={loadingElement} {...loading}> {renderTable()} </Spin>
table組件沒有數(shù)據(jù)的時候就會顯示它
這篇基本全是代碼,大家簡單看看就好,重點是下一篇將table組件,這里主要是做個記錄
目錄結(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是單獨一個項目,自動化生成Icon,還有點復(fù)雜度的,這個后面組件庫詳細講吧 │ ├── 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相當于lodash里的omit,不過自己寫的性能更好,因為沒有那么多兼容性,很簡單 // useMergeProps是合并外界傳入的props,和默認props還有組件全局props的hook import { omit, useMergeProps } from '@mx-design/utils'; // 國際化文件,默認是中文 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'; // 默認參數(shù) const defaultProps: ConfigProviderProps = { locale: defaultLocale, prefixCls: PREFIX_CLS, getPopupContainer: () => document.body, size: 'default', renderEmpty, }; // 默認參數(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實現(xiàn)全局變量傳遞給子組件的目的 return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>; } ConfigProvider.displayName = 'ConfigProvider'; export default ConfigProvider; export type { ConfigProviderProps };
注意在default中,有個renderEmpty函數(shù),實現(xiàn)如下:
export function renderEmpty() { return <Empty />; }
所以,我們接著看Empty組件如何實現(xiàn)
這里順便貼一下ConfigProvider中的類型定義,因為初期組件比較少,參數(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 用于全局配置所有組件的默認參數(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 配置組件的默認尺寸,只會對支持`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é)點。 * @en The parent node of the global popup. * @defaultValue () => document.body */ getPopupContainer?: (node: HTMLElement) => Element; /** * @zh 全局的加載中圖標,作用于所有組件。 * @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組件實現(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組件
也很簡單,值得一提的是,你知道寫一個debounce函數(shù)怎么寫嗎,很多網(wǎng)上的人寫的簡陋不堪,起碼還是有個cancel方法,好吧,要不你useEffect想在組件卸載的時候,清理debounce的定時器都沒辦法。
debounce實現(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; }
順便我們在寫一個useDebounce的hook吧,項目中也要用
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準備Spin Empty ConfigProvider組件實現(xiàn)的詳細內(nèi)容,更多關(guān)于react Table組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解react native頁面間傳遞數(shù)據(jù)的幾種方式
這篇文章主要介紹了詳解react native頁面間傳遞數(shù)據(jù)的幾種方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11jsoneditor二次封裝實時預(yù)覽json編輯器組件react版
這篇文章主要為大家介紹了jsoneditor二次封裝實時預(yù)覽json編輯器組件react版示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10React-Native左右聯(lián)動List的示例代碼
本篇文章主要介紹了React-Native左右聯(lián)動List的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09react native實現(xiàn)往服務(wù)器上傳網(wǎng)絡(luò)圖片的實例
下面小編就為大家?guī)硪黄猺eact native實現(xiàn)往服務(wù)器上傳網(wǎng)絡(luò)圖片的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08