专栏首页IMWeb前端团队微信小程序之SelectorQuery

微信小程序之SelectorQuery

本文作者:IMWeb howenhuo 原文出处:IMWeb社区 未经同意,禁止转载

在开发小程序展开全文组件时需要用到节点查询API - wx.createSelectorQuery() 来查询全文内容的高度。

  • wx.createSelectorQuery() 返回一个 SelectorQuery 对象实例。
  • SelectorQuery 有五个方法(inselectselectAllselectViewportexec),第一个返回 SelectorQuery,后四个返回 NodesRef
  • NodesRef 有四个方法(fieldsboundingClientRectscrollOffsetcontext),第一个返回 NodesRef,后三个返回 SelectorQuery

对照官方提供的示例代码来看

const query = wx.createSelectorQuery() // query 是 SelectorQuery 对象
query.select('#the-id').boundingClientRect() // select 后是 NodesRef 对象,然后 boundingClientRect 返回 SelectorQuery 对象
query.selectViewport().scrollOffset() // selectViewport 后是 NodesRef 对象,然后 scrollOffset 返回 SelectorQuery 对象
query.exec(function (res) { // exec 返回 NodesRef 对象
  res[0].top // #the-id节点的上边界坐标
  res[1].scrollTop // 显示区域的竖直滚动位置
})

问题:每行执行返回的 SelectorQuery 对象是相同的吗?

答案:是的,都是同一个对象。

问题:直接执行 query.select('#the-id').boundingClientRect().exec 也可以吗?

答案:可以,boundingClientRect() 返回就是 query

问题:这样连写 query.select('#the-id').boundingClientRect().selectViewport().scrollOffset() 算两条查询请求吗?

答案:是两条请求。

问题:query.exec 执行后会清空前面的查询请求吗?再次执行还能拿到结果吗?

答案:可以,query 不会清空请求。

问题:boundingClientRectscrollOffset 可以接受 callback 参数,它与 query.exec 执行顺序是怎样,修改 res 结果会影响到后面的 callback 吗?

答案:先执行 boundingClientRectscrollOffsetcallback,再执行 query.execcallback;修改 res 结果会影响到后面 exec 的结果。

上面的问题通过小程序开发者工具中的 WAService.js 源码简单美化还原后可以了解 SelectorQuery 的代码逻辑

SelectorQuery.js

import NodesRef from 'NodesRef';
import requestComponentInfo from 'requestComponentInfo';

export default function(pluginId) {
  return class SelectorQuery {
    constructor(plugin) {
      if (plugin && plugin.page) {
        this._component = this._defaultComponent = plugin.page;
        this._webviewId = this._defaultComponent.__wxWebviewId__;
      } else {
        var pages = __internalGlobal__.getCurrentPagesByDomain('');
        this._defaultComponent = pages[pages.length - 1],
        this._component = null;
        this._webviewId = null;
      }
      this._queue = [];
      this._queueCb = [];
    }
    in(component) {
      if (!this._webviewId) {
        this._webviewId = component.__wxWebviewId__;
        this._component = component;
      } else if (this._webviewId !== component.__wxWebviewId__) {
        console.error('A single SelectorQuery could not work in components in different pages. A SelectorQuery#in call has been ignored.');
      } else {
        this._component = component;
      }
      return this;
    }
    select(selector) {
      return new NodesRef(this, this._component, selector, true);
    }
    selectAll(selector) {
      return new NodesRef(this, this._component, selector, false);
    }
    selectViewport() {
      return new NodesRef(this, 0, '', true);
    }
    _push(selector, component, single, fields, callback) {
      if (!this._webviewId) {
        this._webviewId = this._defaultComponent ? this._defaultComponent.__wxWebviewId__ : undefined;
      }
      const rootNodeId = pluginId ? '' : r.getRootNodeId(this._webviewId);
      this._queue.push({
        component: null != component ? (0 === component ? 0 : component.__wxExparserNodeId__) : rootNodeId,
        selector,
        single,
        fields,
      });
      this._queueCb.push(callback || null);
    }
    exec(callback) {
      requestComponentInfo(this._webviewId, {
        pluginId,
        queue: this._queue,
      }, (results) => {
        const queueCb = this._queueCb;
        results.forEach((res, index) => {
          if ('function' == typeof queueCb[index]) {
            queueCb[index].call(this, res);
          }
        });
        if ('function' == typeof callback) {
          callback.call(this, results);
        }
      })
    }
  }
}

requestComponentInfo.js

const subscribe = function(eventType, callback) {
  const _callback = function(event, webviewId, nativeInfo = {}) {
    const { data = {}, options } = event;
    const startTime = options && options.timestamp || 0;
    const endTime = Date.now();
    if ('function' == typeof callback) {
      callback(data, webviewId);
      Reporter.speedReport({
        key: 'webview2AppService',
        data,
        timeMark: {
          startTime,
          endTime,
          nativeTime: nativeInfo.nativeTime || 0,
        }
      });
    }
  };
  __safeway__.bridge.subscribe(eventType, _callback);
}

const publish = function(eventType, data, webviewIds) {
  const event = {
    data,
    options: {
      timestamp: Date.now(),
    }
  };
  __safeway__.bridge.publish(eventType, event, webviewIds);
}

const requestCb = {};
let requestId = 1;
subscribe('responseComponentInfo', function(data) {
  const reqId = data.reqId;
  const callback = requestCb[reqId];
  if (callback) {
    delete requestCb[reqId];
    callback(data.res);
  }
});
export default function requestComponentInfo(webviewId, reqs, callback) {
  const reqId = requestId++;
  if (!webviewId) {
    console.warn('An SelectorQuery call is ignored because no proper page or component is found. Please considering using `SelectorQuery.in` to specify a proper one.');
    return;
  }
  requestCb[reqId] = callback,
  publish('requestComponentInfo',
    {
      reqId,
      reqs,
    }, 
    [webviewId],
  );
}

NodesRef.js

export default class NodesRef {
  constructor(selectorQuery, component, selector, single) {
    this._selectorQuery = selectorQuery;
    this._component = component;
    this._selector = selector;
    this._single = single;
  }
  fields(fields, callback) {
    this._selectorQuery._push(this._selector, this._component, this._single,
      fields,
      callback,
    );
    return this._selectorQuery;
  }
  boundingClientRect(callback) {
    this._selectorQuery._push( this._selector, this._component, this._single,
      {
        id: true,
        dataset: true,
        rect: true,
        size: true,
      },
      callback,
    );
    return this._selectorQuery;
  }
  scrollOffset(callback) {
    this._selectorQuery._push( this._selector, this._component, this._single,
      {
        id: true,
        dataset: true,
        scrollOffset: true,
      },
      callback,
    );
    return this._selectorQuery;
  }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • lottie系列文章(四):源码分析——svg渲染

    lottie为全局变量,主要有一个loadAnimation的方法,来加载和解析json,播放动画。

    IMWeb前端团队
  • iScroll学习小结

    前言 最近项目需要实现一个fixed标题栏的功能,很普通的功能,实现核心也是在sroll事件中切换到fixed状态即可,但是在某些版本ios的某些内核中,在惯性...

    IMWeb前端团队
  • iScroll学习小结

    最近项目需要实现一个fixed标题栏的功能,很普通的功能,实现核心也是在sroll事件中切换到fixed状态即可,但是在某些版本ios的某些内核中,在惯性滚动过...

    IMWeb前端团队
  • springboot @EnableScheduling 启用多线程

    使用@EnableScheduling注解后,可以发现所有任务都排队执行,并且调度器线程名称都是“taskScheduler-1”

    路过君
  • 再也不用被this苦恼了

    前端编程对于this再熟悉不过了,今日来个老调重弹温故知新,肯定有很多大佬已经完全吃透了this原理,敬请出门左拐。对于理解this似懂非懂的同学可以借鉴一波

    Jack Chen
  • Flutter基础widgets教程-Tooltip篇

    青年码农
  • Flutter基础widgets教程-TextField篇

    青年码农
  • 学习 Phaser.js HTML5游戏开发-DAY3

    3. 构建基本的子弹对象,fire 方法用来初始化子弹实例,update方法用来绘制子弹轨迹

    tonglei0429
  • es6中class类的全方面理解(二)------继承

    继承是面向对象中一个比较核心的概念。ES6 class的继承与java的继承大同小异,如果学过java的小伙伴应该很容易理解,都是通过extends关键字继承。...

    用户1272076
  • three.js 郭先生制作太阳系

    今天郭先生收到评论,想要之前制作太阳系的案例,因为找不到了,于是在vue版本又制作一版太阳系,在线案例请点击three.js制作太阳系(加载时间比较长,请稍等一...

    郭先生的博客

扫码关注云+社区

领取腾讯云代金券