需求
- 支持文本插入,比如
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的对象,非超链接直接返回文本字符串
javascript
export interface LinkPart {
text: string;
url?: string;
onClick?: string;
}
export type ParsedTextPart = string | LinkPart;
[
{
text: 'baidu',
url: 'https://www.baidu.com/',
},
'other content',
'other content',
];
- 遍历解析后的超链接数组,如果是对象,则渲染超链接;如果是字符串,继续解析
- 解析字符串,判断需要解析的{}里面的文本是否是纯文本,如果是纯文本,则直接Text渲染;如果是react元素,则渲染该元素
TextTemplate.tsx:
javascript
import React, { ReactNode } from 'react';
import { routeCenter } from '@shopeepay-rn/route-center';
import { usePageContainerContext } from '@shopeepay-rn/page-container';
import { StyleProp, Text, TextStyle, View, ViewStyle } from 'react-native';
import { parseLinkText } from '../../utils';
import styles from './styles';
interface Props {
template: string;
// eg: {product_name:'spp', click_node:<Text></Text>}
replaceValueMap: Record<string, string | number | ReactNode>;
textStyle?: StyleProp<TextStyle>;
containerStyle?: StyleProp<ViewStyle>;
}
/**
* 支持解析字符串、解析react元素、解析超链接
* @param template 需要解析的字符串
* @param replaceValueMap 需要替换的key-value,value支持字符串和react元素
* @param textStyle 字体样式
* @param containerStyle 容器样式
* @returns react元素
*/
export const TextTemplate = ({
template,
replaceValueMap,
textStyle,
containerStyle,
}: Props) => {
const { rootTag } = usePageContainerContext();
const parseText = (text: string, index: number) => {
const result: React.ReactNode[] = [];
let lastIndex = 0;
text.replace(/{(\w+)}/g, (match: string, key: string, offset: number) => {
const replaceValue = replaceValueMap[key];
if (offset > lastIndex) {
// 未被匹配到的
result.push(
<Text key={index} style={textStyle}>
{text.substring(lastIndex, offset)}
</Text>
);
}
if (React.isValidElement(replaceValue)) {
// 需要替换的是reactnode元素
result.push(React.cloneElement(replaceValue, { key: index }));
} else if (typeof replaceValue === 'string') {
// 需要替换的是字符串
result.push(
<Text key={index} style={textStyle}>
{replaceValue}
</Text>
);
}
lastIndex = offset + match.length;
return '';
});
if (lastIndex < text.length) {
result.push(
<Text key={index} style={textStyle}>
{text.substring(lastIndex)}
</Text>
);
}
return result;
};
const parseTemplate = (text: string) => {
// 解析链接
const linkTexts = parseLinkText(text);
return linkTexts?.map((part, index) => {
return typeof part === 'string' ? (
// 对于字符串,需要解析 纯字符串 还是 reactnode元素
parseText(part, index)
) : (
<Text
key={index}
style={styles.link}
onPress={() =>
routeCenter.navigateWeb(
part.url || '',
{
navbar: {
title: part.text || '',
},
},
rootTag
)
}
>
{part.text}
</Text>
);
});
};
return (
<View style={[styles.textView, containerStyle]}>
<Text>{parseTemplate(template)}</Text>
</View>
);
};
parseLinkText.ts:
javascript
export interface LinkPart {
text: string;
url?: string;
onClick?: string;
}
export type ParsedTextPart = string | LinkPart;
const parseLinkText = (text: string): ParsedTextPart[] => {
const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
const parts: ParsedTextPart[] = [];
let lastIndex = 0;
text.replace(
regex,
(match: string, p1: string, p2: string, offset: number) => {
if (offset > lastIndex) {
parts.push(text.substring(lastIndex, offset));
}
parts.push({ text: p1, url: p2 });
lastIndex = offset + match.length;
return '';
}
);
if (lastIndex < text.length) {
parts.push(text.substring(lastIndex));
}
return parts;
};
// FIXME: 添加 unit test
export { parseLinkText };
使用
javascript
<TextTemplate
template={"you can test the TextTemplate component, parse {string_text}, parse [baidu](https://www.baidu.com/) link, parse {click_node} to show popup"}
replaceValueMap={{string_text:"test string",click_node:<Text>other react node</Text>}}
textStyle={styles.titleText}
/>