react-native-render-html 库 从 ul 或 ol 标签渲染流程分析 ol 标签左边有 20单位的空白区域无法移除的问题

react-native-render-html 仓库地址 (下面源码指的 4.1.2 版)

https://github.com/archriss/react-native-render-html

HTMLRenderers.js 里面定义了 ul 函数,作用是渲染 ul 标签

ul 函数定义如下:

export function ul (htmlAttribs, children, convertedCSSStyles, passProps = {}) {

HTMLRenderers.js 里面还定义了 ol 函数,作用是渲染 ol 标签

ol 函数定义如下:

export const ol = ul;

没错,ol 函数直接和 ul 函数相等。

分析 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;

分析 HTMLStyles.js 的 _constructStyles 函数。根据『要点001』可知,调用 _constructStyles 时传递的 tagName 为『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);
}

分析 HTMLDefaultStyles.js generateDefaultBlockStyles 函数

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』

总结

HTMLRenderers.js 中 ol 函数直接等于了 ul 函数

ul 渲染函数交 tagName 写死成了『ul』,并通过 HTMLStyles.js 的 _constructStyles 函数创建了 『ul 标签(或者 ol 标签)』的『根 View』 的样式。

所以 ol 标签『根 View 』的样式只会跟着 『ul』标签的样式走

由于 ul 渲染函数中有『 else if (rawChild.parentTag === 'ol' && rawChild.tagName === 'li')』语法,所以 ol 标签下子标签的样式会继承自定义的 ol 标签样式,而非 ul 标签样式。

完整可用的示例

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 >
    );
  }
发布了42 篇原创文章 · 获赞 3 · 访问量 3万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章