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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章