From 7dbbbc921cba8a63b4ecd2537b4f04207e2427fe Mon Sep 17 00:00:00 2001 From: reona5 Date: Wed, 4 Sep 2024 22:52:48 +0900 Subject: [PATCH 1/2] docs: Update watcher side effect cleanup + WatchHandle pause / resume --- src/api/reactivity-core.md | 114 ++++++++++++++++++++++++++++- src/guide/essentials/watchers.md | 122 +++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 4 deletions(-) diff --git a/src/api/reactivity-core.md b/src/api/reactivity-core.md index ee31dd60d..9c16a919f 100644 --- a/src/api/reactivity-core.md +++ b/src/api/reactivity-core.md @@ -238,7 +238,7 @@ function watchEffect( effect: (onCleanup: OnCleanup) => void, options?: WatchEffectOptions - ): StopHandle + ): WatchHandle type OnCleanup = (cleanupFn: () => void) => void @@ -248,7 +248,12 @@ onTrigger?: (event: DebuggerEvent) => void } - type StopHandle = () => void + interface WatchHandle { + (): void // 呼び出し可能、`stop` と同様 + pause: () => void + resume: () => void + stop: () => void + } ``` - **詳細** @@ -295,6 +300,47 @@ stop() ``` + ウォッチャーの一時停止 / 再開: + + ```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 @@ -333,14 +379,14 @@ source: WatchSource, callback: WatchCallback, options?: WatchOptions - ): StopHandle + ): WatchHandle // 複数ソースの監視 function watch( sources: WatchSource[], callback: WatchCallback, options?: WatchOptions - ): StopHandle + ): WatchHandle type WatchCallback = ( value: T, @@ -363,6 +409,13 @@ onTrigger?: (event: DebuggerEvent) => void once?: boolean // 初期値: false } + + interface WatchHandle { + (): void // 呼び出し可能、`stop` と同様 + pause: () => void + resume: () => void + stop: () => void + } ``` > 読みやすくするため、型は単純化されています。 @@ -472,6 +525,21 @@ stop() ``` + ウォッチャーの一時停止 / 再開: + + ```js + const { stop, pause, resume } = watchEffect(() => {}) + + // ウォッチャーを一時停止する + pause() + + // あとで再開する + resume() + + // 停止する + stop() + ``` + 副作用のクリーンアップ: ```js @@ -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() {#onwatchercleanup} + +現在のクリーンアップ関数が再実行される直前に実行されるクリーンアップ関数を登録します。`watchEffect` エフェクト関数または `watch` コールバック関数の同期実行中にのみ呼び出すことができます(つまり、非同期関数内の `await` ステートメントの後には呼び出すことができません)。 + +- **Type** + + ```ts + function onWatcherCleanup( + cleanupFn: () => void, + failSilently?: boolean + ): void + ``` + +- **Example** + + ```ts + import { watch, onWatcherCleanup } from 'vue' + + watch(id, (newId) => { + const { response, cancel } = doAsyncWork(newId) + // `id` が変更されると `cancel` が呼ばれ、 + // 前のリクエストがまだ完了していない場合はキャンセルされます + onWatcherCleanup(cancel) + }) + ``` diff --git a/src/guide/essentials/watchers.md b/src/guide/essentials/watchers.md index ee0c68f7c..0eaebc08d 100644 --- a/src/guide/essentials/watchers.md +++ b/src/guide/essentials/watchers.md @@ -362,6 +362,128 @@ watchEffect(async () => { +## 副作用のクリーンアップ {#side-effect-cleanup} + +ウォッチャー内で、例えば非同期リクエストなど、副作用が伴う場合があります: + +
+ +```js +watch(id, (newId) => { + fetch(`/api/${newId}`).then(() => { + // コールバックのロジック + }) +}) +``` + +
+
+ +```js +export default { + watch: { + id(newId) { + fetch(`/api/${newId}`).then(() => { + // コールバックのロジック + }) + } + } +} +``` + +
+ +しかし、リクエストが完了する前に `id` が変更されたらどうなるでしょう?前のリクエストが完了したときに、すでに古くなった ID 値でコールバックが発火してしまいます。理想的には、`id` が新しい値に変更されたときに古いリクエストをキャンセルしたいです。 + +[`onWatcherCleanup()`](/api/reactivity-core#onwatchercleanup) 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() + }) +}) +``` + +
+
+ +```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() + }) + } + } +} +``` + +
+ +`onWatcherCleanup` は Vue 3.5+ でのみサポートされており、`watchEffect` エフェクト関数または `watch` コールバック関数の同期実行中に呼び出す必要があることに注意してください: 非同期関数の `await` ステートメントの後に呼び出すことはできません。 + +代わりに、`onCleanup` 関数も第 3 引数としてウォッチャーコールバックに渡され、`watchEffect` エフェクト関数には第 1 引数として渡されます: + +
+ +```js +watch(id, (newId, oldId, onCleanup) => { + // ... + onCleanup(() => { + // クリーンアップのロジック + }) +}) + +watchEffect((onCleanup) => { + // ... + onCleanup(() => { + // クリーンアップのロジック + }) +}) +``` + +
+
+ +```js +export default { + watch: { + id(newId, oldId, onCleanup) { + // ... + onCleanup(() => { + // クリーンアップのロジック + }) + } + } +} +``` + +
+ +これは 3.5 以前のバージョンで動作します。また、関数の引数で渡される `onCleanup` はウォッチャーインスタンスにバインドされるため、`onWatcherCleanup` の同期的な制約は受けません。 + ## コールバックが実行されるタイミング {#callback-flush-timing} リアクティブな状態が変更されるとき、Vue コンポーネントの更新と生成されたウォッチャーコールバックを実行します。 From 9ecf04b5496a33e00f46599762bab25e67922bea Mon Sep 17 00:00:00 2001 From: reona5 Date: Thu, 5 Sep 2024 10:04:21 +0900 Subject: [PATCH 2/2] fix: Correct for accurate translation --- src/api/reactivity-core.md | 6 +++--- src/guide/essentials/watchers.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/reactivity-core.md b/src/api/reactivity-core.md index 9c16a919f..1bfccbb53 100644 --- a/src/api/reactivity-core.md +++ b/src/api/reactivity-core.md @@ -571,9 +571,9 @@ ## onWatcherCleanup() {#onwatchercleanup} -現在のクリーンアップ関数が再実行される直前に実行されるクリーンアップ関数を登録します。`watchEffect` エフェクト関数または `watch` コールバック関数の同期実行中にのみ呼び出すことができます(つまり、非同期関数内の `await` ステートメントの後には呼び出すことができません)。 +現在のウォッチャーが再実行される直前に実行されるクリーンアップ関数を登録します。`watchEffect` エフェクト関数または `watch` コールバック関数の同期実行中にのみ呼び出すことができます(つまり、非同期関数内の `await` ステートメントの後には呼び出すことができません)。 -- **Type** +- **型** ```ts function onWatcherCleanup( @@ -582,7 +582,7 @@ ): void ``` -- **Example** +- **例** ```ts import { watch, onWatcherCleanup } from 'vue' diff --git a/src/guide/essentials/watchers.md b/src/guide/essentials/watchers.md index 0eaebc08d..f01fa1e09 100644 --- a/src/guide/essentials/watchers.md +++ b/src/guide/essentials/watchers.md @@ -444,7 +444,7 @@ export default { `onWatcherCleanup` は Vue 3.5+ でのみサポートされており、`watchEffect` エフェクト関数または `watch` コールバック関数の同期実行中に呼び出す必要があることに注意してください: 非同期関数の `await` ステートメントの後に呼び出すことはできません。 -代わりに、`onCleanup` 関数も第 3 引数としてウォッチャーコールバックに渡され、`watchEffect` エフェクト関数には第 1 引数として渡されます: +代わりに、`onCleanup` 関数も第 3 引数としてウォッチャーコールバックに渡され、`watchEffect` エフェクト関数には第 1 引数として渡されます: