NervJS / taro

开放式跨端跨框架解决方案,支持使用 React/Vue/Nerv 等框架来开发微信/京东/百度/支付宝/字节跳动/ QQ 小程序/H5/React Native 等应用。 https://taro.zone/
https://docs.taro.zone/
Other
35.59k stars 4.8k forks source link

插件中使用$('#xxx') 可以获取到元素,但是调用.height()会报错 #11966

Closed leifeng closed 2 years ago

leifeng commented 2 years ago

相关平台

微信小程序

复现仓库

示例代码

小程序基础库: 2.24.6 使用框架: React

复现步骤

在插件的自定义组件中

function A(){
 const id="abc"
  useEffect(() => {
    const node = $('#' + id);
    connsole.log(node ) //这里有元素
    node.height().then((res) => {
      console.log(res);
    });
  }, []);
 return <View>
 <View id={id}>
<Text>一堆文字</Text>
</View>
</View>
}

期望结果

能获取元素高度

实际结果

offset error: #abc query fail

环境信息

  Taro CLI 3.4.10 environment info:
    System:
      OS: Windows 10
    Binaries:
      Node: 14.18.2 - d:\Program Files\nodejs\node.EXE
      Yarn: 1.22.17 - d:\Program Files\nodejs\yarn.CMD
      npm: 6.14.15 - d:\Program Files\nodejs\npm.CMD
Barrierml commented 2 years ago

这个是由于useEffect的触发时机导致的,因为小程序中的tarouseEffect与h5中的行为稍微有点不一样 触发useEffect时小程序节点其实并没有挂载完成,所以导致获取不到对应的元素,自然而然也拿不到元素的属性。 推荐使用useReady代替useEffect,等待元素真正装载完毕后就能获取到其属性啦

function A() {
  const id = "abc"
  useReady(() => {
    const node = $('#' + id);
    node.height().then((res) => {
      console.log('useReady',res);
    });
  });
  return <View>
    <View id={id}>
      <Text>一堆文字</Text>
    </View>
  </View>
}
leifeng commented 2 years ago

@Barrierml 不行,我传了示例代码,帮忙给看看

Barrierml commented 2 years ago

@Barrierml 不行,我传了示例代码,帮忙给看看

hello呀,很抱歉之前没有注意到你issue里写的是在插件内,我根据你的示例仓库又查看了一下,发现这其实是一个小程序本身的问题

说明

@tarojs/extend 提供的 $ 在内部调用的是 wx.createSelectorQuery().select('some id').boundingClientRect 来获取元素的属性

在2.0时期,也有人提过类似问题了可以查看这里

而在小程序的插件内部使用wx.createSelectorQuery() 创建一个查询器直接去查询时,是会查询不到当前插件页面内的元素的,这其实与taro本身无关,是在插件内必须使用.in来手动圈定查询域。

解决方案

因为我发现你是在子组件内使用的,大概不能用社区推荐的解决方案 通过 this.props.$scope 手动圈定范围 因为这个参数只会在页面组件内传入。 在任何子组件内都可以使用的解决方案

// 获取当前页面实例
const pageInstance = getCurrentPages()[getCurrentPages().length - 1];
// 获取元素尺寸相关信息
Taro
    .createSelectorQuery()
    .in(pageInstance)
    .select('#' + id)
    .boundingClientRect((res) => { console.log('onReady', res) })
    .exec();

请注意这些代码一定要在 useReady 内使用哦 感觉@tarojs/extend 如果在插件环境下也应该手动指向一下,周末我试着提个PR😊

随笔

接下来要说的可能和本次issue无关,写出来单纯的因为我在查看这个问题的过程中碰到的有趣(弯路)事

我开始查这个问题的思路大概是这样的 |检查在小程序内的行为。 => 👌 能获取到 |检查组件生命周期 => 👌 完全正常 |尝试控制台手动获取实例 => 👌能获取到 到这里我就开始怀疑是不是wx环境的问题了,检查了一下,果然插件内的wx实例与小程序内部的wx实例是相隔离开来的

image

于是我的思路从taro本身放在了小程序上,经过查看wx.createSelectorQuery的行为与代码分析 最终发现了一段代码

_selectorQuery._push = function (t, n, o, r, a) {
    // 如果没有用 in 手动圈选过范围的话就用默认的组件,也就是当前页面
    null === this._webviewId && (this._webviewId = this._defaultComponent ? this._defaultComponent.__wxWebviewId__ : void 0);
    // 这里的e变量,如果存在,则 i 为空,所以在查找时就没有可查元素
    var i = e ? "" : __virtualDOM__.getRootNodeId(this._webviewId);
    // 任务队列
    this._queue.push({
        // 押入栈的component就是要查找的根元素id
        component: null != n ? 0 === n ? 0 : n.__wxExparserNodeId__ : i,
        selector: t,
        single: o,
        fields: r
    }), this._queueCb.push(a || null)
}

这段是在2.2.0基础库内调取wx.createSelectorQuery().select('#some')._selectorQuery._push函数的代码,而这个方法恰恰是调用boundingClientRect时会执行的函数

boundingClientRect =  function (e) {
    // 利用 _selectorQuery._push 将查询压入栈,后续再用exce执行
    return this._selectorQuery._push(this._selector, this._component, this._single, {
        id: !0,
        dataset: !0,
        rect: !0,
        size: !0
    }, e), this._selectorQuery
}

接下来就比较清晰了,看一下这个e这个变量是什么值就可以了,打开函数闭包查看下,就会发现其实就是把当前插件的providerId传入了进来,而当我在微信开发者控制台和主程序内调用时,这个e变量就会是空了。 image

结果

查到这里我才意识到,这小程序压根不想让你在插件内能直接查元素啊,必须要手动去指定查找范围。

不过想一下插件所提供的内容大概也就明白了,因为会侵入其他小程序,所以必须要手动进行环境隔离,防止一些意外

然后我就去google了一下,转了一圈又回到了社区看到了解决方案,或许我最开始查到插件内无法使用createSelectorQuery的时候,直接去google一下就不会有这么多事情了,笑

Chen-jj commented 2 years ago

感谢 @Barrierml 的热心回复!

首先,$(id) 获取到的是 Taro 虚拟 DOM。要获取元素宽高必须使用小程序的 createSelectorQuery API。

再者,的确插件可以看作是一个小程序自定义组件,使用 createSelectorQuery().select().boundingClientRect().in(scope).exec() 时需要调用 .in() 并传入插件对象自身。

Taro 把 scope 对象作为插件页面的 props 传入给开发者,开发者在插件页面使用 this.props.$scope 则可获取,也可以传递给子组件使用,文档位置:

https://docs.taro.zone/docs/miniprogram-plugin#%E6%8F%92%E4%BB%B6%E9%A1%B5%E9%9D%A2%E8%8E%B7%E5%8F%96%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%B8%B2%E6%9F%93%E5%B1%82%E5%85%83%E7%B4%A0