React 如何在组件间插入分隔线

277 阅读2分钟

比如我们有一行卡片,中间需要用分割线隔开,让视觉更美观。

image.png

如果卡片数量固定且较少,很简单,将所有卡片一一写出,中间加上分隔线即可。实际情况卡片数可能较多,一一写出代码量线性增长不好维护,而且很可能数量不定,那就要换种思路了。

🗺️ 解法 1:map + index > 0 去除分割线

条件判断 index > 0 使得开头没有分割线。

const ListWithSeparator: React.FC<IProps> = ({ list }) => {
  return (
    <div style={{ display: 'flex', gap: 40, flexWrap: 'wrap', }}>
      {list.map((item, index) => (
          // 🔥 1. React.Fragment 包裹多个组件
          <React.Fragment key={item.id}>
            {/* 🔥 2. 通过 index 判断使得开头没有分割线 */}
            {index > 0 && (
              <Divider type="vertical" style={{ margin: 0, alignSelf: 'center' }} />
            )}
            
            <Component data={item}> 组件 </Component>
          </React.Fragment>
        ))}
    </div>
  );
};

🔂 解法 2:forEach/for/reduce + index > 0 去除分割线

和解法 1 没本质区别,不展开说。

💉 解法 3:forEach + pop 去除分割线

将多余的分割线放到尾部,然后通过 pop 去除。

const ListWithSeparator: React.FC<IProps> = ({ list }) => {
  const renderList = () => {
    const result = []
    
    list.forEach(item => {
      result.push(<Component data={item}> 组件 </Component>)
      result.push(<Divider type="vertical" style={{ margin: 0, alignSelf: 'center' }} />)
    })
    
    // 去除尾部多余的分隔组件
    result.pop()
    
    return result
  }
  
  return (
    <div style={{ display: 'flex', gap: 40, flexWrap: 'wrap' }}>
      {renderList()}
    </div>
  );
};

当然两次 push 也可以写成一次,效果等同:

result.push(
  <Component data={item}> 组件 </Component>,
  <Divider type="vertical" style={{ margin: 0, alignSelf: 'center' }} />
)

⚖️ 对比

  • map + index代码量少,但需要 React.Fragment 包裹以及设置 key
  • forEach + pop可读性好,代码层面简单没有多余的视觉干扰。性能更好,因为每次遍历无需判断 index(小数组无需关心性能问题)。

🧠 泛化

事情不在于多,而在于好。

每次做完一个需求,我们都要思考下,既然模式固定,能否泛化:

在一系列组件间插入分割线 抽象成在一系列组件间插入组件

实现的话要么组件化要么封装函数。本场景是封装函数。

封装好处是让“小心翼翼移除多余分隔组件”的逻辑被黑盒,调用者无需感知和“担惊受怕”。

我们可以从 array.prorotype.join 汲取灵感,也设计一个类似的函数:

join(Components, Separator): Component[]

不一样的是,我们返回的是数组而非字符串,这样才能被渲染。

两版都给出。

  1. foreEach + pop
import React from 'react';

type Element = React.ReactElement;

export const join = (Components: Element[], Separator: Element): Element[] => {
  const result: Element[] = [];

  Components.forEach(component => {
    result.push(component);
    result.push(Separator);
  });

  // 去除尾部多余的分隔组件
  result.pop();

  return result;
};

  1. map + index < 0 的解法也给出下:
import React from 'react';

type Element = React.ReactElement;

export const join = (Components: Element[], Separator: Element): Element[] => {
  return Components.map((component, index) => (
    <React.Fragment key={index}>
      {index > 0 && Separator}
      {component}
    </React.Fragment>
  ));
};

使用就非常简单了:

{join(
  // 组件列表
  list.map(item) => <Component data={item} />,
  
  // 分割线
  <Divider type="vertical" style={{ margin: 0, alignSelf: 'center' }} />
)}