微信小程序开发问题集

setData: invokeWebviewMethod 数据传输长度为 *** 已经超过最大长度 1048576

在小程序中执行 setData 时,如果数据内容过大的话,会出现数据传输长度超过最大长度的问题。其原因在于 setData 实际上是在 webview 上执行了 stringByEvaluatingJavaScriptFromString 这类方法,如果传入数据过大的话,就会使 webview 的内存开销过大,JavaScript 代码执行失败。

开发中,在存在大量数据的情况下,可以对数据执行 map 之类的操作,仅留下需要展示的数据以及对应的 key,这样每次 setData 的数据就会小很多。


rich-text 数据来源

由于小程序不支持 iframe 这样的组件,所以在需要展示 html 页面的时候,只能使用 rich-text

欲显示的 html 内容最好在服务器端转换成数组的形式,有以下几个原因:

  1. 根据小程序开发文档所言,nodes 属性推荐使用 Array 类型,由于组件会将 String 类型转换为 Array 类型,因而性能会有所下降
  2. rich-text 仅支持有限的 html 标签。对于不支持的标签,一部分可以替换成 span 或者 tag 这类它支持的标签。剩下的也可以根据实际情况直接去除或者替换成提示文字。
  3. 小程序中的 wxss 样式仅能根据 class 生效,而原 html 文档的 css 文件中会有使用类型选择器,为了能使用原有的这些样式,为每一个元素增加类似 __tag__span 这样的类名,同时对原 css 执行正则表达式将其中的 span {...} 替换成 .__tag__span {...} 的形式
  4. 剔除 rich-text 不支持的 attr

以上这些操作适合在服务器端执行,代码如下:

const parse5 = require('parse5');
const entities = require('entities');

const AVAILABLE_ATTRS = [
  'class',
  'style',
  'span',
  'width',
  'alt',
  'src',
  'height',
  'start',
  'type',
  'colspan',
  'rowspan',
];

const AVAILABLE_TAGS = [
  'a', 'abbr', 'b', 'blockquote',
  'br', 'code', 'col', 'colgroup',
  'dd', 'del', 'div', 'dl', 'dt',
  'em', 'fieldset', 'h1', 'h2', 'h3',
  'h4', 'h5', 'h6', 'hr', 'i', 'img',
  'ins', 'label', 'legend', 'li',
  'ol', 'p', 'q', 'span', 'strong',
  'sub', 'sup', 'table', 'tbody',
  'td', 'tfoot', 'th', 'thead',
  'tr', 'ul',
];

const REPLACEABLE_TAGS = [
  'pre', 'small', 'var', 'button', 'font', 'details',
  'summary', 'caption', 'figure', 'figcaption', 'dfn',
  'string', 's',
];

const REPLACE_MAP = {
  pre: 'div',
  small: 'span',
  var: 'span',
  button: 'span',
  font: 'span',
  details: 'div',
  summary: 'div',
  caption: 'div',
  figure: 'div',
  figcaption: 'div',
  dfn: 'span',
  string: 'span',
  s: 'span',
};

const IGNOREABLE_TAGS = [
  'meta',
];

function parseNode(ctx, node) {
  const { childNodes, tagName, attrs: _attrs } = node;
  let needOverrideTagName = false;

  if (node.nodeName === '#text') {
    return {
      type: 'text',
      text: entities.decodeHTML(node.value),
    };
  }

  if (AVAILABLE_TAGS.indexOf(tagName) === -1) {
    if (IGNOREABLE_TAGS.indexOf(tagName) > -1) {
      return null;
    } else if (REPLACEABLE_TAGS.indexOf(tagName) > -1) {
      needOverrideTagName = true;
    } else {
      if (tagName !== 'iframe') {
        ctx.logger.info(`UNSUPPORT TAG: ${tagName}`);
      }

      return {
        name: 'div',
        attrs: {
          class: 'content-unavailable',
        },
        children: [
          {
            type: 'text',
            text: '此处内容无法在当前环境中显示',
          },
        ],
      };
    }
  }

  let children;
  if (childNodes && childNodes.length > 0) {
    children = childNodes.map(node => parseNode(ctx, node)).filter(child => !!child);
  }

  const attrs = _attrs
    .filter(attr => AVAILABLE_ATTRS.indexOf(attr.name) > -1)
    .reduce((obj, attr) => {
      obj[attr.name] = attr.value;
      return obj;
    }, {});

  const classNames = attrs.class ? attrs.class.split(' ') : [];
  classNames.push(`__tag_${tagName}`, '__univ');
  attrs.class = classNames.join(' ');

  const nodeObj = {
    name: needOverrideTagName ? REPLACE_MAP[tagName] : tagName,
    attrs,
    children,
  };

  return nodeObj;
}

function parseHtml(ctx, html) {
  const documentFragment = parse5.parseFragment(html);
  const nodes = documentFragment.childNodes.map(node => parseNode(ctx, node));
  return nodes;
}

rich-text setData

在使用 setDatarich-textnodes 属性赋值时,如果元素过多,则可能出现页面卡顿或者数据传输长度超过最大长度的问题。此时,可以将 nodes 数组分成适量片段,分段 setData

<rich-text wx:for="{{ nodesList }}" wx:for-index="index" wx:for-item="nodes" nodes="{{nodes}}"></rich-text>
const nodes = ***;
const nodesList = [];
const lastNodes = nodes.reduce((arr, cur) => {
  const curArr = [cur];
  const newArr = arr.concat(curArr);
  if (JSON.stringify(newArr).length > (1048576 - 1024 * 1000)) {
    nodesList.push(arr);
    return curArr;
  } else {
    return newArr;
  }
}, []);
nodesList.push(lastNodes);

this.setData({
  'nodesList': []
});
nodesList.forEach((nodes, idx) => {
  if (idx === 0) {
    this.setData({
      [`nodesList[${idx}]`]: nodes,
      loading: false
    });
  } else {
    this.setData({
      [`nodesList[${idx}]`]: nodes
    });
  }
});