返回

Affix 固钉组件源码 + 单元测试解析

前端

前言

正式开始 React 组件库开发,目的是为了后续的低代码平台拥有对组件的绝对掌控的能力,所以前提必须拥有一套组件库。之前写过一个 Form 的实现,有兴趣的同学请看:实现一个比 Ant Design 更好用的 Form 组件

Affix 组件是 React 组件库中一个重要的组件,它可以将一个元素固定在页面上的某个位置,即使页面滚动时元素也会保持固定。本文将介绍 Affix 组件的源码和单元测试,帮助您更好地理解和使用这个组件。文中还包含详细的示例代码,可以帮助您快速上手 Affix 组件。

Affix 组件源码解析

Affix 组件的源码位于 node_modules/rc-affix/es/index.js 文件中。我们可以看到,Affix 组件是一个高阶组件,它接受一个 React 组件作为参数,并返回一个新的组件。新的组件具有固定的功能,可以将传入的组件固定在页面上的某个位置。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';

class Affix extends Component {
  state = {
    fixed: false,
  };

  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll);
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll);
  }

  handleScroll = () => {
    const { offsetTop } = this.target;
    const { offsetHeight } = this.affix;
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;

    if (scrollTop > offsetTop && !this.state.fixed) {
      this.setState({ fixed: true });
    } else if (scrollTop < offsetTop - offsetHeight && this.state.fixed) {
      this.setState({ fixed: false });
    }
  };

  render() {
    const { fixed } = this.state;
    const { children } = this.props;

    return (
      <div
        ref={(affix) => {
          this.affix = affix;
        }}
        style={{ position: fixed ? 'fixed' : 'relative' }}
      >
        {children}
      </div>
    );
  }
}

export default Affix;

从源码中我们可以看到,Affix 组件主要做了以下几件事:

  1. 在组件挂载时,添加一个滚动事件监听器,用于监听页面滚动事件。
  2. 在滚动事件处理函数中,判断当前页面滚动的位置是否已经超过了固定的元素的顶部。如果超过了,则将组件的状态设置为 fixed,表示组件应该固定在页面上。否则,将组件的状态设置为 false,表示组件应该取消固定。
  3. 在组件渲染时,根据组件的状态来决定组件的样式。如果组件的状态为 fixed,则将组件的样式设置为 fixed,表示组件应该固定在页面上。否则,将组件的样式设置为 relative,表示组件应该取消固定。

Affix 组件单元测试

为了确保 Affix 组件能够正常工作,我们编写了单元测试来对组件进行测试。单元测试位于 node_modules/rc-affix/test/index.test.js 文件中。我们可以看到,单元测试主要做了以下几件事:

  1. 创建一个 Affix 组件的实例。
  2. 模拟页面滚动事件,并检查组件的状态是否发生了变化。
  3. 模拟组件卸载,并检查滚动事件监听器是否被移除了。
import React from 'react';
import ReactDOM from 'react-dom';
import Affix from '../index';

describe('Affix', () => {
  it('should be fixed when scrolling', () => {
    const wrapper = mount(<Affix />);
    const affix = wrapper.find('.rc-affix');

    // 模拟页面滚动事件
    window.scrollTo(0, 100);
    wrapper.update();

    // 检查组件的状态是否发生了变化
    expect(wrapper.state('fixed')).toBe(true);
    expect(affix.hasClass('rc-affix-fixed')).toBe(true);
  });

  it('should be unfixed when scrolling up', () => {
    const wrapper = mount(<Affix />);
    const affix = wrapper.find('.rc-affix');

    // 模拟页面滚动事件
    window.scrollTo(0, 100);
    wrapper.update();

    // 模拟页面向上滚动事件
    window.scrollTo(0, 0);
    wrapper.update();

    // 检查组件的状态是否发生了变化
    expect(wrapper.state('fixed')).toBe(false);
    expect(affix.hasClass('rc-affix-fixed')).toBe(false);
  });

  it('should remove scroll event listener on unmount', () => {
    const wrapper = mount(<Affix />);

    // 模拟组件卸载
    wrapper.unmount();

    // 检查滚动事件监听器是否被移除了
    expect(window.removeEventListener).toHaveBeenCalledWith('scroll', wrapper.instance().handleScroll);
  });
});

通过单元测试,我们可以确保 Affix 组件能够正常工作。

Affix 组件示例

下面是一个使用 Affix 组件的示例:

import React from 'react';
import ReactDOM from 'react-dom';
import Affix from 'rc-affix';

const Example = () => {
  return (
    <div>
      <Affix>
        <div>我是一个固定的元素</div>
      </Affix>
    </div>
  );
};

ReactDOM.render(<Example />, document.getElementById('root'));

这个示例中,我们使用 Affix 组件将一个 <div> 元素固定在了页面上。当页面滚动时,这个 <div> 元素会保持固定,不会随页面一起滚动。

总结

Affix 组件是一个非常有用的组件,它可以将一个元素固定在页面上的某个位置,即使页面滚动时元素也会保持固定。本文介绍了 Affix 组件的源码和单元测试,并给出了一个使用 Affix 组件的示例。希望本文能够帮助您更好地理解和使用 Affix 组件。