📌 适用场景:uni-app Vue3 项目、涉及 uni.createSelectorQuery、使用 nvue优化长列表

🛠 环境:HBuilderX 4.36 / uni-app Vue3 (Vite) / Android 14

💥 痛点:升级 Vue3 后,$refs调用 scroll-view等原生组件方法无报错、无响应,官方文档未覆盖。


一、现象:Vue2 正常,Vue3 却“哑火”了

Vue2 写法(正常工作):


  

  
export default {
  methods: {
    scrollToBottom() {
      uni.createSelectorQuery().in(this)
        .select('.scroll-view')
        .boundingClientRect(data => {
          // 直接调用原生方法
          this.$refs.scrollView.scrollTo({
            top: data.height,
            duration: 300
          });
        }).exec();
    }
  }
}

Vue3 <script setup>写法(静默失败):


  

  
<template>
  <scroll-view ref="scrollView" class="scroll-view" />
</template>

<script setup lang="ts">
import { ref } from 'vue';
const scrollView = ref(null);

const scrollToBottom = () => {
  // 打印结果:Proxy {__v_isReadonly: true, __v_isShallow: false, ...}
  console.log(scrollView.value); 
  
  // 无任何报错,但页面不动
  scrollView.value.scrollTo({ top: 1000 }); 
};
</script>

报错日志(仅 Log 可见):


  

  
[JS Framework] Failed to invoke method scrollTo: target is not a valid native component descriptor

二、根因:Vue3 的 Proxy 阻断了原生层调用

uni-app 的 App/Nvue 渲染层是原生 C++ 实现,只能识别原始对象。

Vue3 的 ref()会用 Proxy包裹对象以实现响应式追踪。

结果:原生层拿到了 Proxy对象,无法识别其中的方法指针,导致静默失败。

渲染层

Vue2 行为

Vue3 行为

结果

App/Nvue

返回原生描述符

返回 Proxy 代理对象

调用失效

H5

返回 DOM 节点

返回 Proxy 代理节点

通常正常(浏览器兼容性好)


三、解决方案:穿透 Proxy(二选一)

方案 1:shallowRef(推荐,性能最优)

仅追踪 .value的替换,不代理内部属性,最适合存储组件实例。


  

  
<template>
  <scroll-view ref="scrollView" class="scroll-view" />
</template>

<script setup lang="ts">
import { shallowRef } from 'vue';

// 关键:使用 shallowRef
const scrollView = shallowRef<any>(null);

const scrollToBottom = () => {
  // 此时 value 即为原生实例
  scrollView.value?.scrollTo({ top: 1000, duration: 300 });
};
</script>
方案 2:markRaw(语义明确)

标记对象永不转为响应式。


  

  
<script setup lang="ts">
import { ref, markRaw, onMounted } from 'vue';

const scrollView = ref<any>(null);

onMounted(() => {
  const el = document.querySelector('.scroll-view'); // 伪代码,实际用 uni API 获取
  // 关键:赋值前标记为 raw
  scrollView.value = markRaw(el); 
});
</script>

四、进阶:Nvue 下的渲染时序陷阱

nvue页面中,Vue 的 mounted钩子不等于原生渲染完成。

现象uni.createSelectorQuery返回 null

解决方案nextTick+ setTimeout双重保障。


  

  
<script setup lang="ts">
import { nextTick } from 'vue';

const queryDom = async () => {
  await nextTick(); // 等待 Vue 更新
  
  // 延时确保原生层绘制完成(经验值 50ms)
  setTimeout(() => {
    uni.createSelectorQuery().select('#myEl')
      .boundingClientRect(rect => {
        console.log('真实尺寸:', rect); // 不再为 null
      }).exec();
  }, 50);
};
</script>

五、总结与 CheckList

  1. App/小程序端:凡是用 ref存储原生组件实例,一律改用 shallowRef

  2. Nvue 页面:所有 DOM 查询操作必须包裹在 setTimeout中(建议 50ms)。

  3. 严禁对原生组件实例使用 watchcomputed,原生属性不支持响应式。

适用边界

  • ✅ 适合:uni-app Vue3、App 端开发、Nvue 性能优化。

  • ❌ 不适合:纯 H5 简单页面、Vue2 老项目。


💬 讨论

你在 uni-app Vue3 迁移中还遇到过哪些“官方未写”的坑?欢迎评论区交流。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