React Native 文本渲染前获取行数 解决CollapsibleText折叠组件BUG
解决方案
你遇到的闪烁问题根因是初始isCollapsed状态为false,首次渲染会先加载完整文本,等onTextLayout回调触发后才切换到折叠状态,两者的时间差导致了可见的内容跳变。该需求可以实现,不需要预渲染可见的完整文本,用隐藏测量层的方案即可解决:
核心逻辑
新增一个和展示文本完全同样式的测量节点,设置为绝对定位、透明度为0、不占布局空间,全程对用户不可见,专门用来计算文本总行数。展示文本初始直接按折叠状态渲染,不会出现完整文本闪现的问题。
优化后代码
import { useEffect, useRef, useState, useCallback } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; import { useTheme } from '@react-navigation/native'; // 按需替换为你实际的主题引入 import { useLanguage, useIsMounted } from '你的工具路径'; // 按需替换为实际引入路径 export default function CollapsibleText({ children = "", numberOfLines = 3, ellipsizeMode = "tail", containerStyle, textStyle = styles.text, readMoreButtonStyle = styles.readMoreButton, readMoreTextStyle = styles.readMoreText, }) { const { colors } = useTheme(); const isMounted = useIsMounted(); const { t } = useLanguage(); const measured = useRef(false); const [isCollapsed, setIsCollapsed] = useState(true); // 初始直接为折叠态 const [showReadMoreButton, setShowReadMoreButton] = useState(false); const toggleIsCollapsed = () => { setIsCollapsed((prevIsCollapsed) => !prevIsCollapsed); }; const handleOnMeasure = useCallback( ({ nativeEvent: { lines } }) => { if (isMounted() && !measured.current && lines.length > numberOfLines) { measured.current = true; setShowReadMoreButton(true); } }, [isMounted, numberOfLines] ); // 文本内容、样式、行数限制变更时重置测量状态 useEffect(() => { measured.current = false; setShowReadMoreButton(false); setIsCollapsed(true); }, [children, textStyle, numberOfLines]); const renderReadMoreButton = () => ( <TouchableOpacity activeOpacity={0.75} onPress={toggleIsCollapsed} style={readMoreButtonStyle} > <Text style={[{ color: colors.silverChalice }, readMoreTextStyle]}> {t(`collapsibleText.${isCollapsed ? "readMore" : "readLess"}`)} </Text> </TouchableOpacity> ); return ( <View style={containerStyle}> {/* 隐藏测量节点,全程不可见 */} <Text style={[textStyle, { position: 'absolute', opacity: 0, zIndex: -1 }]} onTextLayout={handleOnMeasure} > {children} </Text> {/* 实际展示的文本节点,初始就按折叠态渲染 */} <Text numberOfLines={isCollapsed ? numberOfLines : undefined} ellipsizeMode={ellipsizeMode} style={textStyle} > {children} </Text> {showReadMoreButton && renderReadMoreButton()} </View> ); } // 示例样式,按需替换 const styles = { text: { fontSize: 14, lineHeight: 20 }, readMoreButton: { marginTop: 4 }, readMoreText: { fontSize: 13 } };
说明
- 测量节点不会占用布局空间,也不会被用户看到,不存在闪烁问题
- 新增了内容、样式、行数限制变更时的重置逻辑,避免动态更新时计算错误
- 修正了原代码中
useCallback依赖项缺失的问题,避免潜在逻辑异常
内容的提问来源于stack exchange,提问作者Raul
相关产品推荐
相关产品推荐

