跨文件规则
本页集中列出所有相关规则的用途、配置、错误示例和正确示例,无需跳转页面查看对照。高亮行标示修改;复制代码时保留完整源代码。每条规则注明适用范围和当前支持限制;规范示例并不保证当前实现会产生诊断。
本页完整展示 66 项项目检查的用途、共享项目文件,以及错误和正确示例的全部修改。两组示例都需使用各项提供的共享文件,并遵循依赖及版本说明。项目检查需要完整的已分析组件图。60 个公开跨文件诊断代码具有不同支持边界:19 个属于 CLI 分析遍(18 组已验证的源代码对照,以及一项附响应式流图的说明性 Vue 项目);16 个有实验性 Rust 分析器实现,但该 CLI 分析遍不会单独发出这些代码;25 个仅是目前没有诊断实现的公开约定。说明性源代码和保留图的验证条件分别注明;启用规则 ID 不会激活尚不可用的实现。
公共 CLI 可通过 vize lint --cross-file 执行同一检查。显示的 vize:croquis/cf/* 代码在 vue.linter.rules 中写为 croquis/cf/*(省略 vize:)。信息和提示诊断会转换为 CLI 警告。相关位置说明来源与消费者之间的关系。
全部规则 · 规则选项 · ESLint 迁移对应表 · 项目检查 · 组件间属性传递
ecosystem/vue-router-extra-param
路由未声明 tab;Vue Router 会丢弃它。
默认严重程度: error
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无
完整且已安装的路由器必须从应用的 createApp(...).use(router) 可达。未知或动态路由表不能证明未知名称诊断。缺失参数属于警告,因为导航可能从当前路由继承值。
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "ecosystem/vue-router-extra-param": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
index.html
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");
src/App.vue
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>
src/router.ts
import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
history: createWebHistory(),
routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});
错误示例
user-post 路径声明 userId 和 postId,但导航还将未声明的 tab 作为路径参数传入。
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2", tab: "a" } });
</script>
<template><p>Post</p></template>
正确示例
从 params 移除 tab,只保留路由路径中的键。应用需要选择标签页时,应另行使用 query。
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
ecosystem/vue-router-missing-param
缺少必需的 postId;依赖当前路由并不稳健。
默认严重程度: warning
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无
完整且已安装的路由器必须从应用的 createApp(...).use(router) 可达。未知或动态路由表不能证明未知名称诊断。缺失参数属于警告,因为导航可能从当前路由继承值。
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "ecosystem/vue-router-missing-param": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
index.html
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");
src/App.vue
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>
src/router.ts
import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
history: createWebHistory(),
routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});
错误示例
导航省略了 user-post 路径必需的 postId。这是警告,因为 Vue Router 可能从当前路由继承该值。
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1" } });
</script>
<template><p>Post</p></template>
正确示例
显式传入 userId 和 postId,使导航不依赖当前路由的参数状态。
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
ecosystem/vue-router-param-type
postId 不是可重复参数,因此数组无效。
默认严重程度: error
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无
完整且已安装的路由器必须从应用的 createApp(...).use(router) 可达。未知或动态路由表不能证明未知名称诊断。缺失参数属于警告,因为导航可能从当前路由继承值。
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "ecosystem/vue-router-param-type": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
index.html
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");
src/App.vue
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>
src/router.ts
import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
history: createWebHistory(),
routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});
错误示例
postId 是标量路径参数,但导航传入了数组 ["2"]。
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: ["2"] } });
</script>
<template><p>Post</p></template>
正确示例
为不可重复的 postId 路径段传入标量 "2"。
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
ecosystem/vue-router-unknown-route
名称不在完整且已安装的路由器中。
默认严重程度: error
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无
完整且已安装的路由器必须从应用的 createApp(...).use(router) 可达。未知或动态路由表不能证明未知名称诊断。缺失参数属于警告,因为导航可能从当前路由继承值。
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "ecosystem/vue-router-unknown-route": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
index.html
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");
src/App.vue
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>
src/router.ts
import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
history: createWebHistory(),
routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});
错误示例
可达且已安装的路由器声明了 user-post,但导航使用拼写错误的 user-posts。
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-posts", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>
正确示例
使用已注册的 user-post 名称,并保留两个已声明的路径参数。
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
html/cross-component-nesting
检查导入组件组合后的实际 HTML 嵌套。
默认严重程度: warning
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "html/cross-component-nesting": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
父组件的 <p> 包含根为 <div> 的已解析子组件,组合后形成无效的段落与块级元素嵌套。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><p><Child /></p></template>
Child.vue
<template><div>Block content</div></template>
正确示例
改用可容纳子组件块级元素的 <section> 容器;子组件保持不变。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><section><Child /></section></template>
Child.vue
<template><div>Block content</div></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/array-mutation
通过索引修改数组,无法被该响应式数组跟踪。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
仅适用于历史 Vue 2.7:此情形须使用匹配的 Vue 2.7 和 SFC 编译器依赖。Vue 3 代理跟踪数组索引赋值,因此 items[0] = next 在 Vue 3 中具有响应性,并非 Vue 3 缺陷。此公开诊断代码目前没有实现。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import Vue from 'vue';
import App from './App.vue';
new Vue({ render: h => h(App) }).$mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script lang="ts">
import Vue from 'vue';
import { replaceFirst } from './replace-first';
export default Vue.extend({
data() { return { items: ['Before'] }; },
methods: { replace() { replaceFirst(this.items, 'After'); } },
});
</script>
<template><section><p>{{ items[0] }}</p><button @click="replace">Replace</button></section></template>
错误示例
在此历史 Vue 2.7 项目中,items[0] = next 修改数组却不通知 Vue 2 数组观察器,因此显示的首项不一定更新。
replace-first.ts
export function replaceFirst(items: string[], next: string): void {
items[0] = next;
}
正确示例
splice(0, 1, next) 使用 Vue 2 可观察的数组修改方法,使同样的替换能更新视图。
replace-first.ts
export function replaceFirst(items: string[], next: string): void {
items.splice(0, 1, next);
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/async-boundary
响应式状态跨越异步边界,可能被观察为过期值。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/async-boundary": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./SearchPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
api.ts
export interface Result { items: string[]; }
export async function load(query: string, options?: { signal?: AbortSignal }): Promise<Result> {
const response = await fetch(`/search?q=${encodeURIComponent(query)}`, options);
return response.json();
}
错误示例
较慢的旧查询可能在新查询之后完成并覆盖 result,因为侦听器没有失效清理。
SearchPage.vue
<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";
const query = ref("");
</script>
<template>
<SearchResults :query="query" />
</template>
SearchResults.vue
<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watch } from "vue";
const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);
watch(
() => props.query,
async (value) => {
result.value = await load(value);
},
);
</script>
正确示例
在等待之前注册清理:中止旧请求并将其 active 标志设为失效,只赋值仍有效的响应。
SearchPage.vue
<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";
const query = ref("");
</script>
<template>
<SearchResults :query="query" />
</template>
SearchResults.vue
<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watch } from "vue";
const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);
watch(
() => props.query,
async (value, _oldValue, onCleanup) => {
const controller = new AbortController();
let active = true;
onCleanup(() => {
active = false;
controller.abort();
});
const next = await load(value, { signal: controller.signal });
if (active) result.value = next;
},
);
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/async-no-suspense
异步组件在没有 Suspense 边界的情况下渲染。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
当前支持情况: no-source-async-fact
边界诊断实现读取 macros.is_async(),但当前源代码解析将顶层 await 记录在 script-setup 作用域。因此,下方完整的错误与正确源代码对照不会通过当前 CLI 产生 async-no-suspense 诊断。它解释 Suspense 规范;补充缺失宏事实属于后续实现工作。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
子组件具有顶层 await,但父组件未提供 <Suspense> 边界。当前源代码解析未提供发出此诊断代码所需的宏事实。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const greeting = await Promise.resolve("Hello");
</script>
<template><p>{{ greeting }}</p></template>
正确示例
父组件将同一异步子组件包在 <Suspense> 中,并提供加载后备内容。这展示规范;当前分析遍对两种源代码形式都不发出该诊断。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Suspense><Child /><template #fallback><p>Loading</p></template></Suspense></template>
Child.vue
<script setup lang="ts">
const greeting = await Promise.resolve("Hello");
</script>
<template><p>{{ greeting }}</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/browser-api-ssr
在组件可能于服务端渲染的位置使用了浏览器专用 API。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/browser-api-ssr": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
window.innerWidth 在 setup 期间运行,但 SSR 环境没有浏览器 window。
App.vue
<script setup lang="ts">
const width = window.innerWidth;
</script>
<template><p>Content</p></template>
正确示例
将 ref 初始化为服务端安全的值,并在客户端挂载后运行的 onMounted 中读取 window。
App.vue
<script setup lang="ts">
import { onMounted, ref } from "vue";
const width = ref(0);
onMounted(() => { width.value = window.innerWidth; });
</script>
<template><p>Content</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/circular-dep
组件相互导入,形成循环。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
此示例展示具体的立即初始化循环。递归 Vue 组件或任意循环导入并不自动构成错误。目前没有实现发出此诊断约定代码。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { aLabel } from './a';
</script>
<template>
<p>{{ aLabel }}</p>
</template>
labels.ts
export const aPrefix = 'A';
export const bPrefix = 'B';
错误示例
a.ts 导入 b.ts,后者又导入 a.ts。两者都立即读取另一模块尚未初始化的常量来初始化自身常量,导致暂时性死区错误。
a.ts
import { bLabel } from './b';
export const aLabel = 'A' + bLabel;
b.ts
import { aLabel } from './a';
export const bLabel = 'B' + aLabel;
正确示例
两个模块都从独立的 labels.ts 读取已初始化前缀,移除循环及立即发生的交叉读取。
a.ts
import { bPrefix } from './labels';
export const aLabel = 'A' + bPrefix;
b.ts
import { aPrefix } from './labels';
export const bLabel = 'B' + aPrefix;
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/circular-reactive-dependency
响应式计算相互依赖,形成循环。
默认严重程度: 依上下文而定
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/circular-reactive-dependency": "warn" },
},
},
});
vp run lint
示例验证状态: illustrative-source-pair
下方完整 Vue 项目展示更新反馈及其修复,但不构成已验证的 CLI 诊断复现:诊断实现需要附图所示的保留响应式流引用身份和边。这些源代码不能证明当前源代码路径会发出此确切代码。专门的跟踪 ID 图诊断对照仍与源代码语法检查分开。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
count-key.ts
import type { InjectionKey, Ref } from 'vue';
export const countKey: InjectionKey<Ref<number>> = Symbol('count');
App.vue
<script setup lang="ts">
import { provide, ref } from 'vue';
import { countKey } from './count-key';
import CycleView from './CycleView.vue';
const count = ref(1); // A: the provider-owned source.
provide(countKey, count);
</script>
<template>
<button @click="count++">Increment</button>
<CycleView />
</template>
错误示例
App 拥有并提供 count(A)。CycleView 派生 nextCount(B),随后立即将每个派生值写回同一注入的 count。每次写入再次改变计算输入,形成 A → B → A 更新反馈。下方保留图中的身份代表这两个引用,而非仅名称相同的无关绑定。
CycleView.vue
<script setup lang="ts">
import { computed, inject, watch } from 'vue';
import { countKey } from './count-key';
const count = inject(countKey)!; // App provides this same A reference.
const nextCount = computed(() => count.value + 1); // B: the derived consumer.
watch(nextCount, value => { count.value = value; }, { immediate: true });
</script>
<template><p>{{ nextCount }}</p></template>
Tracked references: A = provider source; B = consumer reference
Tracked flows: A -> B; B -> A
正确示例
移除将 B 写回 A 的侦听器。App 保留 count 的所有权,仅通过显式 Increment 操作改变它;CycleView 读取派生 nextCount,不将结果反馈回去。同样的引用只保留 A → B 依赖。
CycleView.vue
<script setup lang="ts">
import { computed, inject } from 'vue';
import { countKey } from './count-key';
const count = inject(countKey)!; // App provides this same A reference.
const nextCount = computed(() => count.value + 1); // B: the derived consumer.
</script>
<template><p>{{ nextCount }}</p></template>
Tracked references: A = provider source; B = consumer reference
Tracked flows: A -> B
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/closure-captures-reactive
闭包捕获了响应式值,无法看到后续更新。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { computed, ref } from 'vue';
import { makeReader } from './reader';
const count = ref(0);
const read = makeReader(count);
const shown = computed(read);
</script>
<template>
<button @click="count++">Increment {{ count }}</button><p>{{ shown }}</p>
</template>
错误示例
makeReader 在创建闭包前复制 count.value。计算读取器随后返回初始数字,没有读取响应式依赖。
reader.ts
import type { Ref } from 'vue';
export function makeReader(count: Ref<number>): () => number {
const captured = count.value;
return () => captured;
}
正确示例
闭包在调用时读取 count.value,使计算 getter 能跟踪 ref,并在递增后更新 shown。
reader.ts
import type { Ref } from 'vue';
export function makeReader(count: Ref<number>): () => number {
return () => count.value;
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/composable-outside-setup
组合式函数在 setup 外被调用。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
问题针对这个依赖生命周期的组合式函数,并非全面禁止普通工具函数或 setup 外的所有 Composition API 调用。此诊断约定目前没有实现。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { useTitle } from './use-title';
const title = useTitle();
</script>
<template>
<h1>{{ title }}</h1>
</template>
错误示例
导入 use-title.ts 时,在组件 setup 激活前注册了 onMounted。稍后调用导出函数仅返回模块级 ref,无法补救错失的生命周期所有权。
use-title.ts
import { onMounted, ref } from 'vue';
const title = ref('Before mount');
onMounted(() => { title.value = 'Mounted'; });
export function useTitle() { return title; }
正确示例
状态创建和钩子注册都移到 useTitle,由 App 在 setup 中同步调用。挂载钩子现在属于该 App 实例。
use-title.ts
import { onMounted, ref } from 'vue';
export function useTitle() {
const title = ref('Before mount');
onMounted(() => { title.value = 'Mounted'; });
return title;
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/computed-side-effects
计算 getter 写入状态或执行其他副作用。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { useDouble } from './use-double';
const { count, doubled, lastCalculated } = useDouble();
</script>
<template>
<button @click="count++">Increment {{ count }}</button><p>{{ doubled }} / {{ lastCalculated }}</p>
</template>
错误示例
求值 doubled 会写入 lastCalculated,因此读取计算值也会修改独立状态,将副作用与惰性 getter 的读取时机耦合。
use-double.ts
import { computed, ref } from 'vue';
export function useDouble() {
const count = ref(0);
const lastCalculated = ref(0);
const doubled = computed(() => {
const next = count.value * 2;
lastCalculated.value = next;
return next;
});
return { count, doubled, lastCalculated };
}
正确示例
getter 只返回派生数字。独立侦听器负责在 count 变化时写入 lastCalculated,也包括初始值。
use-double.ts
import { computed, ref, watch } from 'vue';
export function useDouble() {
const count = ref(0);
const lastCalculated = ref(0);
const doubled = computed(() => count.value * 2);
watch(count, next => { lastCalculated.value = next * 2; }, { immediate: true });
return { count, doubled, lastCalculated };
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/deep-import
导入链超过项目允许的深度。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
这是显式选择的项目布局策略,并未宣称存在受支持的深度阈值或选项。此诊断约定目前没有实现。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { label } from './entry';
</script>
<template>
<p>{{ label }}</p>
</template>
value.ts
export const label = 'Notice';
level-one.ts
export { label } from './level-two';
level-two.ts
export { label } from './level-three';
level-three.ts
export { label } from './value';
public-api.ts
export { label } from './value';
错误示例
入口让简单值经过 level-one、level-two 和 level-three,对期望浅层公共边界的项目形成不必要的深导入链。
entry.ts
export { label } from './level-one';
正确示例
入口使用直接重新导出该值的 public-api.ts。消费者保留相同导入名称,导入链变短。
entry.ts
export { label } from './public-api';
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/destructuring-breaks-reactivity
解构响应式对象会复制字段并丢失跟踪。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/destructuring-breaks-reactivity": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
普通解构 props 对象会复制当前 item 值;这与 Vue 3.5 直接解构 defineProps() 不同。
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :item="user" />
</template>
UserSummary.vue
<script setup lang="ts">
const props = defineProps<{ item: { name: string } }>();
const { item } = props;
</script>
正确示例
toRef(props, "item") 保留与 props 属性的联系。
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :item="user" />
</template>
UserSummary.vue
<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ item: { name: string } }>();
const item = toRef(props, "item");
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/di-outside-setup
provide 或 inject 在 setup 外被调用。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
此示例使用组件 provide/inject。app.provide 和受支持的 app.runWithContext 注入是不同且有效的所有权接口,不受此情形禁止。目前没有实现发出此诊断约定代码。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
theme.ts
import { inject, provide } from 'vue';
import type { InjectionKey } from 'vue';
export const ThemeKey: InjectionKey<string> = Symbol('theme');
export function provideTheme() { provide(ThemeKey, 'dark'); }
export function useTheme() { return inject(ThemeKey, 'light'); }
ThemedText.vue
<script setup lang="ts">
import { useTheme } from './theme';
const theme = useTheme();
</script>
<template>
<p>{{ theme }}</p>
</template>
错误示例
main.ts 在没有激活组件实例时调用组件 provide。子组件的 inject 无法获取预期祖先值,因此使用 light。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { provideTheme } from './theme';
provideTheme();
createApp(App).mount('#app');
App.vue
<script setup lang="ts">
import ThemedText from './ThemedText.vue';
</script>
<template>
<ThemedText />
</template>
正确示例
App 在渲染子组件前从 setup 调用提供者。子组件现在从组件祖先继承 dark。
App.vue
<script setup lang="ts">
import ThemedText from './ThemedText.vue';
import { provideTheme } from './theme';
provideTheme();
</script>
<template>
<ThemedText />
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/dom-access-without-next-tick
在 Vue 完成更新刷新之前读取 DOM。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
read-label.ts
export function readLabel(node: HTMLElement | null): string {
return node?.textContent ?? '';
}
错误示例
点击处理器递增 count 后立即读取渲染段落,此时 Vue 尚未刷新计划中的 DOM 更新。sampled 可能包含之前的计数。
App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { readLabel } from './read-label';
const count = ref(0);
const label = ref<HTMLElement | null>(null);
const sampled = ref('');
function increment() {
count.value++;
sampled.value = readLabel(label.value);
}
</script>
<template>
<button @click="increment">Increment</button><p ref="label">{{ count }}</p><p>DOM sample: {{ sampled }}</p>
</template>
正确示例
写入状态后等待 nextTick(),让 Vue 先更新段落,再由 readLabel 读取文本。
App.vue
<script setup lang="ts">
import { nextTick, ref } from 'vue';
import { readLabel } from './read-label';
const count = ref(0);
const label = ref<HTMLElement | null>(null);
const sampled = ref('');
async function increment() {
count.value++;
await nextTick();
sampled.value = readLabel(label.value);
}
</script>
<template>
<button @click="increment">Increment</button><p ref="label">{{ count }}</p><p>DOM sample: {{ sampled }}</p>
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/duplicate-id
多个组件使用同一个元素 id。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/duplicate-id": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./CheckoutForm.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
可达的配送与账单组件都渲染 id="postal-code",使其标签共享不明确的文档目标。
CheckoutForm.vue
<script setup lang="ts">
import BillingAddress from "./BillingAddress.vue";
import ShippingAddress from "./ShippingAddress.vue";
</script>
<template>
<ShippingAddress />
<BillingAddress />
</template>
ShippingAddress.vue
<template>
<label for="postal-code">Shipping postal code</label>
<input id="postal-code" />
</template>
BillingAddress.vue
<template>
<label for="postal-code">Billing postal code</label>
<input id="postal-code" />
</template>
正确示例
每个组件调用 useId(),将自身的值同时绑定给标签和输入框,在不重复字面量 ID 的情况下保留关联。
CheckoutForm.vue
<script setup lang="ts">
import BillingAddress from "./BillingAddress.vue";
import ShippingAddress from "./ShippingAddress.vue";
</script>
<template>
<ShippingAddress />
<BillingAddress />
</template>
ShippingAddress.vue
<script setup lang="ts">
import { useId } from "vue";
const postalCodeId = useId();
</script>
<template>
<label :for="postalCodeId">Shipping postal code</label>
<input :id="postalCodeId" />
</template>
BillingAddress.vue
<script setup lang="ts">
import { useId } from "vue";
const postalCodeId = useId();
</script>
<template>
<label :for="postalCodeId">Billing postal code</label>
<input :id="postalCodeId" />
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/event-listener-leak
注册了事件监听器,却从未移除。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { useWidth } from './use-width';
const width = useWidth();
</script>
<template>
<p>{{ width }}</p>
</template>
错误示例
挂载时添加的 window resize 监听器捕获组件 width ref,但卸载时从不移除。重复挂载可能保留未使用的监听器和状态。
use-width.ts
import { onMounted, ref } from 'vue';
export function useWidth() {
const width = ref(0);
const resize = () => { width.value = window.innerWidth; };
onMounted(() => { resize(); window.addEventListener('resize', resize); });
return width;
}
正确示例
onUnmounted 移除挂载时注册的同一个 resize 函数,结束该实例外部监听器的生命周期。
use-width.ts
import { onMounted, onUnmounted, ref } from 'vue';
export function useWidth() {
const width = ref(0);
const resize = () => { width.value = window.innerWidth; };
onMounted(() => { resize(); window.addEventListener('resize', resize); });
onUnmounted(() => { window.removeEventListener('resize', resize); });
return width;
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/event-modifier
事件监听器使用了发出事件不支持的修饰符。
默认严重程度: info
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
.stop 假定子组件自定义 save 事件具有原生事件的传播方法,但其载荷不一定是 DOM Event。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save.stop="() => {}" /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
正确示例
从自定义事件监听器移除 .stop;需要时在实际 DOM 监听器处理原生传播。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/hydration-risk
此诊断代码归类多种响应性问题,包括将 prop 复制到 ref。它并不表示跨文件分析遍会检测所有 Date.now() 表达式。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/hydration-risk": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
子组件仅初始化一次 ref(props.count),因此局部 count 不再跟随后续父组件 prop 变化。这是当前的 prop 到 ref 诊断产生逻辑,不是普遍的不确定 SSR 示例。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child :count="0" /></template>
Child.vue
<script setup lang="ts">
import { ref } from "vue";
const props = defineProps<{ count: number }>();
const count = ref(props.count);
</script>
<template><p>{{ count }}</p></template>
正确示例
toRef(props, "count") 指向 prop,不再将初始值复制到独立状态。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child :count="0" /></template>
Child.vue
<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ count: number }>();
const count = toRef(props, "count");
</script>
<template><p>{{ count }}</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/inherit-attrs-unused
设置了 inheritAttrs: false,但组件从不读取属性。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
子组件设置 inheritAttrs: false,却从不转发父组件的 class="notice" 属性。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<script setup lang="ts">
defineOptions({ inheritAttrs: false });
</script>
<template><main>Content</main></template>
正确示例
保留显式继承控制,将 $attrs 绑定到预期的 <main> 目标。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<script setup lang="ts">
defineOptions({ inheritAttrs: false });
</script>
<template><main v-bind="$attrs">Content</main></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/inject-without-symbol
inject 使用普通键,而不是 InjectionKey symbol。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/inject-without-symbol": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./ThemeProvider.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
keys/theme.ts
import type { InjectionKey, Ref } from "vue";
export interface Theme { color: string; }
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
错误示例
消费者注入无类型字符串键 "theme",没有与提供者共享的 symbol 身份。
ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
const theme = ref({ color: "blue" });
provide("theme", theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
const theme = inject("theme");
</script>
正确示例
消费者和提供者导入同一个 ThemeKey,不重复字符串名称。
ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey } from "./keys/theme";
const theme = ref({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/injected-async-mutation-race
注入值被可能发生竞态的异步任务修改。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/injected-async-mutation-race": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./StoreProvider.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
api.ts
export async function loadCount(query: string, options?: { signal?: AbortSignal }): Promise<number> {
const response = await fetch(`/count?q=${encodeURIComponent(query)}`, options);
return Number(await response.text());
}
CountSummary.vue
<script setup lang="ts">
import { inject } from "vue";
import { StoreKey } from "./keys/store";
const store = inject(StoreKey)!;
</script>
<template><p>{{ store.count }}</p></template>
错误示例
CountLoader.vue 将等待得到的结果直接写入与 CountSummary.vue 共享的注入 store,使过期任务影响两个消费者。
keys/store.ts
import type { InjectionKey } from "vue";
export interface Store {
count: number;
}
export const StoreKey: InjectionKey<Store> = Symbol("store");
StoreProvider.vue
<script setup lang="ts">
import { provide, reactive } from "vue";
import CountLoader from "./CountLoader.vue";
import CountSummary from "./CountSummary.vue";
import { StoreKey, type Store } from "./keys/store";
const store = reactive<Store>({ count: 0 });
provide(StoreKey, store);
</script>
<template>
<CountLoader />
<CountSummary />
</template>
CountLoader.vue
<script setup lang="ts">
import { loadCount } from "./api";
import { inject, ref, watch } from "vue";
import { StoreKey } from "./keys/store";
const store = inject(StoreKey)!;
const query = ref("");
watch(query, async (value) => {
store.count = await loadCount(value);
});
</script>
正确示例
加载器取消失效任务,只发出有效结果。提供者通过 applyLoadedCount 拥有 store 修改权。
keys/store.ts
import type { InjectionKey } from "vue";
export interface Store {
count: number;
}
export const StoreKey: InjectionKey<Store> = Symbol("store");
StoreProvider.vue
<script setup lang="ts">
import { provide, reactive } from "vue";
import CountLoader from "./CountLoader.vue";
import CountSummary from "./CountSummary.vue";
import { StoreKey, type Store } from "./keys/store";
const store = reactive<Store>({ count: 0 });
provide(StoreKey, store);
function applyLoadedCount(count: number) {
store.count = count;
}
</script>
<template>
<CountLoader @loaded="applyLoadedCount" />
<CountSummary />
</template>
CountLoader.vue
<script setup lang="ts">
import { loadCount } from "./api";
import { ref, watch } from "vue";
const emit = defineEmits<{ loaded: [count: number] }>();
const query = ref("");
watch(query, async (value, _oldValue, onCleanup) => {
const controller = new AbortController();
let active = true;
onCleanup(() => {
active = false;
controller.abort();
});
const count = await loadCount(value, { signal: controller.signal });
if (active) emit("loaded", count);
});
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/lifecycle-outside-setup
生命周期钩子在 setup 外注册。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
install-title.ts
import { onMounted } from 'vue';
export function installTitle() {
onMounted(() => { document.title = 'Mounted application'; });
}
错误示例
入口在挂载应用前调用 installTitle(),因此 onMounted 在没有激活组件 setup 上下文时注册。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { installTitle } from './install-title';
installTitle();
createApp(App).mount('#app');
App.vue
<script setup lang="ts">
</script>
<template>
<p>Application</p>
</template>
正确示例
从 App 的 setup 同步调用同一辅助函数,将生命周期回调关联到该实例的挂载。
App.vue
<script setup lang="ts">
import { installTitle } from './install-title';
installTitle();
</script>
<template>
<p>Application</p>
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/lifecycle-without-cleanup
生命周期钩子启动任务,却从不清理。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
挂载时注册 window resize 监听器,卸载时却不移除同一回调。
App.vue
<script setup lang="ts">
import { onMounted } from "vue";
const resize = () => {};
onMounted(() => { window.addEventListener("resize", resize); });
</script>
<template><p>Content</p></template>
正确示例
onUnmounted 使用与 addEventListener 相同的事件名称和函数身份移除监听器。
App.vue
<script setup lang="ts">
import { onMounted, onUnmounted } from "vue";
const resize = () => {};
onMounted(() => { window.addEventListener("resize", resize); });
onUnmounted(() => { window.removeEventListener("resize", resize); });
</script>
<template><p>Content</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/missing-required-prop
未传入必需的 prop。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
父组件渲染 <Child />,没有提供子组件必需的 title: string prop。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
正确示例
title="Hello" 提供已解析子组件声明的必需 prop。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/missing-suspense
异步依赖在 Suspense 边界之外使用。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
AsyncCard.vue
<script setup lang="ts">
const message = await Promise.resolve('Ready');
</script>
<template>
<p>{{ message }}</p>
</template>
错误示例
AsyncCard 具有顶层 await,使其 setup 异步,但 App 没有使用 Suspense 边界协调该依赖。
App.vue
<script setup lang="ts">
import AsyncCard from './AsyncCard.vue';
</script>
<template>
<AsyncCard />
</template>
正确示例
App 将异步子组件包在 Suspense 内,在子组件 setup 完成前提供加载后备内容。
App.vue
<script setup lang="ts">
import { Suspense } from 'vue';
import AsyncCard from './AsyncCard.vue';
</script>
<template>
<Suspense><AsyncCard /><template #fallback><p>Loading…</p></template></Suspense>
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/module-scope-reactive
响应式状态在模块作用域创建,被所有调用者共享。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
有意共享的应用 store 在模块作用域创建响应式状态是合法的。此示例假定组件与请求隔离;此公开诊断约定目前没有实现。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import Counter from './Counter.vue';
</script>
<template>
<Counter /><Counter />
</template>
Counter.vue
<script setup lang="ts">
import { createCounter } from './counter';
const { count } = createCounter();
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
错误示例
模块仅初始化一次 count,两个 Counter 实例收到同一个 ref。虽然示例期望独立实例状态,点击一个却会改变两个计数器。
counter.ts
import { ref } from 'vue';
const count = ref(0);
export function createCounter() { return { count }; }
正确示例
在 createCounter 内创建 ref,为每个同步 setup 调用提供独立状态对象,使每个按钮拥有自己的计数器。
counter.ts
import { ref } from 'vue';
export function createCounter() {
const count = ref(0);
return { count };
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/multi-root-attrs
多根节点组件接收了属性,却没有放置目标。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
子组件具有 <main> 和 <aside> 两个根节点,Vue 没有可自动接收父组件 class 的唯一根节点。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<template><main>Content</main><aside>Help</aside></template>
正确示例
显式将 $attrs 转发到 <main>,同时保留第二个根节点。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<template><main v-bind="$attrs">Content</main><aside>Help</aside></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/mutated-after-escape
响应式对象逸出其所有者后被修改。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
这是显式的不可变历史所有权策略,并非普遍禁止传递或随后修改响应式对象。目前没有实现发出此诊断约定。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
archive.ts
export interface Profile { name: string }
const records: Readonly<Profile>[] = [];
export function publish(profile: Readonly<Profile>): void { records.push(profile); }
export function latestName(): string { return records.at(-1)?.name ?? ''; }
App.vue
<script setup lang="ts">
import { publishProfile } from './profile';
import { latestName } from './archive';
publishProfile();
const archivedName = latestName();
</script>
<template>
<p>Archived name: {{ archivedName }}</p>
</template>
错误示例
归档保留传给 publish 的同一对象,所有者随后修改其名称,使本应属于历史的记录也变成 Grace。TypeScript 的 Readonly 参数不会复制对象。
profile.ts
import { reactive } from 'vue';
import { publish } from './archive';
export function publishProfile(): void {
const profile = reactive({ name: 'Ada' });
publish(profile);
profile.name = 'Grace';
}
正确示例
发布普通副本,将归档的 Ada 记录与之后对响应式 profile 的编辑分开,维护归档快照策略。
profile.ts
import { reactive } from 'vue';
import { publish } from './archive';
export function publishProfile(): void {
const profile = reactive({ name: 'Ada' });
publish({ ...profile });
profile.name = 'Grace';
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/non-reactive-provide
提供的值不具响应性,后代无法看到更新。
默认严重程度: 依上下文而定
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/non-reactive-provide": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./ThemeProvider.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
ThemeProvider.vue 提供普通对象。修改其字段不会为注入消费者建立 Vue 响应式依赖。
keys/theme.ts
export const ThemeKey = Symbol("theme");
ThemeProvider.vue
<script setup lang="ts">
import { provide } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey } from "./keys/theme";
const theme = { color: "blue" };
provide(ThemeKey, theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
正确示例
提供者将 theme 包装在 ref 内;同一个注入引用可以跟踪后续变化。
keys/theme.ts
export const ThemeKey = Symbol("theme");
ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey } from "./keys/theme";
const theme = ref({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/non-unique-id
循环内的元素 id 对每个列表项并不唯一。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/non-unique-id": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./ResultsList.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
每次 v-for 迭代都渲染相同的 result-title 字面量 ID;循环的 key 不会让 DOM ID 唯一。
ResultsList.vue
<script setup lang="ts">
const results = [{ id: "first", title: "First result" }, { id: "second", title: "Second result" }];
</script>
<template>
<article v-for="result in results" :key="result.id">
<h2 id="result-title">{{ result.title }}</h2>
</article>
</template>
正确示例
标题 ID 包含结果的稳定 ID,为每个列表项产生不同的文档标识符。
ResultsList.vue
<script setup lang="ts">
const results = [{ id: "first", title: "First result" }, { id: "second", title: "Second result" }];
</script>
<template>
<article v-for="result in results" :key="result.id">
<h2 :id="`result-${result.id}-title`">{{ result.title }}</h2>
</article>
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/object-identity-comparison
响应式对象按身份比较,解包后身份会改变。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
此示例假定 ID 唯一标识记录。比较指向同一响应式代理的两个引用仍然有效;此诊断约定目前没有实现。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
user.ts
import { reactive } from 'vue';
export function makeUser() {
const raw = { id: 7, name: 'Ada' };
return { raw, proxy: reactive(raw) };
}
错误示例
proxy === raw 比较包装对象身份,因此即使二者代表同一用户记录,结果也是 false。应用希望比较记录身份,而非包装对象身份。
App.vue
<script setup lang="ts">
import { makeUser } from './user';
const { raw, proxy } = makeUser();
const sameRecord = proxy === raw;
</script>
<template>
<p>Same record: {{ sameRecord }}</p>
</template>
正确示例
比较稳定的记录 id,回答预期问题,不依赖对象是原始对象还是代理。
App.vue
<script setup lang="ts">
import { makeUser } from './user';
const { raw, proxy } = makeUser();
const sameRecord = proxy.id === raw.id;
</script>
<template>
<p>Same record: {{ sameRecord }}</p>
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/pinia-getter
Pinia getter 未通过 storeToRefs 读取,无法保持响应性。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
必须安装 Pinia,且 main.ts 须在挂载前安装其插件。在受跟踪的计算或模板内直接读取 store.doubled 是有效的;此处问题是取普通快照。该诊断约定目前没有实现。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
createApp(App).use(createPinia()).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
counter-store.ts
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: { doubled: state => state.count * 2 },
});
错误示例
const doubled = store.doubled 在 setup 时复制 getter 的当前数字。复制的数字不会跟随后续 store.count 更新。
App.vue
<script setup lang="ts">
import { useCounterStore } from './counter-store';
const store = useCounterStore();
const doubled = store.doubled;
</script>
<template>
<button @click="store.count++">{{ store.count }}</button><p>{{ doubled }}</p>
</template>
正确示例
storeToRefs(store) 提供响应式 getter ref,可被解构并由模板解包,同时继续关联 store。
App.vue
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useCounterStore } from './counter-store';
const store = useCounterStore();
const { doubled } = storeToRefs(store);
</script>
<template>
<button @click="store.count++">{{ store.count }}</button><p>{{ doubled }}</p>
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/prop-type-mismatch
传入的 prop 值与声明类型不匹配。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
父组件将数字表达式 42 传给已解析子组件的 title: string prop。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child :title="42" /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
正确示例
字面量 title="Hello" 提供符合子组件声明的字符串。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/provide-inject-type
提供值与其 inject 类型不一致。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/provide-inject-type": "warn" },
},
},
});
vp run lint
此检查比较提供者与消费者的显式类型注解,而非推断的字面量值类型。保留示例中提供者的 as string 注解。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
提供者将 title 显式注解为 string,后代却为同一键请求 inject<number>。
App.vue
<script setup lang="ts">
import { provide } from "vue";
import Child from "./Child.vue";
provide("title", "Hello" as string);
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
import { inject } from "vue";
const title = inject<number>("title");
</script>
<template><p>Content</p></template>
正确示例
消费者显式的 inject<string> 与提供者注解一致。保留 as string:此诊断产生逻辑比较显式注解,而非推断的字面量类型。
App.vue
<script setup lang="ts">
import { provide } from "vue";
import Child from "./Child.vue";
provide("title", "Hello" as string);
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
import { inject } from "vue";
const title = inject<string>("title");
</script>
<template><p>Content</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/provide-without-symbol
provide 使用普通键,而不是 InjectionKey symbol。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/provide-without-symbol": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./ThemeProvider.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
两个组件都使用字符串 "theme",无关功能可能意外复用此键。
ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
const theme = ref({ color: "blue" });
provide("theme", theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
const theme = inject("theme");
</script>
正确示例
导出一个带类型的 ThemeKey symbol,并在 provide 和 inject 处导入同一值。创建描述相同但独立的 symbols 并不能建立联系。
ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey, type Theme } from "./keys/theme";
const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
keys/theme.ts
import type { InjectionKey, Ref } from "vue";
export interface Theme {
color: string;
}
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/reactive-export
响应式状态从模块导出。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
有意共享的应用 store 可以导出响应式状态。此情形要求状态隔离,并未声称所有响应式导出都无效。目前没有实现发出此诊断约定。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
错误示例
模块导出一个已初始化的响应式对象,所有导入者都收到同一个 count。在请求间共享的 SSR 模块中,这违背示例要求的实例与请求状态隔离。
state.ts
import { reactive } from 'vue';
export const state = reactive({ count: 0 });
App.vue
<script setup lang="ts">
import { state } from './state';
</script>
<template>
<button @click="state.count++">{{ state.count }}</button>
</template>
正确示例
模块导出工厂,由 App 在 setup 内调用。每个实例获得新的响应式 count,而非导出的单例。
state.ts
import { reactive } from 'vue';
export function createState() { return reactive({ count: 0 }); }
App.vue
<script setup lang="ts">
import { createState } from './state';
const state = createState();
</script>
<template>
<button @click="state.count++">{{ state.count }}</button>
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/reactivity-outside-setup
响应式 API 在 setup 外被调用。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
Vue 允许在组件 setup 外使用 ref/reactive/computed。此处风险是在显式实例隔离策略下产生非预期所有权与共享,并非 API 不合法。此诊断约定目前没有实现。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import Counter from './Counter.vue';
</script>
<template>
<Counter /><Counter />
</template>
Counter.vue
<script setup lang="ts">
import { useCounter } from './use-counter';
const { count, doubled } = useCounter();
</script>
<template>
<button @click="count++">{{ count }}</button><p>{{ doubled }}</p>
</template>
错误示例
两个响应式 API 都在模块加载时运行,因此两个 Counter 实例共享一个 ref 和计算值,违背独立计数器的预期。
use-counter.ts
import { computed, ref } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
export function useCounter() { return { count, doubled }; }
正确示例
useCounter 在每次组件 setup 调用中同步创建 ref 和计算值,为每个控件提供自己的状态及受跟踪的派生值。
use-counter.ts
import { computed, ref } from 'vue';
export function useCounter() {
const count = ref(0);
const doubled = computed(() => count.value * 2);
return { count, doubled };
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/reassignment-breaks-reactivity
重新赋值响应式绑定,将其替换为普通值。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/reassignment-breaks-reactivity": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
子组件创建 prop ref 后用 props.user 覆盖变量,丢弃该 ref 联系。
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :user="user" />
</template>
UserSummary.vue
<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ user: { name: string } }>();
let user = toRef(props, "user");
user = props.user;
</script>
正确示例
将 toRef 保留在 const 绑定中,移除替换它的重新赋值。
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :user="user" />
</template>
UserSummary.vue
<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ user: { name: string } }>();
const user = toRef(props, "user");
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/reference-escapes-scope
响应式引用逸出拥有其生命周期的作用域。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
Refs 可以合法地从组合式函数返回或跨作用域共享。此示例显式要求快照缓存,并未声称卸载会使 ref 失效。目前没有实现发出此诊断约定。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
saved.ts
import type { Ref } from 'vue';
let saved: Ref<number> | number | undefined;
export function remember(value: Ref<number> | number): void { saved = value; }
export function remembered(): Ref<number> | number | undefined { return saved; }
错误示例
进程级缓存保留组件的活 count ref,可能让实例状态在卸载后仍可达,并观察后续编辑,但此缓存原本用于存储快照。
App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { remember } from './saved';
const count = ref(0);
remember(count);
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
正确示例
缓存接收当前普通数字,保留快照而不保留组件拥有的 ref。
App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { remember } from './saved';
const count = ref(0);
remember(count.value);
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/setup-context-violation
setup 上下文被以 Vue 不允许的方式使用。
默认严重程度: 依上下文而定
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
ref(0) 在普通脚本模块作用域创建,位于此分析器情形所表示的按实例 setup 上下文之外。
App.vue
<script lang="ts">
import { ref } from "vue";
const count = ref(0);
export default {};
</script>
<template><p>Count</p></template>
正确示例
将绑定移入 script setup,使每个组件实例拥有自己的 count,模板也能读取它。
App.vue
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
</script>
<template><p>{{ count }}</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/shallow-deep-access
读取 shallowReactive 或 shallowRef 的深层属性,却假定它受到跟踪。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { makeProfile } from './profile';
const profile = makeProfile();
</script>
<template>
<p>{{ profile.user.name }}</p><button @click="profile.user.name = 'Grace'">Rename</button>
</template>
错误示例
shallowReactive 跟踪根 user 属性,但嵌套对象仍是原始对象。修改 profile.user.name 不会作为受跟踪的深层修改通知模板。
profile.ts
import { shallowReactive } from 'vue';
export function makeProfile() { return shallowReactive({ user: { name: 'Ada' } }); }
正确示例
深层 reactive 包装嵌套 user 对象,使同样的名称赋值能触发显示名称更新。
profile.ts
import { reactive } from 'vue';
export function makeProfile() { return reactive({ user: { name: 'Ada' } }); }
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/spread-breaks-reactivity
展开响应式对象会复制值并丢失跟踪。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/spread-breaks-reactivity": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
UserSummary.vue 将 props.user 展开为新对象,取得传入响应式数据的快照。
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada", role: "admin" });
</script>
<template>
<UserSummary :user="user" />
</template>
UserSummary.vue
<script setup lang="ts">
const props = defineProps<{ user: { name: string; role: string } }>();
const copiedUser = { ...props.user };
</script>
正确示例
toRef(props, "user") 保留对传入 prop 的引用,不复制其字段。
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada", role: "admin" });
</script>
<template>
<UserSummary :user="user" />
</template>
UserSummary.vue
<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ user: { name: string; role: string } }>();
const user = toRef(props, "user");
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/suspense-no-fallback
<Suspense> 没有后备内容。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
没有后备内容的 Suspense 是有效的 Vue 语法。这是选定的加载界面规范,并非编译器错误;此公开诊断约定目前没有实现。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
AsyncCard.vue
<script setup lang="ts">
const message = await Promise.resolve('Ready');
</script>
<template>
<p>{{ message }}</p>
</template>
错误示例
Suspense 边界有异步子组件,却没有后备内容,因此此示例的等待状态没有加载内容。
App.vue
<script setup lang="ts">
import { Suspense } from 'vue';
import AsyncCard from './AsyncCard.vue';
</script>
<template>
<Suspense><AsyncCard /></Suspense>
</template>
正确示例
#fallback 插槽在异步子组件完成前提供显式的加载段落。
App.vue
<script setup lang="ts">
import { Suspense } from 'vue';
import AsyncCard from './AsyncCard.vue';
</script>
<template>
<Suspense><AsyncCard /><template #fallback><p>Loading…</p></template></Suspense>
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/template-ref-timing
模板 ref 在组件挂载之前被读取。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
focus-input.ts
export function focusInput(input: HTMLInputElement | null): void { input?.focus(); }
错误示例
setup 在挂载前读取模板 ref,此时值仍为 null,因此可选的 focus 调用不会执行聚焦操作。
App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { focusInput } from './focus-input';
const input = ref<HTMLInputElement | null>(null);
focusInput(input.value);
</script>
<template>
<input ref="input" aria-label="Name" />
</template>
正确示例
onMounted 将读取延迟到 Vue 为模板 ref 赋入输入元素之后,使聚焦辅助函数能作用于该元素。
App.vue
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { focusInput } from './focus-input';
const input = ref<HTMLInputElement | null>(null);
onMounted(() => { focusInput(input.value); });
</script>
<template>
<input ref="input" aria-label="Name" />
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/toraw-mutation
使用 toRaw 后修改了原始对象。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { makeProfile, rename } from './profile';
const profile = makeProfile();
</script>
<template>
<p>{{ profile.name }}</p><button @click="rename(profile)">Rename</button>
</template>
错误示例
rename 获取原始目标并写入 raw.name,绕过本应通知显示名称更新的代理 setter。
profile.ts
import { reactive, toRaw } from 'vue';
export function makeProfile() { return reactive({ name: 'Ada' }); }
export function rename(profile: { name: string }): void {
const raw = toRaw(profile);
raw.name = 'Grace';
}
正确示例
通过传入的响应式代理写入 profile.name,保留相同重命名行为,同时通知依赖项。
profile.ts
import { reactive } from 'vue';
export function makeProfile() { return reactive({ name: 'Ada' }); }
export function rename(profile: { name: string }): void {
profile.name = 'Grace';
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/uncaught-error
组件可能抛错,却没有错误边界捕获。
默认严重程度: info
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/uncaught-error": "warn" },
},
},
});
vp run lint
当前诊断实现扫描 JSON.parse(input) 等模板表达式,不会报告仅存在于 script 块中的 throw 语句。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
子组件模板对格式错误的输入调用 JSON.parse,可达父组件没有错误捕获边界。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const input = "{";
</script>
<template><button @click="JSON.parse(input)">Parse</button></template>
正确示例
父组件为该子组件注册 onErrorCaptured。返回 false 会停止传播;生产环境边界还应提供有用的恢复界面。
App.vue
<script setup lang="ts">
import { onErrorCaptured } from "vue";
import Child from "./Child.vue";
onErrorCaptured(() => false);
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const input = "{";
</script>
<template><button @click="JSON.parse(input)">Parse</button></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/undeclared-emit
组件发出了未声明的事件。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
子组件调用 emit("save"),但 defineEmits 约定只声明了 cancel。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ cancel: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
正确示例
以空参数元组声明 save,使发出的事件符合组件约定。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/undeclared-prop
父组件传入了子组件未声明的 prop。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
父组件传入 typo,但已解析子组件只声明 title。此分析器规范独立于 Vue 通常的属性透传行为。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" :typo="true" /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
正确示例
移除意外的 typo 绑定,保留已声明的 title prop。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/undefined-slot
父组件填充了子组件未暴露的插槽。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
Card.vue
<script setup lang="ts">
defineSlots<{ header(): unknown }>();
</script>
<template>
<article><header><slot name="header" /></header></article>
</template>
错误示例
App 提供 footer 插槽,但 Card 仅声明和渲染 header,传入的 Notice 内容在此子组件中没有匹配的插槽出口。
App.vue
<script setup lang="ts">
import Card from './Card.vue';
</script>
<template>
<Card><template #footer>Notice</template></Card>
</template>
正确示例
App 提供 header,与子组件类型化插槽声明和渲染出口一致,因此 Notice 在该处显示。
App.vue
<script setup lang="ts">
import Card from './Card.vue';
</script>
<template>
<Card><template #header>Notice</template></Card>
</template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/unhandled-event
子组件发出的事件没有父组件处理。
默认严重程度: info
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
Child.vue 发出 save,直属包装组件却没有监听;组件事件不会自动穿过包装组件冒泡。
App.vue
<script setup lang="ts">
import Wrapper from "./Wrapper.vue";
</script>
<template><Wrapper /></template>
Wrapper.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
正确示例
Wrapper.vue 为直接子组件附加 save 监听器。空回调展示此规则认可的处理形式,并不是完整保存实现。
App.vue
<script setup lang="ts">
import Wrapper from "./Wrapper.vue";
</script>
<template><Wrapper /></template>
Wrapper.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/unmatched-inject
inject 指定的键没有任何祖先提供。
默认严重程度: error / warning(具有默认值时)
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/unmatched-inject": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
ThemeLabel.vue 注入 ThemeKey,但其可达祖先 App.vue 从未提供该键。
keys/theme.ts
import type { InjectionKey, Ref } from "vue";
export interface Theme {
color: string;
}
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
App.vue
<script setup lang="ts">
import ThemeLabel from "./ThemeLabel.vue";
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
正确示例
App.vue 在渲染注入的后代之前,使用同一导出的 ThemeKey 提供响应式 theme。
keys/theme.ts
import type { InjectionKey, Ref } from "vue";
export interface Theme {
color: string;
}
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
App.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey, type Theme } from "./keys/theme";
const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/unmatched-listener
父组件监听了子组件不发出的事件。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
父组件监听 save,已解析子组件却只声明 cancel。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ cancel: [] }>();
</script>
<template><p>Content</p></template>
正确示例
子组件声明并发出 save,与父组件监听器名称一致。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/unregistered-component
模板使用了未注册或导入的组件。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
虽然存在 Child.vue 文件,父组件既没有导入它,也没有为模板以其他方式注册 Child。
App.vue
<template><Child /></template>
Child.vue
<template><p>Child</p></template>
正确示例
在父组件 script setup 中导入 Child,使模板能解析组件绑定。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<template><p>Child</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/unresolved-import
导入无法解析为模块。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
父组件导入 ./Missing.vue,但项目包含的是 Child.vue,并非该路径。
App.vue
<script setup lang="ts">
import Child from "./Missing.vue";
</script>
<template><Child /></template>
Child.vue
<template><p>Child</p></template>
正确示例
将导入指向已存在的 ./Child.vue 文件,保留相同模板绑定。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<template><p>Child</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/unused-attrs
透传属性传给了未使用它们的多根节点组件。
默认严重程度: info
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
父组件的 tracking-code 既未被多根节点子组件作为 prop 使用,也未被转发。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child tracking-code="notice" /></template>
Child.vue
<template><main>Content</main><aside>Help</aside></template>
正确示例
在 <main> 上绑定 $attrs,为该透传属性提供显式目标。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child tracking-code="notice" /></template>
Child.vue
<template><main v-bind="$attrs">Content</main><aside>Help</aside></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/unused-emit
已声明的 emit 从未使用。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
实验性 Rust CrossFileAnalyzer 有此诊断代码的实现。CLI 分析遍不会单独发出该代码;配置其 ID 不会启用该 Rust 分析遍。这些情形描述分析器支持的图与事实,并非 Vite+ 功能承诺。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
子组件声明了 save,却从未以该名称调用事件发出函数。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
</script>
<template><p>Content</p></template>
正确示例
示例调用 emit("save"),使声明事件得到使用。真实交互应在对应操作发生时发出它。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/unused-provide
提供的键从未被注入。
默认严重程度: warning
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/unused-provide": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
keys/theme.ts
import type { InjectionKey, Ref } from "vue";
export interface Theme { color: string; }
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
错误示例
App.vue 提供 ThemeKey,但渲染的 Dashboard.vue 子树没有该键的消费者。
App.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import Dashboard from "./Dashboard.vue";
import { ThemeKey, type Theme } from "./keys/theme";
const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<Dashboard />
</template>
Dashboard.vue
<template>
<h1>Dashboard</h1>
</template>
正确示例
仪表盘现在渲染 ThemeLabel.vue,后者注入与祖先完全相同身份的 ThemeKey。
App.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import Dashboard from "./Dashboard.vue";
import { ThemeKey, type Theme } from "./keys/theme";
const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<Dashboard />
</template>
Dashboard.vue
<script setup lang="ts">
import ThemeLabel from "./ThemeLabel.vue";
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/value-extraction-breaks-reactivity
将响应式值读取到局部变量后丢失后续更新。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/value-extraction-breaks-reactivity": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
Vue 3.5 响应式解构的 item 被一次性读取到 itemSnapshot,后续 prop 替换不会更新该快照。
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :item="user" />
</template>
UserSummary.vue
<script setup lang="ts">
const { item } = defineProps<{ item: { name: string } }>();
const itemSnapshot = item;
</script>
正确示例
在 computed 内读取 item,让 Vue 的响应式 props 解构转换跟踪每次求值。
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :item="user" />
</template>
UserSummary.vue
<script setup lang="ts">
import { computed } from "vue";
const { item } = defineProps<{ item: { name: string } }>();
const itemView = computed(() => item);
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/watch-can-be-computed
侦听器只将值复制到状态,可以改为计算属性。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
此示例展示公开的纯派生状态偏好。侦听器仍适用于外部副作用或独立可写状态;目前没有实现发出此诊断约定。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { useDouble } from './use-double';
const { count, doubled } = useDouble();
</script>
<template>
<button @click="count++">{{ count }}</button><p>{{ doubled }}</p>
</template>
错误示例
侦听器没有外部副作用,只将第二个可写 ref 同步为 count 的两倍。示例没有对该派生值的独立写入。
use-double.ts
import { ref, watch } from 'vue';
export function useDouble() {
const count = ref(0);
const doubled = ref(0);
watch(count, next => { doubled.value = next * 2; }, { immediate: true });
return { count, doubled };
}
正确示例
计算 getter 直接表达相同派生关系,移除手动同步及额外可写状态。
use-double.ts
import { computed, ref } from 'vue';
export function useDouble() {
const count = ref(0);
const doubled = computed(() => count.value * 2);
return { count, doubled };
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/watcheffect-async
watchEffect 启动异步任务,却无法清理上一轮执行。
默认严重程度: error
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/watcheffect-async": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./SearchPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
api.ts
export interface Result { items: string[]; }
export async function load(query: string, options?: { signal?: AbortSignal }): Promise<Result> {
const response = await fetch(`/search?q=${encodeURIComponent(query)}`, options);
return response.json();
}
错误示例
异步 watchEffect 将隐式依赖收集与等待请求混合,且没有失效保护。
SearchPage.vue
<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";
const query = ref("");
</script>
<template>
<SearchResults :query="query" />
</template>
SearchResults.vue
<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watchEffect } from "vue";
const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);
watchEffect(async () => {
result.value = await load(props.query);
});
</script>
正确示例
显式的 watch(() => props.query, ...) 声明来源、注册请求清理,并在失效后拒绝过期响应。
SearchPage.vue
<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";
const query = ref("");
</script>
<template>
<SearchResults :query="query" />
</template>
SearchResults.vue
<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watch } from "vue";
const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);
watch(
() => props.query,
async (value, _oldValue, onCleanup) => {
const controller = new AbortController();
let active = true;
onCleanup(() => {
active = false;
controller.abort();
});
const next = await load(value, { signal: controller.signal });
if (active) result.value = next;
},
);
</script>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vize:croquis/cf/watcher-outside-setup
watch 或 watchEffect 在 setup 外被调用。
默认严重程度: 不产生诊断
适用范围: 已分析的组件图,以及下方说明的受支持事实
自动修复: 无;请检查相关文件并应用修复
选项: 无针对单个诊断代码的选项;受支持的 CLI 诊断可覆盖严重程度
这是目前没有实现的公开诊断约定。下方错误与正确示例解释风险及修复;目前没有任何标志能使此诊断代码触发。
所有者保留并调用停止句柄,或有意赋予应用级生命周期时,模块作用域侦听器是有效的。此示例要求组件拥有生命周期;该诊断约定目前没有实现。
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import Observer from './Observer.vue';
</script>
<template>
<Observer /><Observer />
</template>
Observer.vue
<script setup lang="ts">
import { useObserver } from './use-observer';
const { count, observed } = useObserver();
</script>
<template>
<button @click="count++">{{ count }}</button><p>{{ observed }}</p>
</template>
错误示例
侦听器在模块加载时创建,位于两个 Observer 的 setup 之外,两实例共享其 refs。某个 Observer 卸载时不会自动停止它。
use-observer.ts
import { ref, watch } from 'vue';
const count = ref(0);
const observed = ref(0);
watch(count, next => { observed.value = next; });
export function useObserver() { return { count, observed }; }
正确示例
每个同步 setup 调用在 useObserver 中创建自身 refs 和侦听器。Vue 将该侦听器与调用组件的生命周期关联。
use-observer.ts
import { ref, watch } from 'vue';
export function useObserver() {
const count = ref(0);
const observed = ref(0);
watch(count, next => { observed.value = next; });
return { count, observed };
}
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。
vue/cross-file-attrs-fallthrough
父组件传入属性,但已解析子组件的根无法继承它们,也没有显式使用 $attrs。
默认严重程度: warning
适用范围: 可达的项目声明及导入组件
选项: crossFile;规则严重程度(off/warn/error)
自动修复: 无
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
vue: {
linter: { preset: "incremental", crossFile: true,
rules: { "vue/cross-file-attrs-fallthrough": "warn" },
},
},
});
vp run lint
共享项目文件
错误和正确示例都使用这些不变的文件。在项目中安装导入的包:Vue,以及示例中出现的 vue-router 或 Pinia。遵循版本特定的支持说明。入口根文件显式展示组件关系。
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
错误示例
父组件将 class="notice" 传给已解析的片段子组件;该子组件没有自动属性目标,也从不读取 $attrs。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<template><main>Content</main><aside>Help</aside></template>
正确示例
子组件将 $attrs 绑定在 <main> 上,选择它作为目标;同级 <aside> 保持独立。
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<template><main v-bind="$attrs">Content</main><aside>Help</aside></template>
正确示例的文件展示上述修改;完整项目仍可能适用其他诊断。