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;
/** 设置 Affix 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 */
target?: () => Window | HTMLElement | null;
/** 样式命名空间,传入可覆盖antd命名空间'ant-|ant' */
prefixCls?: string;
/** 覆写class类名 */
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函数;采用装饰器语法对某些频繁触发的函数进行节流操作,从而达到组件优化的作用。