Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 110 additions & 4 deletions src/api/reactivity-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@
function watchEffect(
effect: (onCleanup: OnCleanup) => void,
options?: WatchEffectOptions
): StopHandle
): WatchHandle

type OnCleanup = (cleanupFn: () => void) => void

Expand All @@ -248,7 +248,12 @@
onTrigger?: (event: DebuggerEvent) => void
}

type StopHandle = () => void
interface WatchHandle {
(): void // 呼び出し可能、`stop` と同様
pause: () => void
resume: () => void
stop: () => void
}
```

- **詳細**
Expand Down Expand Up @@ -295,6 +300,47 @@
stop()
```

ウォッチャーの一時停止 / 再開: <sup class="vt-badge" data-text="3.5+" />

```js
const { stop, pause, resume } = watchEffect(() => {})

// ウォッチャーを一時停止する
pause()

// あとで再開する
resume()

// 停止する
stop()
```

副作用のクリーンアップ:

```js
watchEffect(async (onCleanup) => {
const { response, cancel } = doAsyncWork(newId)
// `id` が変更されると `cancel` が呼ばれ、
// 前のリクエストがまだ完了していない場合はキャンセルされます
onCleanup(cancel)
data.value = await response
})
```

3.5+ での副作用のクリーンアップ:

```js
import { onWatcherCleanup } from 'vue'

watchEffect(async () => {
const { response, cancel } = doAsyncWork(newId)
// `id` が変更されると `cancel` が呼ばれ、
// 前のリクエストがまだ完了していない場合はキャンセルされます
onWatcherCleanup(cancel)
data.value = await response
})
```

オプション:

```js
Expand Down Expand Up @@ -333,14 +379,14 @@
source: WatchSource<T>,
callback: WatchCallback<T>,
options?: WatchOptions
): StopHandle
): WatchHandle

// 複数ソースの監視
function watch<T>(
sources: WatchSource<T>[],
callback: WatchCallback<T[]>,
options?: WatchOptions
): StopHandle
): WatchHandle

type WatchCallback<T> = (
value: T,
Expand All @@ -363,6 +409,13 @@
onTrigger?: (event: DebuggerEvent) => void
once?: boolean // 初期値: false
}

interface WatchHandle {
(): void // 呼び出し可能、`stop` と同様
pause: () => void
resume: () => void
stop: () => void
}
```

> 読みやすくするため、型は単純化されています。
Expand Down Expand Up @@ -472,6 +525,21 @@
stop()
```

ウォッチャーの一時停止 / 再開: <sup class="vt-badge" data-text="3.5+" />

```js
const { stop, pause, resume } = watchEffect(() => {})

// ウォッチャーを一時停止する
pause()

// あとで再開する
resume()

// 停止する
stop()
```

副作用のクリーンアップ:

```js
Expand All @@ -484,7 +552,45 @@
})
```

3.5+ での副作用のクリーンアップ:

```js
import { onWatcherCleanup } from 'vue'

watch(id, async (newId) => {
const { response, cancel } = doAsyncWork(newId)
onWatcherCleanup(cancel)
data.value = await response
})
```

- **参照**

- [ガイド - ウォッチャー](/guide/essentials/watchers)
- [ガイド - ウォッチャーのデバッグ](/guide/extras/reactivity-in-depth#watcher-debugging)

## onWatcherCleanup() <sup class="vt-badge" data-text="3.5+" /> {#onwatchercleanup}

現在のウォッチャーが再実行される直前に実行されるクリーンアップ関数を登録します。`watchEffect` エフェクト関数または `watch` コールバック関数の同期実行中にのみ呼び出すことができます(つまり、非同期関数内の `await` ステートメントの後には呼び出すことができません)。

- **型**

```ts
function onWatcherCleanup(
cleanupFn: () => void,
failSilently?: boolean
): void
```

- **例**

```ts
import { watch, onWatcherCleanup } from 'vue'

watch(id, (newId) => {
const { response, cancel } = doAsyncWork(newId)
// `id` が変更されると `cancel` が呼ばれ、
// 前のリクエストがまだ完了していない場合はキャンセルされます
onWatcherCleanup(cancel)
})
```
122 changes: 122 additions & 0 deletions src/guide/essentials/watchers.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,128 @@ watchEffect(async () => {

</div>

## 副作用のクリーンアップ {#side-effect-cleanup}

ウォッチャー内で、例えば非同期リクエストなど、副作用が伴う場合があります:

<div class="composition-api">

```js
watch(id, (newId) => {
fetch(`/api/${newId}`).then(() => {
// コールバックのロジック
})
})
```

</div>
<div class="options-api">

```js
export default {
watch: {
id(newId) {
fetch(`/api/${newId}`).then(() => {
// コールバックのロジック
})
}
}
}
```

</div>

しかし、リクエストが完了する前に `id` が変更されたらどうなるでしょう?前のリクエストが完了したときに、すでに古くなった ID 値でコールバックが発火してしまいます。理想的には、`id` が新しい値に変更されたときに古いリクエストをキャンセルしたいです。

[`onWatcherCleanup()`](/api/reactivity-core#onwatchercleanup) <sup class="vt-badge" data-text="3.5+" /> API を使って、ウォッチャーが無効になり再実行される直前に呼び出されるクリーンアップ関数を登録することができます:

<div class="composition-api">

```js {10-13}
import { watch, onWatcherCleanup } from 'vue'

watch(id, (newId) => {
const controller = new AbortController()

fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
// コールバックのロジック
})

onWatcherCleanup(() => {
// 古くなったリクエストを中止する
controller.abort()
})
})
```

</div>
<div class="options-api">

```js {12-15}
import { onWatcherCleanup } from 'vue'

export default {
watch: {
id(newId) {
const controller = new AbortController()

fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
// コールバックのロジック
})

onWatcherCleanup(() => {
// 古くなったリクエストを中止する
controller.abort()
})
}
}
}
```

</div>

`onWatcherCleanup` は Vue 3.5+ でのみサポートされており、`watchEffect` エフェクト関数または `watch` コールバック関数の同期実行中に呼び出す必要があることに注意してください: 非同期関数の `await` ステートメントの後に呼び出すことはできません。

代わりに、`onCleanup` 関数も第 3 引数としてウォッチャーコールバックに渡され<span class="composition-api">、`watchEffect` エフェクト関数には第 1 引数として渡され</span>ます:

<div class="composition-api">

```js
watch(id, (newId, oldId, onCleanup) => {
// ...
onCleanup(() => {
// クリーンアップのロジック
})
})

watchEffect((onCleanup) => {
// ...
onCleanup(() => {
// クリーンアップのロジック
})
})
```

</div>
<div class="options-api">

```js
export default {
watch: {
id(newId, oldId, onCleanup) {
// ...
onCleanup(() => {
// クリーンアップのロジック
})
}
}
}
```

</div>

これは 3.5 以前のバージョンで動作します。また、関数の引数で渡される `onCleanup` はウォッチャーインスタンスにバインドされるため、`onWatcherCleanup` の同期的な制約は受けません。

## コールバックが実行されるタイミング {#callback-flush-timing}

リアクティブな状態が変更されるとき、Vue コンポーネントの更新と生成されたウォッチャーコールバックを実行します。
Expand Down