cisen / blog

Time waits for no one.
134 stars 20 forks source link

react-window源码相关 #288

Open cisen opened 5 years ago

cisen commented 5 years ago

说明

https://github.com/cisen/sourcecode-react-window https://github.com/bvaughn/react-window

记录

  1. 触发滚动_onScroll时,只是很简单地setstate一下滚动方向和位置,还有缓存一份数据。真正执行滚动和滚动运算的入口是componentDidUpdate

  2. 计算渲染哪条到哪条的是_getHorizontalRangeToRender

  3. isScrolling控制着是前进方向加载多少条,后退方向加载多少条

问题

cisen commented 5 years ago

edittable测试实现

/* eslint-disable max-classes-per-file */
/* eslint-disable react/no-multi-comp */
import React from 'react';
import memoizeOne from 'memoize-one';
import { isFunction, get, merge, isEqual } from 'lodash/fp';
import { cancelTimeout, requestTimeout } from './utils/timer';

const IS_SCROLLING_DEBOUNCE_INTERVAL = 150;

function getStartIndexForOffset({ itemCount, itemSize }, offset) {
  return Math.max(0, Math.min(itemCount - 1, Math.floor(offset / itemSize)));
}

function getStopIndexForStartIndex({ itemCount, itemSize, height }, startIndex, scrollOffset) {
  const offset = startIndex * itemSize;
  return Math.max(0, Math.min(itemCount - 1, startIndex + Math.floor((height + (scrollOffset - offset)) / itemSize)));
}

function setEditTable(Table) {
  class EditableTable extends React.PureComponent {
    constructor(props) {
      super(props);
      this.resetIsScrollingTimeoutId = null;
      this.instanceProps = {};
      this.scrollWrapperDom = document.querySelector('.ir-content');
      // 对于垂直列表,这是行高。 对于水平列表,这是列宽。
      this.itemSize = 60;
      // 条数的总数
      // itemCount
      this.overscanCount = 10;
      // 窗口的高度
      this.height = 40;
      this.state = {
        // instance: this,
        isScrolling: false,
        scrollDirection: 'forward',
        // 初始化滚动位置
        scrollOffset: typeof props.initialScrollOffset === 'number' ? props.initialScrollOffset : 0,
        scrollUpdateWasRequested: false,
      };
    }

    componentDidMount() {
      this.scrollWrapperDom.addEventListener('scroll', this.onScrollVertical);
    }

    componentDidUpdate() {
      const { scrollOffset, scrollUpdateWasRequested } = this.state;
      // if (scrollUpdateWasRequested && this.scrollWrapperDom !== null) {
      //   this.scrollWrapperDom.scrollTop = scrollOffset;
      // }
    }

    // 垂直滚动事件
    onScrollVertical = event => {
      const { scrollTop } = event.currentTarget;

      this.setState(prevState => {
        if (prevState.scrollOffset === scrollTop) {
          // Scroll position may have been updated by cDM/cDU,
          // In which case we don't need to trigger another render,
          // And we don't want to update state.isScrolling.
          return null;
        }

        return {
          isScrolling: true,
          scrollDirection: prevState.scrollOffset < scrollTop ? 'forward' : 'backward',
          scrollOffset: scrollTop,
          scrollUpdateWasRequested: false,
        };
      }, this.resetIsScrollingDebounced);
    };

    resetIsScrollingDebounced = () => {
      if (this.resetIsScrollingTimeoutId !== null) {
        cancelTimeout(this.resetIsScrollingTimeoutId);
      }

      this.resetIsScrollingTimeoutId = requestTimeout(this.resetIsScrolling, IS_SCROLLING_DEBOUNCE_INTERVAL);
    };

    resetIsScrolling = () => {
      this.resetIsScrollingTimeoutId = null;

      this.setState({ isScrolling: false }, () => {
        // Clear style cache after state update has been committed.
        // This way we don't break pure sCU for items that don't use isScrolling param.
        this.getItemStyleCache(-1, null);
      });
    };

    getItemStyleCache = memoizeOne(() => {});

    handleSave = row => {
      this.props.onEditSave(row);
    };

    getRangeToRender = () => {
      const { dataSource } = this.props;
      const { isScrolling, scrollDirection, scrollOffset } = this.state;
      const overscanCount = this.overscanCount;
      const itemCount = (dataSource && dataSource.length) || 0;
      const itemSize = this.itemSize;
      const height = this.height;

      if (itemCount === 0) {
        return [0, 0, 0, 0];
      }

      const startIndex = getStartIndexForOffset({ itemCount, itemSize }, scrollOffset, this.instanceProps);
      const stopIndex = getStopIndexForStartIndex(
        { itemCount, itemSize, height },
        startIndex,
        scrollOffset,
        this.instanceProps,
      );

      // Overscan by one item in each direction so that tab/focus works.
      // If there isn't at least one extra item, tab loops back around.
      const overscanBackward = !isScrolling || scrollDirection === 'backward' ? Math.max(1, overscanCount) : 1;
      const overscanForward = !isScrolling || scrollDirection === 'forward' ? Math.max(1, overscanCount) : 1;

      return [
        Math.max(0, startIndex - overscanBackward),
        Math.max(0, Math.min(itemCount - 1, stopIndex + overscanForward)),
        startIndex,
        stopIndex,
      ];
    }

    onRowCb = (startIndex, stopIndex) => {
      return (record, index) => {
        const tools = this.props.tools || this.props.renderTools || this.props.renderLeftTools;
        const { onRow } = this.props;
        const custom = onRow ? onRow(record, index) : {};

        return {
          ...custom,
          tools,
          startIndex,
          stopIndex,
        };
      };
    };
    // onRow = (record, index) => {
    //   const tools = this.props.tools || this.props.renderTools || this.props.renderLeftTools;
    //   const { onRow } = this.props;
    //   const custom = onRow ? onRow(record, index) : {};

    //   return {
    //     ...custom,
    //     tools,
    //   };
    // };

    render() {
      const { columns, components, dataSource, rowKey, ...restProps } = this.props;
      const [startIndex, stopIndex] = this.getRangeToRender();
      console.log('edit render', startIndex, stopIndex);
      // const spliceDate = dataSource && dataSource.slice(startIndex, stopIndex);
      const componentsEdit = {
        body: {
          cell: EditableCell,
        },
      };
      const finalColumns = columns.map(col => {
        if (!isFunction(col.edit)) {
          return col;
        }
        return {
          ...col,
          onCell: (record, rowIndex) => {
            const editConf = col.edit(record);

            return {
              record,
              rowIndex,
              editable: editConf.editable,
              inputEl: editConf.inputEl,
              value: editConf.value,
              getProps: editConf.getProps,
              dataIndex: col.dataIndex,
              title: col.title,
              width: editConf.editableWidth ?? '100%',
              content: editConf.popoverContent,
              onSave: this.handleSave,
            };
          },
        };
      });

      return (
        <div>
          <Table
            {...restProps}
            columns={finalColumns}
            onRow={this.onRowCb(startIndex, stopIndex)}
            components={merge(components)(componentsEdit)}
            dataSource={dataSource}
            rowClassName={() => 'ir-editable-row'}
            rowKey={rowKey}
          />
        </div>
      );
    }
  }

  return EditableTable;
}

export default setEditTable;