真的看不下去了!!!字节的table组件写成啥了!

前言

先不说别的,上两个arco design table的bug。本来是写react table组件,然后看源码学习思路,结果看的我真的很想吐槽。(其他组件我在学习源码上受益匪浅,尤其是工程化arco-cli那部分,我自己尝试写的轮子也是受到很多启发,这个吐槽并不是真的有恶意,我对arco和腾讯的tdeisgn是有期待的,因为ant一家独大太久了,很期待新鲜的血液)

如果arco deisgn的团队看到这篇文章,请一定让写table的同学看一下!!!把多级表头的筛选 + 排序 + 固定逻辑好好梳理一下,目前的写法隐患太多了,我后面会写为什么目前的写法隐患很多,非常容易出bug!

1、这是在线bug demo codesandbox.io/s/jovial-ka…

bug显示

2、继续看,我筛选userInfo上,工资大于2000的行,根本没效果

在线bug 的demo codesandbox.io/s/competent…

说实话,我随便送给大家几个table的bug,都可以去给官方提pr了。(这个写table的人一定要好好的批评一下!!!!)

离谱的filter代码

filter是啥呢,我们看下图

这个表头的筛选我们简称为filter

首先官方把columns上所有的受控和非受控的filter收集起来,代码如下:

  const { currentFilters, currentSorter } = getDefaultFiltersAndSorter(columns);
复制代码

columns我们假设长成这样:

const columns = [
  {
    title: "Name",
    dataIndex: "name",
    width: 140,
  },
  {
    title: "User Info",
    filters: [
      {
        text: "> 20000",
        value: "20000",
      },
      {
        text: "> 30000",
        value: "30000",
      },
    ],
    onFilter: (value, row) => row.salary > value,
  },
  {
    title: "Information",
    children: [
      {
        title: "Email",
        dataIndex: "email",
      },
      {
        title: "Phone",
        dataIndex: "phone",
      },
    ],
  },
]
复制代码

getDefaultFiltersAndSorter的代码如下,不想看细节的,我就说下结论,这个函数是把filters受控属性,filteredValue和非受控属性defaultFilters放到currentFilters对象里,然后导出,其中key可以简单认为是每个columns上的dataIndex,也就是每一列的唯一标识符。

currentSorter我们暂时不看,也是为排序的bug埋下隐患,我们这篇文章先不谈排序的bug。

functiongetDefaultFiltersAndSorter(columns) {
    const currentFilters = {} asPartial<Record<keyof T, string[]>>;
    const currentSorter = {} asSorterResult;
    functiontravel(columns) {
      if (columns && columns.length > 0) {
        columns.forEach((column, index) => {
          const innerDataIndex = column.dataIndex === undefined ? index : column.dataIndex;
          if (!column[childrenColumnName]) {
            if (column.defaultFilters) {
              currentFilters[innerDataIndex] = column.defaultFilters;
            }
            if (column.filteredValue) {
              currentFilters[innerDataIndex] = column.filteredValue;
            }
            if (column.defaultSortOrder) {
              currentSorter.field = innerDataIndex;
              currentSorter.direction = column.defaultSortOrder;
            }
            if (column.sortOrder) {
              currentSorter.field = innerDataIndex;
              currentSorter.direction = column.sortOrder;
            }
          } else {
            travel(column[childrenColumnName]);
          }
        });
      }
    }

    travel(columns);

    return { currentFilters, currentSorter };
  }

复制代码

这里的已经为出bug埋下隐患了,大家看啊,它是递归收集所有columns上的filter相关的受控和非受控的属性,而且受控的属性会覆盖非受控。

这里没有单独区分受控的filter属性和非受控的属性就很奇怪。后面分析,因为arco deisgn有个专门处理受控和非受控的hooks,因为他现在不区分,还用错这个hooks,造成我看起来它的代码奇怪的要命!!

接着看!

然后,他用上面的currentFilters去

  const [filters, setFilters] = useState<FilterType<T>>(currentFilters);复制代码

接着看一下useColunms,这个跟filters后面息息相关,所以我们必须要看下useColumns的实现

  const [groupColumns, flattenColumns] = useColumns<T>(props);复制代码

简单描述一下useColumns的返回值 groupColumns, flattenColumns分别代表什么:

  • groupColumns,它将columns按行存储到数组里面,啥是按行呢,看下图

  • name、user info、Information、salary是第一行

  • Birthday、address是第二行,Email,phone也是第二行

  • city、road、no是第三行

flattenColumns是啥意思呢?就是columns叶子节点组成的数组,叶子节点是指所有columns中没有children属性的节点。以下是具体代码,有兴趣的可以看看,我们接着看,马上很奇怪的代码就要来了!

function useColumns<T>(props: TableProps<T>): [InternalColumnProps[][], InternalColumnProps[]] {
  const {
    components, // 覆盖原生表格标签
    rowSelection, // 设置表格行是否可选,选中事件等
    expandedRowRender, // 点击展开额外的行,渲染函数。返回值为 null 时,不会渲染展开按钮
    expandProps = {}, // 展开参数
    columns = [], // 外界传入的columns
    childrenColumnName, // 默认是children
  } = props;

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/59dbcdab3b154494b751f61eeebe2432~tplv-k3u1fbpfcp-watermark.image?)// 下面有getFlattenColumns方法// getFlattenColumns平铺columns,因为可能有多级表头,所以需要平铺// getFlattenColumns,注意这个平铺只会搜集叶子节点!!!!constrows: InternalColumnProps[] = useMemo(
    () =>getFlattenColumns(columns, childrenColumnName),
    [columns, childrenColumnName]
  );

  // 是否是checkboxconst isCheckbox =
    (rowSelection && rowSelection.type === 'checkbox') ||
    (rowSelection && !('type'in rowSelection));

  // 是否是radioconst isRadio = rowSelection && rowSelection.type === 'radio';

  // 展开按钮列的宽度const { width: expandColWidth } = expandProps;

  // 是否有expand—rowconst shouldRenderExpandCol = !!expandedRowRender;
  const shouldRenderSelectionCol = isCheckbox || isRadio;

  // 获取到自定义的操作栏,默认是selectNode和expandNodeconst { getHeaderComponentOperations, getBodyComponentOperations } = useComponent(components);

  const headerOperations = useMemo(
    () =>getHeaderComponentOperations({
        selectionNode: shouldRenderSelectionCol ? 'holder_node' : '',
        expandNode: shouldRenderExpandCol ? 'holder_node' : '',
      }),
    [shouldRenderSelectionCol, shouldRenderExpandCol, getHeaderComponentOperations]
  );
  const bodyOperations = useMemo(
    () =>getBodyComponentOperations({
        selectionNode: shouldRenderSelectionCol ? 'holder_node' : '',
        expandNode: shouldRenderExpandCol ? 'holder_node' : '',
      }),
    [shouldRenderSelectionCol, shouldRenderExpandCol, getBodyComponentOperations]
  );

  // rowSelection.fixed 表示checkbox是否固定在左边const selectionFixedLeft = rowSelection && rowSelection.fixed;
  // 选择列的宽度const selectionColumnWidth = rowSelection && rowSelection.columnWidth;

  const getInternalColumns = useCallback(
    (rows, operations, index?: number) => {
      constoperationFixedProps: { fixed?: 'left' | 'right' } = {};
      const_rows: InternalColumnProps[] = [];
      rows.forEach((r, i) => {
        const _r = { ...r };
        if (!('key'in r)) {
          _r.key = _r.dataIndex || i;
        }
        if (i === 0) {
          _r.$$isFirstColumn = true;

          if (_r.fixed === 'left') {
            operationFixedProps.fixed = _r.fixed;
          }
        } else {
          _r.$$isFirstColumn = false;
        }
        _rows.push(_r);
      });

      const expandColumn = shouldRenderExpandCol && {
        key: INTERNAL_EXPAND_KEY,
        title: INTERNAL_EXPAND_KEY,
        width: expandColWidth,
        $$isOperation: true,
      };
      const selectionColumn = shouldRenderSelectionCol && {
        key: INTERNAL_SELECTION_KEY,
        title: INTERNAL_SELECTION_KEY,
        width: selectionColumnWidth,
        $$isOperation: true,
      };

      if (selectionFixedLeft) {
        operationFixedProps.fixed = 'left';
      }
      if (typeof index !== 'number' || index === 0) {
        [...operations].reverse().forEach((operation) => {
          if (operation.node) {
            if (operation.name === 'expandNode') {
              _rows.unshift({ ...expandColumn, ...operationFixedProps });
            } elseif (operation.name === 'selectionNode') {
              _rows.unshift({ ...selectionColumn, ...operationFixedProps });
            } else {
              _rows.unshift({
                ...operation,
                ...operationFixedProps,
                title: operation.name,
                key: operation.name,
                $$isOperation: true,
                width: operation.width || 40,
              });
            }
          }
        });
      }

      return _rows;
    },
    [
      expandColWidth,
      shouldRenderExpandCol,
      shouldRenderSelectionCol,
      selectionColumnWidth,
      selectionFixedLeft,
    ]
  );

  const flattenColumns = useMemo(
    () =>getInternalColumns(rows, bodyOperations),
    [rows, getInternalColumns, bodyOperations]
  );

  // 把表头分组的 columns 分成 n 行,并且加上 colSpan 和 rowSpan,没有表头分组的话是 1 行。// 获取column的深度const rowCount = useMemo(
    () =>getAllHeaderRowsCount(columns, childrenColumnName),
    [columns, childrenColumnName]
  );

  // 分行之后的rowsconst groupColumns = useMemo(() => {
    if (rowCount === 1) {
      return [getInternalColumns(columns, headerOperations, 0)];
    }
    constrows: InternalColumnProps[][] = [];
    consttravel = (columns, current = 0) => {
      rows[current] = rows[current] || [];
      columns.forEach((col) => {
        constcolumn: InternalColumnProps = { ...col };
        if (column[childrenColumnName]) {
          // 求出叶子结点的个数就是colSpan
          column.colSpan = getFlattenColumns(col[childrenColumnName], childrenColumnName).length;
          column.rowSpan = 1;
          rows[current].push(column);
          travel(column[childrenColumnName], current + 1);
        } else {
          column.colSpan = 1;
          // 这是
          column.rowSpan = rowCount - current;
          rows[current].push(column);
        }
      });
      rows[current] = getInternalColumns(rows[current], headerOperations, current);
    };
    travel(columns);
    return rows;
  }, [columns, childrenColumnName, rowCount, getInternalColumns, headerOperations]);

  return [groupColumns, flattenColumns];
}

exportdefault useColumns;


functiongetFlattenColumns(columns: InternalColumnProps[], childrenColumnName: string) {
  constrows: InternalColumnProps[] = [];
  functiontravel(columns) {
    if (columns && columns.length > 0) {
      columns.forEach((column) => {
        if (!column[childrenColumnName]) {
          rows.push({ ...column, key: column.key || column.dataIndex });
        } else {
          travel(column[childrenColumnName]);
        }
      });
    }
  }
  travel(columns);

  return rows;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值