Vize

vize:croquis/cf/injected-async-mutation-race

inject した値が、競合し得る async タスクから変更されています。

既定の重大度: error
適用範囲: 解析対象のコンポーネント構成と、下記に示す対応済みの情報
自動修正: なし。関連ファイルを確認して修正してください
オプション: コード固有のオプションはありません。対応済みの CLI 検出は重大度を変更できます

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  lint: {
    vize: { preset: "incremental", crossFile: true,
      rules: { "croquis/cf/injected-async-mutation-race": "warn" },
    },
  },
});
vp run lint

共通のプロジェクト ファイル

以下のファイルは悪い例・良い例で共通です。import する 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 が await の結果を、CountSummary.vue と共有する inject 済み 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>

良い

loader は無効化した処理を中止し、有効な結果だけ emit します。store の変更は提供側の applyLoadedCount が担当します。

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>

良い例のファイルは上で説明した変更を示します。プロジェクトには別の検出が残る場合があります。

公開の説明

生成元

ファイル間ルール一覧