Skip to main content
Version: 3.x

节点获取

在 Web 开发中,常用的节点获取方法包括了 document.getElementById 等 DOM API,以及各开发框架的 ref 语法。而在 Taro 中,一般情况下建议使用 ref 语法来获取节点。但受限于小程序平台的实现机制,如果需要获取节点的尺寸、定位等与渲染有关的信息,开发者需要使用 Taro.createSelectorQuery API 来获取节点。

ref 语法

Taro 支持使用 React / Vue 等开发框架的 ref 语法来获取节点。开发者可以对这些节点进行一些操作,例如:调用节点的方法、设置属性等,但在小程序平台中不能获取到节点的尺寸信息。

info

不同平台 ref 获取到的节点类型不同,但 Taro 尽量保证了这些节点具有统一的属性与 API。

  • H5 Ref 类型:Taro 组件实例,一般是 WebComponents。
  • 小程序 Ref 类型:Taro 虚拟 DOM 节点,如 TaroElement。存在于逻辑层,因此不携带节点尺寸信息。
  • RN Ref 类型:Taro 组件实例。
示例代码
import { useRef } from 'react'
import { Input, Button } from '@tarojs/components'

export default function Index () {
const inputRef = useRef<HTMLInputElement>(null)

function handleClick() {
inputRef.current?.focus()
}

return (
<>
<Input ref={inputRef} />
<Button onClick={handleClick}>Focus the input</Button>
</>
)
}

获取节点尺寸信息

使用 Taro.createSelectorQuery API 可以获取到节点的尺寸、定位等与渲染有关的信息。考虑到小程序的实现机制,需要配合在 onReady 生命周期中获取节点信息。

tip

各个小程序的渲染机制不同,导致了小程序生命周期和 Taro React / Vue 生命周期的触发顺序也不尽相同。如果存在获取节点失败的情况,请尝试使用 Taro.nextTicksetTimeout 等方法增加延时。

示例代码
import { View } from '@tarojs/components'
import Taro, { useReady } from '@tarojs/taro'

export default function Index () {
useReady(() => {
// 初次渲染时,在小程序触发 onReady 后,才能获取小程序的渲染层节点
Taro.createSelectorQuery()
.select('#target')
.boundingClientRect()
.exec(res => console.log(res))
})

return (
<View id="target"></View>
)
}

在子组件中获取

onReady 是页面级别的生命周期,如果需要在子组件中调用 Taro.createSelectorQuery,建议使用 useReady 钩子。

若开发者没有使用 React Hook 或 Vue3 Composition API,则需要基于 Taro 的事件通讯机制,监听页面 onReady 的调用,请参考:ReactVue

示例代码
import { View } from '@tarojs/components'
import Taro, { useReady } from '@tarojs/taro'

function Comp () {
useReady(() => {
// 初次渲染时,在小程序触发 onReady 后,才能获取小程序的渲染层节点
Taro.createSelectorQuery()
.select('#target')
.boundingClientRect()
.exec((res) => console.log(res))
})

return (
<View id="target"></View>
)
}

export default function Index () {
return (
<Comp></Comp>
)
}

在懒加载组件中获取

另一个常见的场景则是使用了懒加载的组件。当它们挂载时,页面的 onReady 早已触发,这时需要配合使用 Taro.nextTick API 才能成功获取到节点信息。

示例代码
import { View, Button, Canvas } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useState, useEffect } from 'react'

export default function Index () {
const [isShow, setIsShow] = useState(false)

return (
<View>
<Button onClick={() => setIsShow(true)}>Load Component</Button>
{isShow && <LazyFloor></LazyFloor>}
</View>
)
}

function LazyFloor () {
useEffect(() => {
Taro.nextTick(() => {
Taro.createSelectorQuery()
.select(`.ec-canvas`)
.fields({ node: true, size: true })
.exec(res => console.log('res: ', res))
})
}, [])
return (
<View>
<Canvas canvasId='canvas' className='ec-canvas'></Canvas>
</View>
)
}

在更新渲染时获取

上述讨论的都是初次渲染的情况,如果需要在更新渲染时获取节点信息,可以在 componentDidUpdateuseEffect(React)或 onUpdated(Vue3)等更新渲染完成钩子中配合 nextTick 获取。用法和懒加载组件类似。

使用了 CustomWrapper 时

tip

如果你的项目只需要运行在小程序端,也可以使用 >>> 选择器来解决:#7411

在小程序平台,每个 CustomWrapper 实例对应一个原生自定义组件。小程序规定,如果要获取自定义组件内的节点,必须调用 .in 方法,其中 scope 是对应的自定义组件实例:Taro.createSelectorQuery().in(scope)

对应在 Taro 中,开发者可以先使用 ref 获取 CustomWrapper 实例,然后通过其 ctx 属性获取到 scope,例子:

示例代码
import { View, Canvas, CustomWrapper } from '@tarojs/components'
import Taro, { useReady } from '@tarojs/taro'
import { createRef } from 'react'

export default function Index () {
const wrapperRef = createRef()

useReady(() => {
Taro.createSelectorQuery()
.in(wrapperRef.current.ctx)
.select(`.ec-canvas`)
.fields({ node: true, size: true })
.exec(res => console.log('res: ', res))
})

return (
<View>
<CustomWrapper ref={wrapperRef}>
<Canvas canvasId='canvas' className='ec-canvas'></Canvas>
</CustomWrapper>
</View>
)
}

嵌套层级超过 baseLevel 时

tip

如果你的项目只需要运行在小程序端,也可以使用 >>> 选择器来解决:#7411

在微信以及京东小程序平台,当组件的嵌套层级超过 baseLevel(默认 16 层)时,Taro 内部会创建一个原生自定义组件协助开启更深层次的嵌套,因此获取超过 baseLevel 层级的节点时会失败。这时我们需要借助 CustomWrapper 来解决这个问题:

示例代码
import { View, Canvas, CustomWrapper } from '@tarojs/components'
import Taro, { useReady } from '@tarojs/taro'
import { createRef } from 'react'

export default function Index () {
const wrapperRef = createRef()

useReady(() => {
Taro.createSelectorQuery()
.in(wrapperRef.current.ctx)
.select(`.ec-canvas`)
.fields({ node: true, size: true })
.exec(res => console.log('res: ', res))
})

return (
<View><View><View><View>
<View><View><View><View>
<View><View><View><View>
<CustomWrapper ref={wrapperRef}>
<View><View><View><View>
<Canvas canvasId='canvas' className='ec-canvas'></Canvas>
</View></View></View></View>
</CustomWrapper>
</View></View></View></View>
</View></View></View></View>
</View></View></View></View>
)
}