react里面如何封装一个通用的Ellipsis组件

背景

在平时的业务场景中,为了提升用户体验,经常会使用固定宽度,超出显示...的css样式。但是每次都写一次,比较麻烦,为了更好的摸鱼,所以把Ellipsis组件抽离出来。

先捋一下要实现的功能:

  • 固定宽度,超出显示...
  • 鼠标hover上去,能看到全部
  • 支持多行显示...
  • 考虑resize场景

思路

计算要渲染文字的宽度,如果宽度超出,就显示...

这里的问题是,如何计算文字的宽度呢?

  • 可以先把文字在看不见的地方先渲染出来,获取到实际宽度,然后再去比较
  • 直接给容器设置overflow:hidden,然后比较scrollWidthclientWidth的值

这里我用的是第二种。

import React, { FC, useCallback, useLayoutEffect, useRef, useState } from 'react';
import { Tooltip } from 'antd';
import debounce from 'lodash/debounce';
import useDimensions from 'react-cool-dimensions';
import { TooltipProps } from 'antd/lib';


interface EllipsisProps {
  line?: number
  style?: React.CSSProperties
  children?: React.ReactElement
}
const Ellipsis: FC<EllipsisProps & TooltipProps> = (props) => {
  const {
    children,
    line = 1,
    style: currentStyle = {},
    ...other
  } = props;
  const wrapper = useRef<HTMLElement>();
  const [showPopover, setShowPopover] = useState(false);
  // 先设置overflow: 'hidden', 这样超出了才能进行比较
  const defaultStyle: React.CSSProperties = line === 1 ? {
    display: 'block',
    maxWidth: '100%',
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    whiteSpace: 'nowrap',
    ...currentStyle
  } : {
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    display: '-webkit-box',
    WebkitBoxOrient: 'vertical',
    WebkitLineClamp: line,
    ...currentStyle
  }
  const validShowPopover = () => {
    // 当line为1的时候,比较width
    // 否则,比较height
    const {
      scrollWidth = 0,
      clientWidth = 0,
      scrollHeight = 0,
      clientHeight = 0
    } = wrapper.current as HTMLElement || {};
    setShowPopover(scrollWidth > clientWidth || scrollHeight > clientHeight)
  }
  const debounceMesaure = useCallback(debounce(() => {
    validShowPopover();
  }, 200), [validShowPopover]);
  // 支持resize
  const { observe } = useDimensions({
    onResize: debounceMesaure,
  });
  useEffect(() => {
    validShowPopover();
  }, []);

  const renderChildren = () => {
    return React.cloneElement(children, {
      style: defaultStyle,
      ref: (element: HTMLElement | undefined) => {
        observe(element);
        wrapper.current = element;
      },
    })
  }
  // 基于ant的Tooltip组件
  if (showPopover) {
    return <Tooltip {...other}>{renderChildren()}</Tooltip>
  }
  return renderChildren();
}

export default Ellipsis

使用

Ellipsis组件的children必须是一个html元素标签

如果要渲染的元素没有给定宽度的话,需要手动向Ellipsis组件传一个width参数

import Ellipsis from './Ellipsis';
const EllipsisPage = () => {
  return <div>
    <Ellipsis style={{ width: 200 }} title={text} line={2}>
      <span>{text}</span>
    </Ellipsis>
  </div>
}

export default EllipsisPage;

如果要渲染的元素已经有指定的宽度,则不需要

 
import Ellipsis from './Ellipsis';
const EllipsisPage = () => {
  return <div>
    <div style={{ width: 200 }}>
      <Ellipsis title={text} line={1}>
        <span>{text}</span>
      </Ellipsis>
    </div>
  </div>
}

export default EllipsisPage;

更多前端面试资料: 前端资料

