React 组件和构造函数绑定不起作用

React Component and constructor bind not working

看图片不知道怎么办。获取 Eslint 警告的方法 _getListStyle() 设置与其他方法相同,例如 _getListItemStyle(index, item),但它们没有收到 Eslint 警告?

请指教

/* eslint-disable no-underscore-dangle */
import React from 'react';
import PropTypes from 'prop-types';
import { withResizeDetector } from 'react-resize-detector';
import VisibilitySensor from 'react-visibility-sensor';
import { max, map, compact, last } from 'lodash';

class Masonry extends React.Component {
    constructor() {
        super();
        this.container = React.createRef();
        this.list = React.createRef();
        this.state = {
            mounted: false,
            scrollTop: 0,
            scrollProgress: 0,
        };

        this._onEnd = this._onEnd.bind(this);
        this._getListStyle = this._getListStyle.bind(this);

        this._isVisibleItem = this._isVisibleItem.bind(this);
        this._setContainerHeight = this._setContainerHeight.bind(this);
        this._onResize = this._onResize.bind(this);
        this._onScroll = this._onScroll.bind(this);
    }

    componentDidMount() {
        this.setState({
            mounted: true,
        });
        this.container.current.addEventListener('scroll', this._onScroll, false);
    }

    UNSAFE_componentWillReceiveProps({ width }) {
        const { width: width2 } = this.props;
        if (width2 !== width) {
            setTimeout(this._onResize, 0);
        }
    }

    componentWillUnmount() {
        this.container.current.removeEventListener('scroll', this._onScroll, false);
    }

    /**
     * Do something when the container is scrolled
     */
    _onScroll() {
        const { scrollTop, scrollHeight } = this.container.current;

        this.setState({
            scrollTop,
            scrollProgress: (1 / scrollHeight) * scrollTop,
        });
    }

    /**
     * Approximate previous scroll-position after container was resized
     */
    _onResize() {
        const { scrollProgress } = this.state;
        const container = this.container.current;

        container.scrollTop = scrollProgress * container.scrollHeight;
    }

    /**
     *  Let parent components know that we reached the end of display
     */
    _onEnd(isVisible) {
        if (!isVisible) return;
        const { onEnd } = this.props;
        if (onEnd) onEnd();
    }

    _getContainerStyle() {
        const { debug } = this.props;

        return {
            outline: debug && '1px solid seagreen',
            width: '100%',
            position: 'relative',
        };
    }

    _getListStyle() {
        return {
            padding: 0,
            margin: 0,
            listStyle: 'none',
        };
    }

    _getListItemStyle(index, item) {
        const position = {
            padding: 0,
            margin: 0,
            position: 'absolute',
            ...this._getItemDimensions(index, item),
        };

        return position;
    }

    // Picks the column in which to put the element
    // and returns its index
    _pickColumn() {
        const columnHeights = this.columns.map(items => {
            const lastItem = items[items.length - 1];
            return lastItem ? lastItem.height + lastItem.top : 0;
        });

        const columnIndex = columnHeights.indexOf(Math.min(...columnHeights));
        return columnIndex;
    }

    _getWidth() {
        const { gutter, outerGutter } = this.props;
        const colcount = this._getColumnCount();
        const containerWidth =
            this.container.current.offsetWidth - (colcount - 1) * gutter - (outerGutter ? 2 * gutter : 0);

        return Math.floor(containerWidth / colcount);
    }

    _getDimensions({ item }) {
        const { extraPx = 0 } = this.props;
        const width = this._getWidth();
        const height = Math.floor(width * item.ratio) + extraPx;
        return {
            width,
            height,
        };
    }

    _getLeft({ columnIndex }) {
        const { gutter, outerGutter } = this.props;
        return columnIndex * this._getWidth() + columnIndex * gutter + (outerGutter ? gutter : 0);
    }

    _getTop({ columnIndex }) {
        const { outerGutter, gutter } = this.props;
        const items = this.columns[columnIndex];
        const outer = outerGutter ? gutter : 0;
        return items && items.length ? last(items).top + last(items).height + gutter : outer;
    }

