页面白屏 + Invalid prop: type check failed for prop "options"?原来是 script setup 的 ref

页面打开一片空白,控制台只有一行很克制的警告:Invalid prop: type check failed for prop "options". Expected Array, got Object。你检查了数据明明是个数组,为什么模板里就变成了 Object?这篇文章从一次真实的 Vue 3 排障出发,讲清楚 script setup 里 ref 自动解包到底解到了哪一层。

一、现象:明明返回数组,模板却提示 Object

业务里常见的一种组织方式是"Hook 组合式函数":把一堆逻辑(查询、分页、筛选状态)收进 useSomething(),页面里解构出来直接用。

某个页面结构大致是这样------一个外层 view 引用了组合函数和子组件:

vue 复制代码
<script setup>
const { baseOptions, tableData, loading } = useSomething();
</script>

<template>
  <MySelect :options="baseOptions" />
  <MyTable :data-source="tableData" loading="loading" />
</template>

子组件里按常规方式声明了 prop 类型:

vue 复制代码
<script setup>
defineProps({
  options: { type: Array, required: true },
  dataSource: { type: Array, default: () => [] },
});
</script>

结果一运行,页面白屏,控制台冒出:

typescript 复制代码
Invalid prop: type check failed for prop "options". Expected Array, got Object

诡异的是,你在 Hook 里把数据 log 出来,它明明就是 [] 或一个真实数组。

二、根因:嵌套对象里的 ref 不会被自动解包

问题出在 Vue 3 的 ref 解包机制 在 script setup 中的边界上。

Vue 3 有个便利特性:在模板里,顶层 ref 会自动解包 ------你写 {{ count }},哪怕 count 是个 ref(0),渲染的也是 0 这个值,不用写 count.value。这是很多人爱 script setup 的原因之一。

但请注意关键字:顶层(top-level) 。只有 script setup 顶层的绑定、且是解构出来的顶层变量,才会在模板里被自动解包。

如果组合函数返回的是嵌套对象,情况就完全不同了:

js 复制代码
export function useSomething() {
  const baseOptions = ref([]);
  const tableData = ref([]);
  return { baseOptions, tableData }; // ref 被包在对象里
}

页面里这样解构:

js 复制代码
const result = useSomething();
// 模板里用 result.baseOptions

此时 result.baseOptions 是一个 ref 对象,而不是数组。模板中 ref 的解包只发生在"顶层绑定"上------

  • const { baseOptions } = useSomething() 解构到顶层 → template 自动解包,正常;
  • const result = useSomething() 用 result.baseOptions → 不自动解包,你拿到的是 ref 对象。

上面那个报错的例子,很可能是把 Hook 的返回值整体接管后,把 ref 包装对象直接丢给了子组件 prop:

js 复制代码
const state = useSomething(); // 嵌套对象里全是 ref

于是 :options="state.baseOptions" 传过去的实际上是个 Ref 对象,而 prop 声明是 Array,类型校验自然炸了------数据确实有,但它不是你以为的形状 。页面空白,是因为模板里拿着 ref 对象去当数组用(length、迭代都取不到正确值),渲染不出来。

三、修复:顶层解构 + 正确的解包位置

修复方式很直接,就是让 ref 落到能被自动解包的位置。

方案 A:顶层解构(最推荐)

把组合函数返回值里你需要的 ref,在 script setup 顶层直接解构出来:

vue 复制代码
<script setup>
// 顶层解构 → 模板自动解包
const { baseOptions, tableData, loading } = useSomething();
</script>

<template>
  <MySelect :options="baseOptions" />
  <MyTable :data-source="tableData" :loading="loading" />
</template>

这样 baseOptions 就是被解包后的数组,prop 类型校验通过,页面恢复显示。

关于"为什么顶层解构的解包能生效"要理解一点:Vue 编译 script setup 的模板时,baseOptions 已成为顶层绑定,模板访问它会走 unref(),把 ref 解开。而 state.baseOptions 这种"属性访问"不会被编译器特殊处理。

方案 B:Hook 内部若必须整体返回对象,用 toRefs 摊平

如果你确实想整体接管返回值(比如在一个函数里统一维护状态),可以这样处理,让每个字段仍是可解包的 ref:

js 复制代码
import { ref, toRefs } from 'vue';

export function useSomething() {
  const stateRef = reactive({ baseOptions: [], tableData: [] });
  return toRefs(stateRef); // 每个都是 ref
}

但在模板里直接 result.baseOptions 依然不会自动解包------除非再解构一层。所以最省心的还是方案 A:在页面 script setup 顶层解构需要传给子组件 prop 的 ref。传 prop 前,让数据要么已是原始值,要么已是可解包的顶层绑定。

四、另一个相关乌龙:Prop 类型自己写翻了

这次排障还带出一个容易和上面混淆的小坑:type: Array 写成了 Array,但实际传的本来就是数组,却仍报错。

常见原因不是解包,而是类型字符串写错 或默认值/工厂函数写错:

js 复制代码
defineProps({
  options: { type: Array, default: [] }, // ❌ 引用类型默认值必须是工厂函数
});

defineProps({
  options: { type: Array, default: () => [] }, // ✅ 正确
});

Vue 会警告"Props with type Object/Array must use a factory function to return the default value"。虽然报错文案不同,但同样会造成页面空白或渲染异常,排查时别漏掉这个更基础的点。

五、方法论:报错前先确认"数据的真实形状"

回看这次排障,最有价值的不是那行修复,而是定位思路:先确认你手里那个值,到底是什么。

  • 控制台 log 一下,看 typeof 和 Array.isArray------它到底是数组还是 ref 包装对象?
  • 看它来自哪里:是顶层解构出来的,还是从嵌套对象属性里取出来的?
  • 如果是嵌套对象,模板里它不会被自动解包,prop 拿到的就是一个 ref 对象。

把"引用怎么传的"和"Vue 在哪一层帮你解包"对上号,这类 Invalid prop 白屏问题通常五分钟内就能定位。

结语

script setup 的 ref 自动解包很爽,但它有明确的边界------只有顶层绑定才会被模板自动解包 。把 ref 塞进嵌套对象再整体传给子组件,就绕开了这个便捷机制,换来一串 Invalid prop 和一次白屏。

记住这句话:传 prop 之前,让数据是"被解包后的原始值"。你就能和大多数诡异的白屏说再见。

你在 Vue 3 里有没有被 ref 解包坑过?欢迎聊聊你遇到过的"Expected X, got Y"查因经历。

相关推荐
deli0071 小时前
GROUP BY 先别想当然:我把 SQL 分组语义做成了沙盘,8 个实验 + 27 条自检全绿
前端
WayneX1 小时前
开源 Vue 3 组件库 Morya UI:把组件、文档、AI 工具链一起做进一个包
前端·vue.js·前端框架
btcSteven1 小时前
给浏览器装了个「AI 操作员」:纯聊天帮你完成任何任务
前端
高晶1 小时前
一种小功率锂电池组充电器方案
前端·架构
deli0071 小时前
AI 说写完了怎么知道它没骗你?16 项交付证据清单,我用码道做成一键核对页
前端
zReadonly1 小时前
不用反复 nvm use 了:nvm-windows 2.x 按项目自动切换 Node.js
前端·node.js
汉堡大王95271 小时前
GPT-6 上线 48 小时,我扒开了 Intelligent UI 的运行机制:DIL、沙箱 Worker 和一个 React 式协调器
前端·javascript·后端
hai_android1 小时前
Chat 聊天模块功能总结
前端·javascript·vue.js
子非鱼a1 小时前
【WEB】EasySSTI
java·开发语言·前端