<think>我们正在讨论的文本溢出处理组件(TextOverflowHandler)被设计为通用的、可复用的组件。根据引用[2][3][4]中提到的场景,在同一个页面中多次使用同一个组件是常见的需求。我们的组件完全支持在同一个页面的多个位置使用,且不会相互干扰。 关键设计点: 1. **组件独立性**:每个组件实例通过绑定到各自的容器元素(.ellipsis-container)来实现独立运作。每个实例管理自己的DOM元素和状态。 2. **无全局状态**:组件不依赖全局状态,所有状态(如是否溢出、工具提示内容)都存储在实例内部,因此多个实例不会相互干扰。 3. **ResizeObserver隔离**:每个实例监听自己的容器和内容元素的大小变化,互不影响。 引用[4]中提到在Vue中复用同一个组件时可能出现数据相互干扰的问题,这是因为如果组件内部使用了共享的静态数据或没有正确管理实例状态就会导致问题。我们的组件设计避免了这个问题,因为: - 每个组件实例都是独立的,通过构造函数创建,拥有自己的属性和方法。 - 在初始化时,我们通过`new TextOverflowHandler(container)`为每个容器创建独立的实例。 因此,你可以在同一个页面中多次使用该组件,例如: ```html <!-- 第一个实例 --> <div class="ellipsis-container"> <div class="ellipsis-content ellipsis-single">第一个长文本内容...</div> <div class="custom-tooltip"></div> </div> <!-- 第二个实例 --> <div class="ellipsis-container"> <div class="ellipsis-content ellipsis-multi" style="--lines:2">第二个多行文本内容...</div> <div class="custom-tooltip"></div> </div> ``` 在脚本中,我们通过遍历所有.ellipsis-container元素来初始化多个实例: ```javascript document.querySelectorAll('.ellipsis-container').forEach(container => { new TextOverflowHandler(container); }); ``` 每个实例都会独立检测自己容器内的文本溢出情况,并独立显示工具提示。 引用[3]中提到在同一个页面多次引入同一个地图组件时,只有一个组件有数据的问题。这通常是因为组件内部使用了全局变量或静态属性,导致多个实例共享了相同的状态。我们的文本溢出组件没有这样的问题,因为它不依赖任何全局状态,每个实例都是独立的。 因此,你可以放心地在同一个页面中使用多个文本溢出处理组件,它们会各自独立工作。 如果你是在Vue或React框架中使用,同样可以封装框架组件,并且通过为每个组件实例创建独立的TextOverflowHandler实例来保证独立性(具体框架组件封装方法可以参考相关问题的回答)。 总结:该文本溢出处理组件支持在同一个页面中多次使用,且多个实例之间不会相互干扰。</think>基于您的查询和提供的引用内容,文本溢出处理组件**完全支持在单个页面的多个位置重复使用**,且不会相互干扰。以下是关键说明和技术实现: ### 一、多实例支持原理 1. **独立实例化机制** 通过遍历所有`.ellipsis-container`容器,为每个容器创建独立实例: ```javascript document.querySelectorAll('.ellipsis-container').forEach(container => { new TextOverflowHandler(container); // 每个容器独立实例化 }); ``` 2. **实例隔离设计** 每个实例绑定专属DOM元素,通过闭包隔离状态(引用[4]解决方案): ```javascript class TextOverflowHandler { constructor(container) { this.container = container; // 绑定专属DOM节点 this.contentElement = container.querySelector('.ellipsis-content'); // 其他私有属性... } } ``` 3. **无全局状态依赖** 所有检测逻辑基于当前容器计算,避免共享状态(引用[3]问题的反面案例): ```javascript isOverflowing() { return this.contentElement.scrollWidth > this.contentElement.clientWidth; // 仅检测当前实例 } ``` ### 二、使用示例 ```html <!-- 位置1:表格单元格 --> <div class="table-cell"> <div class="ellipsis-container"> <div class="ellipsis-content ellipsis-single">产品描述文本...</div> <div class="custom-tooltip"></div> </div> </div> <!-- 位置2:卡片描述 --> <div class="card"> <div class="ellipsis-container"> <div class="ellipsis-content ellipsis-multi" style="--lines:2">多行描述文本...</div> <div class="custom-tooltip"></div> </div> </div> <!-- 位置3:导航菜单 --> <nav> <div class="ellipsis-container"> <div class="ellipsis-content ellipsis-single">超长菜单项名称...</div> <div class="custom-tooltip"></div> </div> </nav> ``` ### 三、框架集成建议(引用[2][4]) 在Vue/React中复用组件时,确保: 1. **Key属性隔离**(Vue) ```vue <template> <TextOverflow :key="'comp1'" /> <TextOverflow :key="'comp2'" /> </template> ``` 2. **动态组件注册**(引用[4]方案) ```javascript components: { CompA: () => import('./TextOverflow.vue'), CompB: () => import('./TextOverflow.vue') // 独立实例 } ``` 3. **Props数据隔离**(React) ```jsx function Page() { return ( <> <TextOverflow content={data1} /> <TextOverflow content={data2} /> </> ) } ``` ### 四、多实例验证指标 1. **独立检测**:每个实例根据自身容器尺寸判断溢出 2. **事件隔离**:悬停提示仅触发当前实例的tooltip 3. **响应式独立**:ResizeObserver监听各自容器 4. **无交叉渲染**:组件内部无共享状态变量 > 该设计已通过引用[5]同类组件(KindEditor多实例)的验证模式,完全满足同页面多位置复用需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值