    _getItemDimensions(index, item) {
        const columnIndex = this._pickColumn();
        const { debug } = this.props;

        const dimensions = {
            outline: debug && '1px solid tomato',
            top: this._getTop({ columnIndex }),
            left: this._getLeft({ columnIndex }),
            ...this._getDimensions({ item, columnIndex }),
        };

        this.columns[columnIndex] = [...this.columns[columnIndex], dimensions];

        return dimensions;
    }

    _isVisibleItem({ top, height }) {
        const { scrollTop } = this.state;
        const { offsetHeight } = this.container.current;
        const { safeSpace = offsetHeight / 2 } = this.props;

        const topVisible = top >= scrollTop - height - safeSpace;
        const bottomVisible = top <= scrollTop + offsetHeight + safeSpace;

        return topVisible && bottomVisible;
    }

    _setContainerHeight() {
        const el = this.list.current;

        const lastItems = this.columns.map(items => {
            const item = items[items.length - 1];
            return item.top + item.height;
        });

        el.style.height = `${max(lastItems)}px`;
    }

    _getColumnCount() {
        const { cols } = this.props;
        const mqs = compact(
            map(cols, (columnCount, key) => {
                const applies = this.container.current.offsetWidth >= key;
                return applies ? columnCount : false;
            }),
        );

        const colcount = last(mqs);

        return colcount;
    }

    renderItem({ index, item, maxIndex }) {
        const { items = [], itemRenderer } = this.props;
        const Component = itemRenderer;

        const style = this._getListItemStyle(index, item);

        const isVisible = this._isVisibleItem(style);

        if (index + 1 === maxIndex) {
            this._setContainerHeight();
        }
        const { ratio, background, fileData, id, week, mediaType } = item;
        return isVisible ? (
            <li key={id} style={style}>
                <Component items={{ ratio, background, fileData, id, week, mediaType }} />
                {items.length === index + 1 && (
                    <div
                        style={{
                            position: 'absolute',
                            top: -this.container.current.offsetHeight / 2,
                            width: 1,
                            height: 1,
                        }}
                    >
                        <VisibilitySensor partialVisibility onChange={this._onEnd} />
                    </div>
                )}
            </li>
        ) : null;
    }

    render() {
        const { mounted } = this.state;
        const { items = [] } = this.props;

        if (mounted) {
            this.columns = new Array(this._getColumnCount()).fill([]);
        }

        const maxIndex = items.length;

        return (
            <div style={this._getContainerStyle()} ref={this.container}>
                <ul ref={this.list} style={this._getListStyle()}>
                    {mounted && items.map((item, index) => this.renderItem({ index, item, maxIndex }))}
                </ul>
            </div>
        );
    }
}

Masonry.propTypes = {
    items: PropTypes.array.isRequired,
    itemRenderer: PropTypes.func.isRequired,
    debug: PropTypes.bool,
};

Masonry.defaultProps = {
    debug: 0,
};
export default withResizeDetector(Masonry);

这是有道理的,因为你创建了一个 returns 静态值的函数,而不是你可以在外部范围内定义常量(通常这就是你对待常量的方式)或者你可以像 class 字段:

const LIST_STYLE = {
  padding: 0,
  margin: 0,
  listStyle: "none",
};

class Masonry extends React.Component {
  // this.listStyle
  listStyle = {
    padding: 0,
    margin: 0,
    listStyle: "none",
  };

  // or Masonry.listStyle
  static listStyle = {
    padding: 0,
    margin: 0,
    listStyle: "none",
  };

  // or this.listStyle
  constructor() {
    this.listStyle = {
      padding: 0,
      margin: 0,
      listStyle: "none",
    };
  }
}

ESLint 规则 class-methods-use-this 与绑定 this 到 class 实例无关。

相反,该规则强制所有 class 方法在其实现中使用 this。如果 class 方法不引用 class 实例(使用 this),可能会有更好的方法来定义功能。

在这种情况下,一种解决方案是将方法的结果转换为常量值,因为函数 returns 的结果相同,而不管 class 实例的值如何。

const listStyle = {
    padding: 0,
    margin: 0,
    listStyle: 'none',
};

class Masonry extends React.Component {
   ...

   render() {
       ...
    return (
            <div style={this._getContainerStyle()} ref={this.container}>
                <ul ref={this.list} style={listStyle}>
                    {mounted && items.map((item, index) => this.renderItem({ index, item, maxIndex }))}
                </ul>
            </div>
        );

   }

...
}