【RN】实现markdown文本简单解析

打印 上一主题 下一主题

主题 1040|帖子 1040|积分 3120

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
需求



  • 支持文本插入,比如 xxx {product_name} xxx ,如果提供了product_name变量的值为feedback,则可以渲染出 xxx feedback xxx。
  • 支持链接解析,比如 [baidu](https://www.baidu.com/),可以直接渲染成超链接的形式。
  • 支持插入reactnode元素,比如 xxx {jump_node} xxx,且jump_node是一个reactnode元素,则可以将node节点插入到{}位置上。
实现

步骤:


  • 先解析链接, 返回如许子的数据布局。超链接返回url-text的对象,非超链接直接返回文本字符串
  1. export interface LinkPart {
  2.   text: string;
  3.   url?: string;
  4.   onClick?: string;
  5. }
  6. export type ParsedTextPart = string | LinkPart;
  7. [
  8.   {
  9.     text: 'baidu',
  10.     url: 'https://www.baidu.com/',
  11.   },
  12.   'other content',
  13.   'other content',
  14. ];
复制代码


  • 遍历解析后的超链接数组,如果是对象,则渲染超链接;如果是字符串,继续解析
  • 解析字符串,判定必要解析的{}里面的文本是否是纯文本,如果是纯文本,则直接Text渲染;如果是react元素,则渲染该元素
TextTemplate.tsx:
  1. import React, { ReactNode } from 'react';
  2. import { routeCenter } from '@shopeepay-rn/route-center';
  3. import { usePageContainerContext } from '@shopeepay-rn/page-container';
  4. import { StyleProp, Text, TextStyle, View, ViewStyle } from 'react-native';
  5. import { parseLinkText } from '../../utils';
  6. import styles from './styles';
  7. interface Props {
  8.   template: string;
  9.   // eg: {product_name:'spp', click_node:<Text></Text>}
  10.   replaceValueMap: Record<string, string | number | ReactNode>;
  11.   textStyle?: StyleProp<TextStyle>;
  12.   containerStyle?: StyleProp<ViewStyle>;
  13. }
  14. /**
  15. * 支持解析字符串、解析react元素、解析超链接
  16. * @param template 需要解析的字符串
  17. * @param replaceValueMap 需要替换的key-value,value支持字符串和react元素
  18. * @param textStyle 字体样式
  19. * @param containerStyle 容器样式
  20. * @returns react元素
  21. */
  22. export const TextTemplate = ({
  23.   template,
  24.   replaceValueMap,
  25.   textStyle,
  26.   containerStyle,
  27. }: Props) => {
  28.   const { rootTag } = usePageContainerContext();
  29.   const parseText = (text: string, index: number) => {
  30.     const result: React.ReactNode[] = [];
  31.     let lastIndex = 0;
  32.     text.replace(/{(\w+)}/g, (match: string, key: string, offset: number) => {
  33.       const replaceValue = replaceValueMap[key];
  34.       if (offset > lastIndex) {
  35.         // 未被匹配到的
  36.         result.push(
  37.           <Text key={index} style={textStyle}>
  38.             {text.substring(lastIndex, offset)}
  39.           </Text>
  40.         );
  41.       }
  42.       if (React.isValidElement(replaceValue)) {
  43.         // 需要替换的是reactnode元素
  44.         result.push(React.cloneElement(replaceValue, { key: index }));
  45.       } else if (typeof replaceValue === 'string') {
  46.         // 需要替换的是字符串
  47.         result.push(
  48.           <Text key={index} style={textStyle}>
  49.             {replaceValue}
  50.           </Text>
  51.         );
  52.       }
  53.       lastIndex = offset + match.length;
  54.       return '';
  55.     });
  56.     if (lastIndex < text.length) {
  57.       result.push(
  58.         <Text key={index} style={textStyle}>
  59.           {text.substring(lastIndex)}
  60.         </Text>
  61.       );
  62.     }
  63.     return result;
  64.   };
  65.   const parseTemplate = (text: string) => {
  66.     // 解析链接
  67.     const linkTexts = parseLinkText(text);
  68.     return linkTexts?.map((part, index) => {
  69.       return typeof part === 'string' ? (
  70.         // 对于字符串,需要解析 纯字符串 还是 reactnode元素
  71.         parseText(part, index)
  72.       ) : (
  73.         <Text
  74.           key={index}
  75.           style={styles.link}
  76.           onPress={() =>
  77.             routeCenter.navigateWeb(
  78.               part.url || '',
  79.               {
  80.                 navbar: {
  81.                   title: part.text || '',
  82.                 },
  83.               },
  84.               rootTag
  85.             )
  86.           }
  87.         >
  88.           {part.text}
  89.         </Text>
  90.       );
  91.     });
  92.   };
  93.   return (
  94.     <View style={[styles.textView, containerStyle]}>
  95.       <Text>{parseTemplate(template)}</Text>
  96.     </View>
  97.   );
  98. };
复制代码
parseLinkText.ts:
  1. export interface LinkPart {
  2.   text: string;
  3.   url?: string;
  4.   onClick?: string;
  5. }
  6. export type ParsedTextPart = string | LinkPart;
  7. const parseLinkText = (text: string): ParsedTextPart[] => {
  8.   const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
  9.   const parts: ParsedTextPart[] = [];
  10.   let lastIndex = 0;
  11.   text.replace(
  12.     regex,
  13.     (match: string, p1: string, p2: string, offset: number) => {
  14.       if (offset > lastIndex) {
  15.         parts.push(text.substring(lastIndex, offset));
  16.       }
  17.       parts.push({ text: p1, url: p2 });
  18.       lastIndex = offset + match.length;
  19.       return '';
  20.     }
  21.   );
  22.   if (lastIndex < text.length) {
  23.     parts.push(text.substring(lastIndex));
  24.   }
  25.   return parts;
  26. };
  27. // FIXME: 添加 unit test
  28. export { parseLinkText };
复制代码
使用

  1. <TextTemplate
  2.   template={"you can test the TextTemplate component, parse {string_text}, parse [baidu](https://www.baidu.com/) link, parse {click_node} to show popup"}
  3.   replaceValueMap={{string_text:"test string",click_node:<Text>other react node</Text>}}
  4.   textStyle={styles.titleText}
  5. />
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

莫张周刘王

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表