Taro: Taro 3.0.0-rc.0 中 componentDidMount 中通过 Taro.createSelectorQuery.select 无法获取指定元素

Created on 28 May 2020  ·  17Comments  ·  Source: NervJS/taro

问题描述

Taro 3.0.0-rc.0 中 自定义的 component 在 componentDidMount 中通过 Taro.createSelectorQuery.select 无法获取指定元素

复现步骤

interface IMarkdownProps {
  className?: string
}

interface IMarkdownState {
  isTooLong: boolean
  isExpanded: boolean
}

class Markdown extends React.Component<IMarkdownProps, IMarkdownState> {
  static defaultProps = {
    type: 'html'
  }

  constructor (props) {
    super(props)
    this.state = {
      isTooLong: false,
      isExpanded: false,
    }
  }

  componentDidMount () {
      this.pageAdjust()
  }

 pageAdjust = () => {
    const query = Taro.createSelectorQuery()
    query.select('#wrapper').boundingClientRect((rect) => {
      console.log('rect', rect)
      if (rect) {
        console.log('rect.height', rect.height)
        this.setState({
            isTooLong: rect.height >= 280,
        });
      }
    }).exec()
  }

  render() {
    const {className} = this.props
    const {isTooLong, isExpanded} = this.state
    return (
      <View className={cn([s.root, isExpanded ? s.expanded : '', className])}>
        <View id='wrapper'>
            aaaaaaaa
        </View>
        {isTooLong && <View className={s.collapseButton} onClick={this.toggleExpanded}>
          {isExpanded ? '收起描述' : '展开描述'}
        </View>}
      </View>
    )
  }
}


期望行为

componentDidMount 中可获取到元素

报错信息

系统信息

Taro v3.0.0-rc.0

Taro CLI 3.0.0-rc.0 environment info:
System:
OS: macOS 10.15.5
Shell: 5.7.1 - /bin/zsh
Binaries:
Node: 12.16.3 - /usr/local/bin/node
Yarn: 1.16.0 - /usr/local/bin/yarn
npm: 6.14.4 - /usr/local/bin/npm
npmPackages:
@tarojs/components: 3.0.0-rc.0 => 3.0.0-rc.0
@tarojs/taro: 3.0.0-rc.0 => 3.0.0-rc.0
@tarojs/webpack-runner: 3.0.0-rc.0 => 3.0.0-rc.0
eslint-config-taro: 3.0.0-rc.0 => 3.0.0-rc.0
nervjs: ^1.5.6 => 1.5.6
react: ^16.10.0 => 16.13.1
taro-ui: 3.0.0-alpha.1 => 3.0.0-alpha.

Most helpful comment

在 #6776 之后,nextTick 函数一定会在 onReady 之后运行

All 17 comments

正常情况下, 你可以监听页面的 onReady 事件,在元素渲染后再查询。

但是,@yuche rc2 版本 并没有执行。

Taro.eventCenter.once(Taro.Current.router.onReady, () => {
          const query = Taro.createSelectorQuery()
          query.select(this.viewId).boundingClientRect()
          query.exec(res => {
            console.log(res, 'res')
          })
          console.log('onReady')
        })

@Chen-jj rc2 版本 Taro.eventCenter.once(Taro.Current.router.onReady, () => {}) 不会执行,请抽空看一下。

componentDidMount 代表 React 组件组装完毕交给 Taro 处理, Taro 刷新到 UI 是有延迟的, 就像 createSelectorQuery 一样的延迟. 所以 componentDidMount 时节点还不存在.

解决办法有3种:

  1. Taro 3.0 页面应该有个 onReady 生命周期.
  2. 如果使用 hooks 可以试试 useLayoutEffect.
  3. 试试 <View id='wrapper' ref={node => { Taro.createSelectorQuery().select(`#${node?.id}`) }}>

如果 1 和 2 都不适合, 可以试试 3, 但要考虑去重和防抖.

@cncolder 刚刚使用 3 试了一下,ref 只能拿到组件,并不意味着页面组件dom 已经渲染完成。

@cncolder 试了一下 ,还是不行

receiveRef = node => {
      if (!node) return;
      this.viewRef = node
      const query = Taro.createSelectorQuery()
            query.select('#' + this.viewId).boundingClientRect()
            query.exec(res => {
              console.log('未延迟', res)
            })
      setTimeout(() => {
        const query = Taro.createSelectorQuery()
        query.select('#' + this.viewId).boundingClientRect()
        query.exec(res => {
          console.log('延时500毫秒', res)
        })
        console.log('onReady')
      }, 500);
    }

