Affix 固钉,就是固定在页面的某个地方,类似于样式中的固定布局。
API参数
先看组件可以传入什么参数使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export interface AffixProps { offsetTop?: number; offsetBottom?: number; style?: React .CSSProperties ; onChange?: (affixed?: boolean ) => void ; target?: () => Window | HTMLElement | null ; prefixCls?: string; className?: string; children : React .ReactNode ; }
render函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 render = () => { const { getPrefixCls } = this .context ; const { affixStyle, placeholderStyle } = this .state ; const { prefixCls, children } = this .props ; const className = classNames ({ [getPrefixCls ('affix' , prefixCls)]: !!affixStyle, }); let props = omit (this .props , ['prefixCls' , 'offsetTop' , 'offsetBottom' , 'target' , 'onChange' ]); return ( <ResizeObserver onResize ={() => { this.updatePosition(); }} > <div {...props } ref ={this.savePlaceholderNode} > {affixStyle && <div style ={placeholderStyle} aria-hidden ="true" /> } <div className ={className} ref ={this.saveFixedNode} style ={affixStyle} > <ResizeObserver onResize ={() => { this.updatePosition(); }} > {children} </ResizeObserver > </div > </div > </ResizeObserver > ); };
这里做了一些默认样式的加载,固定初始位置等,利用ResizeObserver组件来监听元素位置、大小的变化。
组件生命周期函数执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 componentDidMount ( ) { const targetFunc = this .getTargetFunc (); if (targetFunc) { this .timeout = setTimeout (() => { addObserveTarget (targetFunc (), this ); this .updatePosition (); }); } } componentDidUpdate (prevProps: AffixProps ) { const { prevTarget } = this .state ; const targetFunc = this .getTargetFunc (); let newTarget = null ; if (targetFunc) { newTarget = targetFunc () || null ; } if (prevTarget !== newTarget) { removeObserveTarget (this ); if (newTarget) { addObserveTarget (newTarget, this ); this .updatePosition (); } this .setState ({ prevTarget : newTarget }); } if ( prevProps.offsetTop !== this .props .offsetTop || prevProps.offsetBottom !== this .props .offsetBottom ) { this .updatePosition (); } this .measure (); } componentWillUnmount ( ) { clearTimeout (this .timeout ); removeObserveTarget (this ); (this .updatePosition as any).cancel (); (this .lazyUpdatePosition as any).cancel (); }
① 组件初始化时addObserveTarget
函数给target
元素添加了滚动事件监听(不传入target参数相当于给window添加滚动监听),该函数内部调用lazyUpdatePosition
函数用于判断事件触发后组件的top、bottom值是否变化,使用装饰器语法在位置更新的函数上做了节流处理,并调用updatePosition
函数初始化一些组件状态AffixStyle与placeholderStyle等。
② 状态更新触发componentDidUpdate
函数,对比前后的target元素,如果发生变动则移除之前的监听并添加新的监听然后更新位置,否则直接更新位置,然后调用measure
函数。
③ 触发组件卸载函数,清空定时器,移除一些副作用的操作。
measure函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 measure = () => { const { status, lastAffix } = this .state ; const { onChange } = this .props ; const targetFunc = this .getTargetFunc (); if (status !== AffixStatus .Prepare || !this .fixedNode || !this .placeholderNode || !targetFunc) { return ; } const offsetTop = this .getOffsetTop (); const offsetBottom = this .getOffsetBottom (); const targetNode = targetFunc (); if (!targetNode) { return ; } const newState : Partial <AffixState > = { status : AffixStatus .None , }; const targetRect = getTargetRect (targetNode); const placeholderReact = getTargetRect (this .placeholderNode ); const fixedTop = getFixedTop (placeholderReact, targetRect, offsetTop); const fixedBottom = getFixedBottom (placeholderReact, targetRect, offsetBottom); console .log (targetRect, placeholderReact, fixedTop, fixedBottom, '123456' ) if (fixedTop !== undefined ) { newState.affixStyle = { position : 'fixed' , top : fixedTop, width : placeholderReact.width , height : placeholderReact.height , }; newState.placeholderStyle = { width : placeholderReact.width , height : placeholderReact.height , }; } else if (fixedBottom !== undefined ) { newState.affixStyle = { position : 'fixed' , bottom : fixedBottom, width : placeholderReact.width , height : placeholderReact.height , }; newState.placeholderStyle = { width : placeholderReact.width , height : placeholderReact.height , }; } newState.lastAffix = !!newState.affixStyle ; if (onChange && lastAffix !== newState.lastAffix ) { onChange (newState.lastAffix ); } this .setState (newState as AffixState ); };
函数中主要计算对应的top和bottom值,保存到状态affixStyle中用于组件渲染,通过lastAffix来判断是否调用回调函数onChange
总结 :组件设计中,可以利用数组队列维护一个事件池,如源码中的addObserveTarget
函数;采用装饰器语法对某些频繁触发的函数进行节流操作,从而达到组件优化的作用。