antd4里table滾動(dòng)的實(shí)現(xiàn)
首先antd4的table的底層實(shí)現(xiàn)是rc-table,就從rc-table來(lái)看看。
一、rc-table里Header、Footer、TableBody實(shí)現(xiàn)保持同頻滾動(dòng)的方法
場(chǎng)景:Table內(nèi)容區(qū)域大于容器Table寬度,并且Table設(shè)置了scrollX,Header、Footer都有, 才關(guān)注同頻滾動(dòng)
那么是如何實(shí)現(xiàn)的?
- 監(jiān)聽(tīng)onScroll方法獲取到滾動(dòng)條向左的滾動(dòng)的距離scrollLeft;
- 同時(shí)給三個(gè)dom設(shè)置scrollLeft
二、 rc-table里的onScroll實(shí)現(xiàn)
先看一般的onScroll實(shí)現(xiàn)
- 監(jiān)聽(tīng)onScroll獲取scrollLeft
- 設(shè)置header、footer、tableBody的scrollLeft
下面是偽代碼哈
const onScroll = (e: ScrollEvent) => { // 拿到scrollLeft const scrollLeft = e.target.scrollLeft // 給所有的header、footer、table-body設(shè)置scrollLeft header.scrollLeft = scrollLeft footer.scrollLeft = scrollLeft tableBody.scrollLeft = scrollLeft }
源碼里onScroll的實(shí)現(xiàn)
?const onScroll = ({ ? ? currentTarget, ? ? scrollLeft, ? }: { ? ? currentTarget: HTMLElement; ? ? scrollLeft?: number; ? }) => { ? ? const mergedScrollLeft = typeof scrollLeft === 'number' ? scrollLeft : currentTarget.scrollLeft; ? ? const compareTarget = currentTarget || EMPTY_SCROLL_TARGET;? ? ? if (!getScrollTarget() || getScrollTarget() === compareTarget) {? ? ? ? setScrollTarget(compareTarget); ? ? ? //一個(gè) 滾動(dòng)需要 控制 header、body、summary、stickyScrollBar所有同步滾動(dòng) ?? ??? ??? ?// header設(shè)置scrollLeft ?? ??? ??? ?scrollHeaderRef.current = mergedScrollLeft ?? ??? ??? ?// body 設(shè)置scrollLeft ?? ??? ??? ?scrollBodyRef.current = mergedScrollLeft ? ? } ? };
對(duì)比兩個(gè)的實(shí)現(xiàn),可以看到rc-table里的實(shí)現(xiàn)多了一個(gè)入?yún)crollLeft和一個(gè)if判斷;
為什么多了一個(gè)入?yún)?、一個(gè)判斷?繼續(xù)往下看?
三、 Header、Footer的滾動(dòng)監(jiān)聽(tīng)
- 用組件FixedHolder實(shí)現(xiàn),給FixedHolder綁定ref;
- 監(jiān)聽(tīng)的是onWheel, 不是onScroll;
為什么監(jiān)聽(tīng)onWheel不是onScroll?
React.useEffect(() => { ? ? ? function onWheel(e: WheelEvent) { ? ? ? ? // deltaX: Returns a double representing the horizontal scroll amount ? ? ? ? const { currentTarget, deltaX } = e as unknown as React.WheelEvent<HTMLDivElement>; ? ? ? ? // 避免觸發(fā)不必要滾動(dòng), 是一種優(yōu)化 ? ? ? ? if (deltaX) { ? ? ? ? ? onScroll({ currentTarget, scrollLeft: currentTarget.scrollLeft + deltaX }); ? ? ? ? ? e.preventDefault(); ? ? ? ? } ? ? ? } ? ? ? fixHolder.current?.addEventListener('wheel', onWheel); ? ? ? return () => { ? ? ? ? fixHolder.current?.removeEventListener('wheel', onWheel); ? ? ? }; ? ? }, []);
不要將 onscroll 與 onwheel混淆。onwheel 是鼠標(biāo)滾輪旋轉(zhuǎn),而 onscroll 處理的是對(duì)象內(nèi)部?jī)?nèi)容區(qū)的滾動(dòng)事件。
當(dāng)dom滿足下面任意一條的時(shí)候,不會(huì)觸發(fā)onScroll;
- overflow:hidden
- 滾動(dòng)條不存在
FixHolder組件
設(shè)置了樣式overflow:hidden;
<div style={{ overflow: 'hidden', ...(isSticky ? { top: stickyTopOffset, bottom: stickyBottomOffset } : {}), }} ref={setScrollRef} className={classNames(className, { [stickyClassName]: !!stickyClassName, })} /> <table style={{ tableLayout: 'fixed', visibility: noData || mergedColumnWidth ? null : 'hidden', }} > {(!noData || !maxContentScroll || allFlattenColumnsWithWidth) && ( <ColGroup colWidths={mergedColumnWidth ? [...mergedColumnWidth, combinationScrollBarSize] : []} columCount={columCount + 1} columns={flattenColumnsWithScrollbar} /> )} {children({ ...props, stickyOffsets: headerStickyOffsets, columns: columnsWithScrollbar, flattenColumns: flattenColumnsWithScrollbar, })} </table> </div>
通過(guò)ref,調(diào)用useCallback賦值dom;利用scrollRef.current監(jiān)聽(tīng)wheel事件,轉(zhuǎn)成onScroll,增加入?yún)crollLeft;
const setScrollRef = React.useCallback((element: HTMLElement) => { scrollRef.current = element; }, []);
四、TableBody的滾動(dòng)
當(dāng)然是監(jiān)聽(tīng)onScroll事件;
給Tables設(shè)置scrollX的情況下,TableBody設(shè)置樣式{overflow-x: auto}這樣會(huì)有同頻滾動(dòng)
<div style={ ...scrollXStyle, ...scrollYStyle } onScroll={onScroll} ref={scrollBodyRef} > <TableComponent> {bodyColGroup} {bodyTable} </TableComponent> </div>
五、很nice的點(diǎn)
獲得當(dāng)前正在執(zhí)行的dom
const [setScrollTarget, getScrollTarget] = useTimeoutLock(null);
getScrollTarget用來(lái)獲得當(dāng)前正在執(zhí)行的dom
使用useState來(lái)存儲(chǔ)正在執(zhí)行的dom; 當(dāng)組件重新渲染,dom更新,此時(shí)正在執(zhí)行的dom,在下一個(gè)render的時(shí)候,就變了;useRef在下一次渲染之前不重新賦值,還是保留和上一次一樣的值;
源碼里使用useRef + setTimeout實(shí)現(xiàn);useRef是用來(lái)存放當(dāng)前正在執(zhí)行的dom;setTimeout用來(lái)節(jié)流;
其中g(shù)etState獲取正在執(zhí)行當(dāng)前state,可能是空的;setState設(shè)置當(dāng)前的State,并且在100ms以后清空設(shè)置的狀態(tài);
export function useTimeoutLock<State>(defaultState?: State): [(state: State) => void, () => State | null] { ? const frameRef = useRef<State | null>(defaultState || null); ? const timeoutRef = useRef<number>(); ? function cleanUp() { ? ? window.clearTimeout(timeoutRef.current); ? } ? function setState(newState: State) { ? ? frameRef.current = newState; ?? ??? ?// 清空上一次的定時(shí)器 ? ? cleanUp(); ? ?? ? ? timeoutRef.current = window.setTimeout(() => { ? ? ? frameRef.current = null; ? ? ? timeoutRef.current = undefined; ? ? }, 100); ? } ? function getState() { ? ? return frameRef.current; ? } ? useEffect(() => cleanUp, []); ? return [setState, getState]; }
onScroll為什么設(shè)置if判斷
getScrollTarget()調(diào)用onScroll的之前,是否有滾動(dòng)的dom; 沒(méi)有就更新;有,判斷是否和觸發(fā)onScroll是相同Dom;是,更新;目的是為了避免執(zhí)行上一個(gè)onScroll的時(shí)候,下一個(gè)onScroll執(zhí)行,陷入循環(huán),就相當(dāng)于節(jié)流了;hooks版本的節(jié)流
const compareTarget = currentTarget || EMPTY_SCROLL_TARGET; // 固定滾動(dòng)項(xiàng) // 在處理上一個(gè)滾動(dòng)的時(shí)候,禁止下一個(gè)也滾動(dòng)執(zhí)行onScroll if (!getScrollTarget() || getScrollTarget() === compareTarget) { setScrollTarget(compareTarget); }
看這塊邏輯的時(shí)候,優(yōu)化細(xì)節(jié)??;從hooks的角度,實(shí)現(xiàn)節(jié)流;wheel和scroll都是滾動(dòng),但是也有區(qū)別;并且在react里支持dom綁定onScroll、onWheel;
相關(guān)鏈接:
rc-table: https://github.com/react-component/table
antd-table: https://ant.design/components/table-cn#components-table-demo-fixed-columns
到此這篇關(guān)于antd4里table滾動(dòng)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)antd4 table滾動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ahooks正式發(fā)布React?Hooks工具庫(kù)
這篇文章主要為大家介紹了ahooks正式發(fā)布值得擁有的React?Hooks工具庫(kù)使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07React使用useImperativeHandle自定義暴露給父組件的示例詳解
useImperativeHandle?是?React?提供的一個(gè)自定義?Hook,用于在函數(shù)組件中顯式地暴露給父組件特定實(shí)例的方法,本文將介紹?useImperativeHandle的基本用法、常見(jiàn)應(yīng)用場(chǎng)景,需要的可以參考下2024-03-03React中項(xiàng)目路由配置與跳轉(zhuǎn)方法詳解
這篇文章主要為大家詳細(xì)介紹了React中項(xiàng)目路由配置與跳轉(zhuǎn)方法的相關(guān)資料,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2023-08-08Next.js實(shí)現(xiàn)react服務(wù)器端渲染的方法示例
這篇文章主要介紹了Next.js實(shí)現(xiàn)react服務(wù)器端渲染的方法示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01React Draggable插件如何實(shí)現(xiàn)拖拽功能
這篇文章主要介紹了React Draggable插件如何實(shí)現(xiàn)拖拽功能問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07react實(shí)現(xiàn)復(fù)選框全選和反選組件效果
這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)復(fù)選框全選和反選組件效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08React實(shí)現(xiàn)路由鑒權(quán)的實(shí)例詳解
React應(yīng)用中的路由鑒權(quán)是確保用戶僅能訪問(wèn)其授權(quán)頁(yè)面的方式,用于已登錄或具有訪問(wèn)特定頁(yè)面所需的權(quán)限,這篇文章就來(lái)記錄下React實(shí)現(xiàn)路由鑒權(quán)的流程,需要的朋友可以參考下2024-07-07