未延迟 [null]
延时500毫秒 [{…}]

@yuche 我是在3 4层深的子组件中,只能使用 eventCenter 来使用 onReady ,参考的 #6221

发现个很奇怪的问题
componentDidMount 中直接执行下面这段 就可以 ok

Taro.eventCenter.once(Taro.Current.router?.onReady, () => {
    console.log('[ok]')
 })

但是如果加了判断,就不会执行ok,更离奇的是 除了 onLayout 别的props字段都没事,onLayout 是 function 类型

if (this.props.onLayout) {
  Taro.eventCenter.once(Taro.Current.router?.onReady, () => {
    console.log('[ok]')
   })
}

@cncolder 真的不行,您看看 我试了 beta6 rc0 rc2 都不行

<View 
          ref={node => node && Taro.createSelectorQuery()
              .select(`#${node.id}`)
              .boundingClientRect()
              .exec(console.log)
          }
          onClick={onClick}
          className={className} style={style} {...other}
        >
          {children}
        </View>

image

@yuche 我是在3 4层深的子组件中,只能使用 eventCenter 来使用 onReady ,参考的 #6221

发现个很奇怪的问题
componentDidMount 中直接执行下面这段 就可以 ok

Taro.eventCenter.once(Taro.Current.router?.onReady, () => {
    console.log('[ok]')
 })

但是如果加了判断,就不会执行ok,更离奇的是 除了 onLayout 别的props字段都没事,onLayout 是 function 类型

if (this.props.onLayout) {
  Taro.eventCenter.once(Taro.Current.router?.onReady, () => {
    console.log('[ok]')
   })
}

看起来你的方法能解决我的问题

@charmtiger 你的 View 没有 id, onLayout 是你的业务代码, 解决问题之前要排除业务代码的干扰.
这里不是论坛.

@yuche 我已经暂时得出结论,当组件是根据接口参数,来判断是否实例化,也就是说当页面 onReady 的时候,这个组件并没有存在于组件树中,等接口返回后,再实例化组件,在组件的 componentDidMount 中直接调用 query 和使用 eventCenter 都无法查找到元素和执行。另外我对 cncolder 的 ref 的方式,在上述的情况中,同样无法通过多次执行 ref 来拿到元素,只会执行一次。

我认为目前 eventCenter 的方式,只是利用小程序页面的 onReady,对于后来出现的dom是不适用的。理想情况是每次渲染过后,都会有一次类似 Vue.nextTick 的回调,不过感觉不容易实现。

我知道这里不是论坛,我也在大量的尝试各种可能,尽可能确认问题,如果啰嗦了很抱歉🤗。

--------- 更新

刚刚发现 Taro 提供了 Taro.nextTick,使用后问题解决,另外请问 Taro.nextTick 能否代替 eventCenter 的方式?

Taro 2.1.5, 本地调试正常可以通过createSelectorQuery准确获取到指定内置组件的高度,并成功渲染;但真机调试获取到的组件高度就不准确了。any suggestions? @charmtiger

@SamChangi 我看 taroui 也是用 延时 来 query的,我也是改用 如果查不到元素,就延时 反复查。

@SamChangi 我看 taroui 也是用 延时 来 query的,我也是改用 如果查不到元素,就延时 反复查。

fixed, thanks a lot

这个先打开,后续我们再找有没有更好的方案

之前taro2版本组件内是要加 .in(this.$scope) 的,后来发现taro3去掉 .in(this.$scope) 就好了

const query = Taro.createSelectorQuery().in(this.$scope)

改为

const query = Taro.createSelectorQuery()

(实测环境):"@tarojs/taro": "3.0.0-rc.3",

试试?

`//查询dom的宽度和高度,注意这里获得的是一个px单位
async function getDOMRect(id){
const query = Taro.createSelectorQuery();
query.select('#'+id).boundingClientRect()
return new Promise((reslove,reject)=>{
query.exec(res=>{
if(res[0]==null){return reject('Cannot find the #'+id+' selector')}
reslove({
width:res[0].width,
height:res[0].height,
})
})
})
}

//不断查询直到有结果
function poll(promiseFunc){
return promiseFunc().catch(()=>new Promise(reslove=>Taro.nextTick(()=>reslove(poll(promiseFunc)))))
} 调用的时候:
poll(‘id’).then(console.log)
`

在 #6776 之后,nextTick 函数一定会在 onReady 之后运行

Was this page helpful?
0 / 5 - 0 ratings