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

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

Closed
ksbaozi opened this issue May 28, 2020 · 17 comments
Assignees
Milestone

Comments

@ksbaozi
Copy link

ksbaozi commented May 28, 2020

问题描述

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.

@charmtiger
Copy link

正常情况下, 你可以监听页面的 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')
        })

@charmtiger
Copy link

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

@cncolder
Copy link
Contributor

cncolder commented May 28, 2020

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, 但要考虑去重和防抖.

@charmtiger
Copy link

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

@charmtiger
Copy link

@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毫秒 [{…}]

@charmtiger
Copy link

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

@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

@ksbaozi
Copy link
Author

ksbaozi commented May 28, 2020

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

cncolder commented May 28, 2020

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

@charmtiger
Copy link

charmtiger commented May 28, 2020

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

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

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

--------- 更新

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

@ksbaozi ksbaozi closed this as completed May 29, 2020
@SamChangi
Copy link

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

@charmtiger
Copy link

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

@SamChangi
Copy link

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

fixed, thanks a lot

@yuche yuche reopened this Jun 2, 2020
@yuche
Copy link
Contributor

yuche commented Jun 2, 2020

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

@Carnia
Copy link

Carnia commented Jun 10, 2020

之前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",

试试?

@liuhuapiaoyuan
Copy link

`//查询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)
`

@yuche
Copy link
Contributor

yuche commented Jun 23, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants