Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
leifeng opened this issue Jun 21, 2022 · 4 comments
Closed
Labels
F-react Framework - React T-weapp Target - 编译到微信小程序 V-3 Version - 3.x

Comments

@leifeng
Copy link

leifeng commented Jun 21, 2022

相关平台

微信小程序

复现仓库

示例代码

小程序基础库: 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
@taro-bot2 taro-bot2 bot added F-react Framework - React T-weapp Target - 编译到微信小程序 V-3 Version - 3.x labels Jun 21, 2022
@Barrierml
Copy link
Contributor

Barrierml commented Jun 22, 2022

这个是由于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
Copy link
Author

leifeng commented Jun 24, 2022

@Barrierml
Copy link
Contributor

Barrierml commented Jun 24, 2022

@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
Copy link
Contributor

Chen-jj commented Jun 29, 2022

感谢 @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

@Chen-jj Chen-jj closed this as completed Jun 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
F-react Framework - React T-weapp Target - 编译到微信小程序 V-3 Version - 3.x
Projects
None yet
Development

No branches or pull requests

3 participants