返回

React Native渲染Sanity富文本: 解决"Text strings must be rendered within a <Text> component"错误

javascript

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 component” 的错误。本文将深入分析该错误产生的原因,并提供一些实用的解决方案。

错误原因分析

这个错误的核心在于 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 component” 错误,确保在 React Native 应用中正确渲染 Sanity.io 的富文本内容。 在实际开发中,建议根据项目的具体情况选择合适的解决方案,并进行充分的测试。 此外,维护一个全面的 serializers 对象是最佳实践,可以减少潜在问题的出现。 始终仔细检查你的 Sanity schema,确保你的 serializers 覆盖了所有可能的 block 类型和 mark 类型。