Skip to content

Latest commit

 

History

History
532 lines (421 loc) · 12.7 KB

ref.mdx

File metadata and controls

532 lines (421 loc) · 12.7 KB
title
节点获取

import { ReactIcon, VueIcon } from '@site/static/icons' import TabItem from '@theme/TabItem' import Tabs from '@theme/Tabs'

在 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 组件实例。 :::

<Tabs defaultValue="react" values={[ {label: , value: 'react'}, {label: , value: 'vue2'}, {label: , value: 'vue3'} ]}

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>
    </>
  )
}
<template>
  <view>
    <input ref="inputRef" />
    <button @tap="handleClick">Focus the input</button>
  </view>
</template>

<script>
  export default {
    methods: {
      handleClick () {
        this.$refs.inputRef.focus()
      }
    }
  }
</script>
<template>
  <input ref="inputRef" />
  <button @tap="handleClick">Focus the input</button>
</template>

<script setup>
  import { ref } from 'vue'

  const inputRef = ref(null)

  function handleClick () {
    inputRef.value.focus()
  }
</script>

获取节点尺寸信息

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

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

<Tabs defaultValue="react" values={[ {label: , value: 'react'}, {label: , value: 'vue2'}, {label: , value: 'vue3'} ]}

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>
  )
}
<template>
  <view id="target" />
</template>

<script>
  import Taro from '@tarojs/taro'

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

<script setup>
  import Taro, { useReady } from '@tarojs/taro'

  useReady(() => {
    // 初次渲染时,在小程序触发 onReady 后,才能获取小程序的渲染层节点
    // Vue3 需要使用 Taro.nextTick 确保已经渲染完成
    Taro.nextTick(() => {
      Taro.createSelectorQuery()
        .select('#target')
        .boundingClientRect()
        .exec(res => console.log(res))
    })
  })
</script>

在子组件中获取

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

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

<Tabs defaultValue="react" values={[ {label: , value: 'react'}, {label: , value: 'vue3'} ]}

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>
  )
}
<!-- page -->
<template>
  <comp></comp>
</template>

<script setup>
  import Comp from './comp'
</script>

<!-- component -->
<template>
  <view id="target" />
</template>

<script setup>
  import Taro, { useReady } from '@tarojs/taro'

  useReady(() => {
    Taro.nextTick(() => {
      Taro.createSelectorQuery()
        .select('#target')
        .boundingClientRect()
        .exec(res => console.log(res))
    })
  })
</script>

在懒加载组件中获取

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

<Tabs defaultValue="react" values={[ {label: , value: 'react'}, {label: , value: 'vue3'} ]}

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>
  )
}
<!-- page -->
<template>
  <view>
    <button @tap="isShow = true">Load Component</button>
    <LazyFloor v-if="isShow"></LazyFloor>
  </view>
</template>

<script setup>
  import { ref } from 'vue'
  import LazyFloor from './lazyFloor'

  const isShow = ref(false)
</script>

<!-- component -->
<template>
  <view>
    <canvas canvas-id='canvas' class='ec-canvas'></canvas>
  </view>
</template>

<script setup>
  import Taro from '@tarojs/taro'
  import { onMounted } from 'vue'

  onMounted(() => {
    Taro.nextTick(() => {
      Taro.createSelectorQuery()
        .select(`.ec-canvas`)
        .fields({ node: true, size: true })
        .exec(res => console.log('res: ', res))
    })
  })
</script>

在更新渲染时获取

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

使用了 CustomWrapper 时

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

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

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

<Tabs defaultValue="react" values={[ {label: , value: 'react'}, {label: , value: 'vue3'} ]}

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>
  )
}
<template>
  <view>
    <custom-wrapper ref="wrapperRef">
      <canvas canvas-id='canvas' class='ec-canvas'></canvas>
    </custom-wrapper>
  </view>
</template>

<script setup>
  import Taro, { useReady } from '@tarojs/taro'
  import { ref } from 'vue'

  const wrapperRef = ref()

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

嵌套层级超过 baseLevel 时

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

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

<Tabs defaultValue="react" values={[ {label: , value: 'react'}, {label: , value: 'vue3'} ]}

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>
  )
}
<template>
  <view><view><view><view>
    <view><view><view><view>
      <view><view><view><view>
        <custom-wrapper ref="wrapperRef">
        <view><view><view><view>
          <canvas canvas-id='canvas' class='ec-canvas'></canvas>
        </view></view></view></view>
        </custom-wrapper>
      </view></view></view></view>
    </view></view></view></view>
  </view></view></view></view>
</template>

<script setup>
  import Taro, { useReady } from '@tarojs/taro'
  import { ref } from 'vue'

  const wrapperRef = ref()

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