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

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

前言
先不说别的,上两个arco design table的bug。本来是写react table组件,然后看源码学习思路,结果看的我真的很想吐槽。(其他组件我在学习源码上受益匪浅,尤其是工程化arco-cli那部分,我自己尝试写的轮子也是受到很多启发,这个吐槽并不是真的有恶意,我对arco和腾讯的tdeisgn是有期待的,因为ant一家独大太久了,很期待新鲜的血液)
如果arco deisgn的团队看到这篇文章,请一定让写table的同学看一下!!!把多级表头的筛选 + 排序 + 固定逻辑好好梳理一下,目前的写法隐患太多了,我后面会写为什么目前的写法隐患很多,非常容易出bug!
1、这是在线bug demo codesandbox.io/s/jovial-ka…
0.png

bug显示
0.2.png

2、继续看,我筛选userInfo上,工资大于2000的行,根本没效果
在线bug 的demo codesandbox.io/s/competent…
0.3.png

说实话,我随便送给大家几个table的bug,都可以去给官方提pr了。(这个写table的人一定要好好的批评一下!!!!)
离谱的filter代码
filter是啥呢,我们看下图
这个表头的筛选我们简称为filter
0.4.png

首先官方把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。
function getDefaultFiltersAndSorter(columns) {

const currentFilters = {} as Partial<Record<keyof T, string[]>>;
const currentSorter = {} as SorterResult;
function travel(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>(currentFilters);
复制代码
接着看一下useColunms,这个跟filters后面息息相关,所以我们必须要看下useColumns的实现
const [groupColumns, flattenColumns] = useColumns(props);
复制代码
简单描述一下useColumns的返回值 groupColumns, flattenColumns分别代表什么:

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

name、user info、Information、salary是第一行
Birthday、address是第二行,Email,phone也是第二行
city、road、no是第三行

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

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

} = props;

image.png
// 下面有getFlattenColumns方法
// getFlattenColumns平铺columns,因为可能有多级表头,所以需要平铺
// getFlattenColumns,注意这个平铺只会搜集叶子节点!!!!
const rows: InternalColumnProps[] = useMemo(

() => getFlattenColumns(columns, childrenColumnName),
[columns, childrenColumnName]

);

// 是否是checkbox
const isCheckbox =

(rowSelection && rowSelection.type === 'checkbox') ||
(rowSelection && !('type' in rowSelection));

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

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

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

// 获取到自定义的操作栏,默认是selectNode和expandNode
const { 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) => {
  const operationFixedProps: { 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 });
        } else if (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]

);

// 分行之后的rows
const groupColumns = useMemo(() => {

if (rowCount === 1) {
  return [getInternalColumns(columns, headerOperations, 0)];
}
const rows: InternalColumnProps[][] = [];
const travel = (columns, current = 0) => {
  rows[current] = rows[current] || [];
  columns.forEach((col) => {
    const column: 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];
}

export default useColumns;

function getFlattenColumns(columns: InternalColumnProps[], childrenColumnName: string) {
const rows: InternalColumnProps[] = [];
function travel(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;
}

相关文章
|
5月前
|
机器学习/深度学习 缓存 安全
Python标准库中的`str`类型有一个`translate()`方法,它用于替换字符串中的字符或字符子集。这通常与`str.maketrans()`方法一起使用,后者创建一个映射表,用于定义哪些字符应该被替换。
Python标准库中的`str`类型有一个`translate()`方法,它用于替换字符串中的字符或字符子集。这通常与`str.maketrans()`方法一起使用,后者创建一个映射表,用于定义哪些字符应该被替换。
|
PHP
php函数基础学习:array_chunk() 函数把一个数组分割为新的数组块
php函数基础学习:array_chunk() 函数把一个数组分割为新的数组块
60 0
求字符串的长度(4种写法)(普通写法,函数写法(两种:有无返回值),不允许创建临时变量法(递归))
求字符串的长度(4种写法)(普通写法,函数写法(两种:有无返回值),不允许创建临时变量法(递归))
163 0
求字符串的长度(4种写法)(普通写法,函数写法(两种:有无返回值),不允许创建临时变量法(递归))
|
存储 安全 前端开发
Go-字符和字符串类型详解(原始字符串、拼接、修改、比较、拆分、查找等)
Go-字符和字符串类型详解(原始字符串、拼接、修改、比较、拆分、查找等)
160 0
Go-字符和字符串类型详解(原始字符串、拼接、修改、比较、拆分、查找等)
|
Oracle 关系型数据库
oracle按code编码长度查询代码展现层级关系(给字段前加空格)
学习oracle按code编码长度查询代码展现层级关系(给字段前加空格)
154 0
oracle按code编码长度查询代码展现层级关系(给字段前加空格)
结构体类型对齐问题----例题及方法详细解析
结构体类型对齐问题----例题及方法详细解析
79 0
|
SQL 关系型数据库 MySQL
【MySQL】根据相同值,拼接指定字段值,还可添加前缀后缀(GROUP_CONCAT()、CONCAT()、GROUP BY 联用)
【MySQL】根据相同值,拼接指定字段值,还可添加前缀后缀(GROUP_CONCAT()、CONCAT()、GROUP BY 联用)
484 0
【MySQL】根据相同值,拼接指定字段值,还可添加前缀后缀(GROUP_CONCAT()、CONCAT()、GROUP BY 联用)
|
存储 SQL 编解码
base64编码底层转换规则举例解读
base64编码底层转换规则举例解读
265 0
base64编码底层转换规则举例解读
|
关系型数据库 Oracle SQL
[20171021]绑定变量的分配长度8.txt
[20171021]绑定变量的分配长度8.txt --//前几天跟别人讨论,提到我写的测试链接 http://blog.itpub.net/267265/viewspace-2125825/ --//很有意思.
724 0
|
SQL Oracle 关系型数据库
[20171019]绑定变量的分配长度7.txt
[20171019]绑定变量的分配长度7.txt --//如果绑定变量中字符串分配占用空间的长度变化,oracle会建立子光标。 --//参考连接: http://blog.
1071 0