React Native渲染Sanity富文本: 解决"Text strings must be rendered within a <Text> component"错误
2024-11-22 09:33:55
React Native 中使用 @sanity/block-content-to-react 渲染富文本的常见错误与解决方案
在 React Native 项目中集成来自 Sanity.io 的富文本内容时,使用 @sanity/block-content-to-react
库是一个不错的选择。 虽然该库声称对 React Native 提供实验性支持,但在实际使用中,开发者经常会遇到 “Text strings must be rendered within a
错误原因分析
这个错误的核心在于 React Native 的渲染机制。与 Web 不同,React Native 要求所有文本内容都必须包裹在 <Text>
组件内。 @sanity/block-content-to-react
默认情况下会将 Sanity 数据转换为 HTML 元素,例如 <p>
、<span>
等,这些元素在 React Native 中无法直接渲染。因此,即使你使用了 serializers,如果 serializers 的实现不够全面,仍然可能导致某些文本节点没有被正确地包裹在 <Text>
组件内,从而引发错误。
解决方案
1. 全面覆盖 serializers
问题通常出现在某些特定的 Sanity block 类型没有被 serializers 覆盖到。 为了确保所有文本都被正确渲染,需要提供一个更全面的 serializers
对象。 这意味着需要处理各种可能出现的块类型,包括段落 (block
)、标题 (h1
-h6
)、列表 (list
)、列表项 (listItem
)、标记 (marks
) 等等。
import BlockContent from '@sanity/block-content-to-react';
import { Text, View, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
text: {
// 添加你需要的样式
},
});
const serializers = {
types: {
block: (props) => <Text style={styles.text}>{props.children}</Text>,
h1: (props) => <Text style={[styles.text, { fontSize: 32 }]}>{props.children}</Text>,
// ... 其他标题类型 h2, h3, h4, h5, h6 ...
list: (props) => {
const {type} = props.node;
if(type === 'bullet'){
return <View>{props.children}</View>
}
if (type === 'number') {
return <View>{props.children}</View>
}
},
listItem: (props) => <View style={{flexDirection: props.node.level === 1 ? 'row':'column'}}> {props.children} </View>,
//处理marks
marks: {
strong: (props) => <Text style={{ fontWeight: 'bold' }}>{props.children}</Text>,
em: (props) => <Text style={{ fontStyle: 'italic' }}>{props.children}</Text>,
// ... 其他 marks 类型,例如 underline, code 等等
},
// 其他类型,如 images, code blocks 等
// image: ...
// code: ...
},
};
// ... 在你的组件中使用
<BlockContent blocks={answer} serializers={serializers} />
需要注意的是,listItem
使用 View
包裹是为了处理嵌套列表的情况, 并根据嵌套级别调整 flexDirection
。
2. 自定义渲染函数并递归处理文本节点
如果遇到一些无法预料的 block 类型或者 mark 类型,可以编写一个自定义的渲染函数,递归遍历所有子节点,确保所有文本节点都被 <Text>
组件包裹。
import { Text, View } from 'react-native';
function renderBlockContent(block) {
if (typeof block === 'string') {
return <Text>{block}</Text>;
}
if (Array.isArray(block)) {
return block.map(renderBlockContent);
}
if (typeof block === 'object' && block !== null) {
// 如果block是span类型
if (block.marks) {
// 这里进行对span标签中的其他mark进行递归处理
return (block.marks.reduce( (element, mark) => {
// 根据span标签mark type来执行对应的function
const renderMark = serializers.marks[mark];
return renderMark && typeof renderMark === 'function' ?
renderMark({ children: element}):
element
} ,<Text> { block.text } </Text> ));
}
if (block.children) {
return <View>{block.children.map(renderBlockContent)}</View>;
}
//针对没有处理到的block,直接return <></>,也可以选择返回一个占位字符什么的
return <></>
}
return null;
}
const serializers = {
// 其他serializer...
//用renderBlockContent作为兜底
default: renderBlockContent
};
// ... 在你的组件中使用
<BlockContent blocks={answer} serializers={serializers} />;
这个方案通过递归遍历,可以处理各种复杂的嵌套结构,确保所有文本节点都被正确处理。 但是,这种方法可能会影响性能,尤其是在处理大量数据时。
通过以上方法,可以有效地解决 “Text strings must be rendered within a serializers
对象是最佳实践,可以减少潜在问题的出现。 始终仔细检查你的 Sanity schema,确保你的 serializers
覆盖了所有可能的 block 类型和 mark 类型。