https://github.com/archriss/react-native-render-html
ul 函数定义如下:
export function ul (htmlAttribs, children, convertedCSSStyles, passProps = {}) {
HTMLRenderers.js 里面还定义了 ol 函数,作用是渲染 ol 标签
ol 函数定义如下:
export const ol = ul;
没错,ol 函数直接和 ul 函数相等。
export function ul (htmlAttribs, children, convertedCSSStyles, passProps = {}) {
//创建 ul 标签所代表的 View 的根视图的样式。 -- 命名为『样式1』
const style = _constructStyles({
//这里写死了 tagName 为 『ul』 -- 命名为 『要点001』
tagName: 'ul',
htmlAttribs,
passProps,
//这里写死了 styleSet 为 『View』
styleSet: 'VIEW'
});
const { allowFontScaling, rawChildren, nodeIndex, key, baseFontStyle, listsPrefixesRenderers } = passProps;
const baseFontSize = baseFontStyle.fontSize || 14;
//处理 ol 或者 ul 下面的 li 标签
children = children && children.map((child, index) => {
const rawChild = rawChildren[index];
let prefix = false;
const rendererArgs = [
htmlAttribs,
children,
convertedCSSStyles,
{
...passProps,
index
}
];
if (rawChild) {
if (rawChild.parentTag === 'ul' && rawChild.tagName === 'li') {
prefix = listsPrefixesRenderers && listsPrefixesRenderers.ul ? listsPrefixesRenderers.ul(...rendererArgs) : (
<View style={{
marginRight: 10,
width: baseFontSize / 2.8,
height: baseFontSize / 2.8,
marginTop: baseFontSize / 2,
borderRadius: baseFontSize / 2.8,
backgroundColor: 'black'
}} />
);
//上面虽然将 tagName 写死了,不过在解析 ol 的子标签的时候,还是通过 else if 对 ol 做了区分处理
} else if (rawChild.parentTag === 'ol' && rawChild.tagName === 'li') {
prefix = listsPrefixesRenderers && listsPrefixesRenderers.ol ? listsPrefixesRenderers.ol(...rendererArgs) : (
<Text allowFontScaling={allowFontScaling} style={{ marginRight: 5, fontSize: baseFontSize }}>{ index + 1 })</Text>
);
}
}
return (
<View key={`list-${nodeIndex}-${index}-${key}`} style={{ flexDirection: 'row', marginBottom: 10 }}>
{ prefix }
<View style={{ flex: 1 }}>{ child }</View>
</View>
);
});
return (
//这里的 style 即 『样式1』
//此 View 即整个 ul 标签的根 View
<View style={style} key={key}>
{/*渲染 li 标签 */}
{ children }
</View>
);
}
export const ol = ul;
/**
* Helper that composes styles with the default style for a tag, the "style" attribute and
* any given addiitional style. Checks everything against the style sets of views, images,
* or texts with prop-types.
* @export
* @param {any} { tagName, htmlAttribs, passProps, additionalStyles, styleSet = 'VIEW' }
* @returns {object}
*/
export function _constructStyles ({ tagName, htmlAttribs, passProps, additionalStyles, styleSet = 'VIEW', baseFontSize }) {
let defaultTextStyles = generateDefaultTextStyles(baseFontSize);
/*** 对于 styleSet 为 『View』的标签,都会加上此默认样式 --- 『要点002』 ***/
let defaultBlockStyles = generateDefaultBlockStyles(baseFontSize);
passProps.ignoredStyles.forEach((ignoredStyle) => {
htmlAttribs[ignoredStyle] && delete htmlAttribs[ignoredStyle];
});
let style = [
//前面 ul 标签 styleSet 传递过来的是写死的 『VIEW』。所以这里取的 『defaultBlockStyles』
//前面 ul 标签 tagName 传递过来的是 『ul』。由于 ol 标签的函数定义和 ul 标签一样,所以,ol 标签对应的函数,传递过来的 tagName 也是 『ul』
(styleSet === 'VIEW' ? defaultBlockStyles : defaultTextStyles)[tagName],
passProps.tagsStyles ? passProps.tagsStyles[tagName] : undefined,
_getElementClassStyles(htmlAttribs, passProps.classesStyles),
htmlAttribs.style ?
cssStringToRNStyle(
htmlAttribs.style,
STYLESETS[styleSet],
{ ...passProps, parentTag: tagName }
) :
undefined
];
if (additionalStyles) {
style = style.concat(!additionalStyles.length ? [additionalStyles] : additionalStyles);
}
return style.filter((style) => style !== undefined);
}
const BASE_FONT_SIZE = 14;
/**
* 构建默认样式
*/
export function generateDefaultBlockStyles (baseFontSize = BASE_FONT_SIZE) {
return {
div: { },
//这里给 ul 标签设置了默认样式
ul: {
paddingLeft: 20,
//这里 baseFontSize 默认为 BASE_FONT_SIZE,即 14.
marginBottom: baseFontSize
},
//这里给 ol 标签设置了默认样式
//实际上,由前面可知, ol 标签解析函数直接等于了 ul 标签解析函数。而 ul 标签解析函数的 tagName 写死的 『ul』,所以下面定义的 ol 标签的样式仅在解析 『child - li』 标签的时候有点用。
ol: {
paddingLeft: 20,
marginBottom: baseFontSize
},
iframe: {
height: 200
},
hr: {
marginTop: baseFontSize / 2,
marginBottom: baseFontSize / 2,
height: 1,
backgroundColor: '#CCC'
}
};
}
使用 react-native-render-html 库时添加的自定义样式
const htmlStyles = {
tag: {
p: {
marginBottom: 44,
paddingLeft: 45,
},
h2: {
marginTop: 50,
marginBottom: 23,
fontWeight: '600',
fontSize: 22,
lineHeight: 31,
},
a: {
color: colors.green1,
fontWeight: '500',
fontSize: 18,
lineHeight: 32,
},
ol: {
//『紫色 ff00ff』
backgroundColor: '#0000ff',
paddingLeft: 50,
},
ul: {
//『浓绿色 00ff00』
backgroundColor: '#00ff00',
paddingLeft: 10,
},
img: {
borderRadius: 4,
paddingLeft: 43,
},
div: {
backgroundColor: '#f0f0f0',
paddingLeft: 0,
},
html: {
backgroundColor: '#00ffff',
paddingLeft: 0,
},
body: {
paddingLeft: 0,
},
},
container: {
//『黑色 000000』
backgroundColor:'#000000',
paddingLeft: 40,
},
};
示例的 react-native-render-html 库的使用
import HTML from 'react-native-render-html';
<HTML html={this.props.html}
style={{
backgroundColor:'#ff00ff',
paddingLeft: 25,
}}
listsPrefixesRenderers={
{
ul: () => {
return (
<Text style={[htmlStyles.text, {width: 20}]}>·</Text>
)
},
ol: (htmlAttribs, children, convertedCSSStyles, passProps) => {
console.log("htmlAttribs = " + htmlAttribs);
return (
//『黄色 yellow』
<Text
style={{backgroundColor:'yellow'}}>{passProps.index + 1}.</Text>
)
}
}
}
imagesMaxWidth={Dimensions.get('window').width}
containerStyle={htmlStyles.container}
baseFontStyle={htmlStyles.text}
tagsStyles={htmlStyles.tag}
debug={true}
onLinkPress={this.jumpToUrl}
/>
『示例的 html 片断』
<ol>
<li>列表第一行列表第一行列表第一行列表第一行列表第一行列表第一行</li>
<li>列表第二行列表第二行列表第二行列表第二行列表第二行列表第二行列表第二行</li>
<li>列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行列表第三行</li>
</ol>
如上,tagsStyles 属性传递的 htmlStyles.tag ,其中定义了 ol『紫色 ff00ff』 和 ul『浓绿色 00ff00』 的样式。根据上面的分析可知,其中 ol 样式对于整个 ol 标签根 View 无效。但是,由于有 else if 语句,ul 函数中,对 ol 标签做了特殊处理。所以 ol 样式对于 ol 标签的『子标签 li』有效。
如上,containerStyle 属性传递的 htmlStyles.container ,其中定义了 paddingLeft 40 和 黑色的背景。
结果,『示例的 html 片断』渲染后,由左向右依次是 container『黑色 000000』 -> ul『浓绿色 00ff00』 -> Text『黄色 yellow』 -> ol『紫色 ff00ff』
import React, { Component } from 'react';
import { Text, Dimensions } from 'react-native';
import HTML from 'react-native-render-html';
import PropTypes from "prop-types";
import colors from '../../base/colors';
import DXYJSBridge from '../../core/DXYJSBridge';
export default class HtmlView extends Component {
static defaultProps = {
html: ''
};
constructor(props) {
super(props);
}
render() {
const htmlStyles = {
text: {
color: colors.grey2,
fontWeight: '300',
fontSize: 18,
lineHeight: 32,
},
tag: {
p: {
marginBottom: 14,
},
h2: {
marginTop: 50,
marginBottom: 20,
fontWeight: '600',
fontSize: 22,
lineHeight: 31,
},
a: {
color: colors.green1,
fontWeight: '500',
fontSize: 18,
lineHeight: 32,
},
ul: {
paddingLeft: 0,
marginBottom: 14,
},
ol: {
paddingLeft: 0,
marginBottom: 14,
},
img: {
borderRadius: 4
}
},
container: {
paddingHorizontal: 16
},
};
return (
<HTML html={this.props.html}
listsPrefixesRenderers={
{
ul: () => {
return (
<Text style={[htmlStyles.text, { width: 20 }]}>·</Text>
)
},
ol: (htmlAttribs, children, convertedCSSStyles, passProps) => {
return (
<Text style={[htmlStyles.text, { width: 20 }]}>{passProps.index + 1}.</Text>
)
}
}
}
imagesMaxWidth={Dimensions.get('window').width - 32}
containerStyle={htmlStyles.container}
baseFontStyle={htmlStyles.text}
tagsStyles={htmlStyles.tag}
onLinkPress={this.jumpToUrl}
/>
);
}
jumpToUrl(event, href) {
DXYJSBridge.invoke('urlJump', {
url: href
}, () => {
});
}
}
HtmlView.propTypes = {
html: PropTypes.string
};
render() {
const ImgMap = {
bg: 'https://img1.dxycdn.com/2019/1030/175/3376768251689520355-22.png',
bg_news: 'https://img1.dxycdn.com/2019/1028/374/3376374396746010087-22.png',
ic_review_green_big: 'https://img1.dxycdn.com/2019/1028/064/3376371562067594441-22.png'
};
const styles = StyleSheet.create({
banner: {
paddingHorizontal: 16,
paddingTop: 9,
paddingBottom: 15,
},
bannerTag: {
paddingHorizontal: 4,
paddingVertical: 2,
backgroundColor: colors.green1,
alignSelf: 'flex-start',
borderBottomLeftRadius: 5,
borderTopRightRadius: 5,
overflow: 'hidden'
},
bannerTagText: {
fontSize: 14,
fontWeight: '500',
lineHeight: 16,
color: 'white',
},
bannerTitle: {
fontSize: 28,
fontWeight: '600',
lineHeight: 42,
color: colors.grey1,
marginBottom: 12,
},
bannerDate: {
fontSize: 12,
lineHeight: 14,
color: colors.black5
},
bannerBgShape: {
width: 75,
height: 78,
position: 'absolute',
right: 0,
bottom: 0
},
commentIcon: {
width: 13,
height: 22,
marginRight: 8
},
commentTitle: {
paddingHorizontal: 16,
paddingVertical: 20,
flexDirection: 'row',
},
commentTitleText: {
color: colors.grey2,
fontWeight: '600',
fontSize: 22,
lineHeight: 24,
},
content: {
paddingTop: 20,
paddingBottom: 80
}
});
return (
<SafeAreaView style={{ flex: 1, backgroundColor: 'white' }} ref={root => this.rootRef = root} >
<ScrollView>
<ImageBackground source={{ uri: ImgMap.bg }} style={[styles.banner]} >
<Image style={styles.bannerBgShape} source={{ uri: ImgMap.bg_news }} />
<View style={styles.bannerTag}>
<Text style={styles.bannerTagText}>每日资讯</Text>
</View>
<Text style={styles.bannerTitle}
numberOfLines={2}
ellipsizeMode={'tail'} >{this.state.title}</Text>
<Text style={styles.bannerDate}>{this.state.dateStr}</Text>
</ImageBackground>
<View style={styles.content}>
<HtmlView html={this.state.content} />
<View style={styles.commentTitle}>
<Image style={styles.commentIcon} source={{ uri: ImgMap.ic_review_green_big }} />
<Text style={styles.commentTitleText}>丁香点评</Text>
</View>
<HtmlView html={this.state.comment} />
</View>
</ScrollView >
<LoadingComponent state={this.state.loadingStatus} />
</SafeAreaView >
);
}