>[success] # watchEffect
1. `watchEffect `是一個新的`api`,需要將這個單詞拆解來看`watch `和`effect `,**effect全稱叫side effect,副作用**。在watch里面進行異步請求,這個請求是隨時的不可控的這個就是一種副作用,所以可以理解成`watchEffect`的意思就是在觀察(watch)到變化后執行一些操作(effect)
* `onTrack `和 `onTrigger `只能在開發模式下工作。可用于調試偵聽器的行為。`onTrack`作為依賴項被追蹤時被調用。`onTrigger `將在依賴項變更導致副作用被觸發時被調用。
* `flush : 'pre' | 'post' | 'sync' `默認是`pre`,`pre` 會在 Vue 組件更新**之前**被調用。這意味著你在偵聽器回調中訪問的 DOM 將是被 Vue 更新之前的狀態,`post`,在偵聽器回調中能訪問被 Vue 更新**之后**的 DOM,`sync` 是每一步都會觸發(實際vue 為了防止使用隊列緩沖回調。回調只會添加到隊列中一次,即使所監視的值更改了多次。中間值將被跳過,并且不會傳遞給回調,但`sync`是次次觸發) ,具體可[查看的案例參考](https://github.com/vuejs/core/issues/5721)
~~~
watchEffect(() => {}, {
flush: 'post',
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
~~~
>[danger] ##### 特點
1. `watchEffect` 不需要指定監聽的屬性,監聽是立即執行的(**會默認初始時就會執行第一次, 從而可以收集需要監聽的數據**)
2. 調用返回值以停止偵聽
3. 傳入`watchEffect `函數可以接收一個 `onInvalidate `函數作入參,用來注冊清理失效時的回調,失效回調會被觸發
條件:
* 副作用即將重新執行時
* 偵聽器被停止 (如果在 setup() 或生命周期鉤子函數中使用了 watchEffect,則在組件卸載時)
4. 調整`watchEffect`的執行時機,`watchEffect`立即執行時候`dom `還沒被渲染,可以調整執行時機來獲取`dom`
* 沒有任何指定的屬性依然會執行下面案例運行后控制臺打印1,`watchEffect `需要先立即執行收集內部需要**被監聽的數據**
~~~
<template>
<div>
</div>
</template>
<script lang="ts">
import {ref, watchEffect} from 'vue'
export default {
setup() {
const stop = watchEffect(()=>{
console.log(1)
})
return {
}
}
}
</script>
~~~
* **自動只能收集是屬性**雖然會自動收集監聽但是,如果是一個`reactive `對象是不會觸發,**但是如果是其一個具體屬性值可以觸發**
第立即執行時候都執行,但第二次改變時候`info`的`watchEffect `沒觸發

~~~
<template>
<button @click="changeInfo">change</button>
</template>
<script>
import { watchEffect, ref, reactive } from '@vue/runtime-core'
export default {
name: 'App',
setup() {
const test = ref(1)
const info = reactive({ name: 'w', hobbies: ['footer'] })
const changeTest = () => {}
const changeInfo = () => {
info.hobbies[0] = 'sleep'
test.value = 12
}
watchEffect(() => {
console.log(info)
})
watchEffect(() => {
console.log(info.hobbies[0])
})
watchEffect(() => {
console.log(test.value)
})
return {
test,
info,
changeInfo,
changeTest,
}
},
}
</script>
<style></style>
~~~
>[danger] ##### 簡單案例
1. 下面代碼執行結果先打印**1 0 "w" ** 在一秒后打印 **8 1 10 "z"** ,因為`watchEffect `是立即執行因此先打印一次,在第一次立即執行的時候收集了需要監聽的響應屬性,在定時器1s后執行改變了監聽的響應數據,因此`watchEffect `**被觸發**
~~~
<template>
<div>
</div>
</template>
<script lang="ts">
import {ref, watchEffect} from 'vue'
export default {
setup() {
const count = ref(0)
const str = ref('w')
const stop = watchEffect(()=>{
console.log(1,count.value,str.value)
count.value = 100
})
setTimeout(()=>{
count.value = 10
str.value = "z"
},1000)
return {
count
}
}
}
</script>
~~~
>[danger] ##### 調用返回值以停止偵聽
1. 在一些情況下,也可以顯式調用返回值以停止偵聽,下面案例只打印了**1 0 "w"** 在一秒后并沒有打印 **8 1 10 "z"**原因手動調用了**停止響應式監聽**
~~~
<template>
<div>
</div>
</template>
<script lang="ts">
import {ref, watchEffect} from 'vue'
export default {
setup() {
const count = ref(0)
const str = ref('w')
const stop = watchEffect(()=>{
console.log(1,count.value,str.value)
count.value = 100
})
// 停止了響應式監聽
stop()
setTimeout(()=>{
count.value = 10
str.value = "z"
},1000)
return {
count
}
}
}
</script>
~~~
>[danger] ##### 清除副作用
1. 回調函數還提供了參數,例如下面`onInvalidate `在沒有放開下面兩條注釋條件任意一個的時候`onInvalidate `不會執行,但是放開其中任意一個條件`onInvalidate `都將會執行,說明參數會在觸發監聽屬性改變前執行即,如果放開下面1s后執行的注釋控制臺打印的結果:
~~~
1 0 "w"
執行回調
1 10 "z"
~~~
所以`onInvalidate `觸發跟你放的順序無關
~~~
<template>
<div>
</div>
</template>
<script lang="ts">
import {ref, watchEffect} from 'vue'
export default {
setup() {
const count = ref(0)
const str = ref('w')
const stop = watchEffect((onInvalidate )=>{
console.log(1,count.value,str.value)
onInvalidate(()=>{
console.log('執行回調')
})
})
// 停止了響應式監聽 --偵聽器被停止觸發回調
// stop()
// ----------------------
// setTimeout(()=>{ // 副作用即將重新執行時
// count.value = 10
// str.value = "z"
// },1000)
return {
count
}
}
}
</script>
~~~
* 官網給了一個案例參考
1. 當請求 id 改變時候,請求接口,但是id 是高頻率變化,希望每次變化如果上次接口數據沒請求完則銷毀
~~~
watchEffect(onInvalidate => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id has changed or watcher is stopped.
// invalidate previously pending async operation
token.cancel()
})
})
~~~
>[danger] ##### 關于停止監聽說明
1. 除了手動觸發 停止監聽,實際同步語句創建的偵聽器會自動綁定到宿主組件實例上,并且會在宿主組件卸載時自動停止,所以不用擔心如果頁面已經銷毀了監聽還在內存中。
2. 但如果是異步創建 ,必須手動監聽例如下面例子
~~~
<script setup>
import { watchEffect } from 'vue'
// 它會自動停止
watchEffect(() => {})
// ...這個則不會!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
~~~
>[danger] ##### 雖然監聽是立即執行的但可控
1. 監聽雖然立即執行但是可以使用**條件式的偵聽邏輯** 來控制內部代碼
~~~
// 需要異步請求得到的數據
const data = ref(null)
watchEffect(() => {
if (data.value) {
// 數據加載后執行某些操作...
}
})
~~~
>[danger] ##### 渲染 是獲取不到dom
1. 默認情況下,用戶創建的偵聽器回調,都會在 Vue 組件更新之前被調用。這意味著你在偵聽器回調中訪問的DOM 將是被 Vue 更新之前的狀態
~~~
<template>
<div ref="test"></div>
</template>
<script>
import { watchEffect, ref } from '@vue/runtime-core'
export default {
name: 'App',
setup() {
const test = ref(null)
watchEffect(() => {
console.log(test.value)
})
return {
test,
}
},
}
</script>
<style></style>
~~~
* 如圖
~~~
1第一次執行時候dom 還沒掛載此時獲取為null,當掛載上后再次觸發此時為dom元素
~~~

* 解決上面方法
~~~
watchEffect(()=>{.....}, {
flush: 'post'
})
或者
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* 在 Vue 更新后執行 */
})
兩個都是在vue更新后執行此時第一次就能獲取dom
~~~
>[success] # watchPostEffect()[](https://cn.vuejs.org/api/reactivity-core.html#watchposteffect)
[`watchEffect()`](https://cn.vuejs.org/api/reactivity-core.html#watcheffect)使用`flush: 'post'`選項時的別名。
>[success] # watchSyncEffect()[](https://cn.vuejs.org/api/reactivity-core.html#watchsynceffect)
[`watchEffect()`](https://cn.vuejs.org/api/reactivity-core.html#watcheffect)使用`flush: 'sync'`選項時的別名。
>[info] ## 官網參考
[## watchEffect](https://cn.vuejs.org/api/reactivity-core.html#readonly)
[## `watchEffect()`](https://cn.vuejs.org/guide/essentials/watchers.html#watcheffect)
- 官網給的工具
- 聲明vue2 和 vue3
- 指令速覽
- Mustache -- 語法
- v-once -- 只渲染一次
- v-text -- 插入文本
- v-html -- 渲染html
- v-pre -- 顯示原始的Mustache標簽
- v-cloak -- 遮蓋
- v-memo(新)-- 緩存指定值
- v-if/v-show -- 條件渲染
- v-for -- 循環
- v-bind -- 知識
- v-bind -- 修飾符
- v-on -- 點擊事件
- v-model -- 雙向綁定
- 其他基礎知識速覽
- 快速使用
- 常識知識點
- key -- 作用 (后續要更新)
- computed -- 計算屬性
- watch -- 偵聽
- 防抖和節流
- vue3 -- 生命周期
- vue-cli 和 vite 項目搭建方法
- vite -- 導入動態圖片
- 組件
- 單文件組件 -- SFC
- 組件通信 -- porp
- 組件通信 -- $emit
- 組件通信 -- Provide / Inject
- 組件通信 -- 全局事件總線mitt庫
- 插槽 -- slot
- 整體使用案例
- 動態組件 -- is
- keep-alive
- 分包 -- 異步組價
- mixin -- 混入
- v-model-- 組件
- 使用計算屬性
- v-model -- 自定義修飾符
- Suspense -- 實驗屬性
- Teleport -- 指定掛載
- 組件實例 -- $ 屬性
- Option API VS Composition API
- Setup -- 組合API 入口
- api -- reactive
- api -- ref
- 使用ref 和 reactive 場景
- api -- toRefs 和 toRef
- api -- readonly
- 判斷性 -- API
- 功能性 -- API
- api -- computed
- api -- $ref 使用
- api -- 生命周期
- Provide 和 Inject
- watch
- watchEffect
- watch vs. watchEffect
- 簡單使用composition Api
- 響應性語法糖
- css -- 功能
- 修改css -- :deep() 和 var
- Vue3.2 -- 語法
- ts -- vscode 配置
- attrs/emit/props/expose/slots -- 使用
- props -- defineProps
- props -- defineProps Ts
- emit -- defineEmits
- emit -- defineEmits Ts
- $ref -- defineExpose
- slots/attrs -- useSlots() 和 useAttrs()
- 自定義指令
- Vue -- 插件
- Vue2.x 和 Vue3.x 不同點
- $children -- 移除
- v-for 和 ref
- attribute 強制行為
- 按鍵修飾符
- v-if 和 v-for 優先級
- 組件使用 v-model -- 非兼容
- 組件
- h -- 函數
- jsx -- 編寫
- Vue -- Router
- 了解路由和vue搭配
- vueRouter -- 簡單實現
- 安裝即使用
- 路由懶加載
- router-view
- router-link
- 路由匹配規則
- 404 頁面配置
- 路由嵌套
- 路由組件傳參
- 路由重定向和別名
- 路由跳轉方法
- 命名路由
- 命名視圖
- Composition API
- 路由守衛
- 路由元信息
- 路由其他方法 -- 添加/刪除/獲取
- 服務器配置映射
- 其他
- Vuex -- 狀態管理
- Option Api -- VUEX
- composition API -- VUEX
- module -- VUEX
- 刷新后vuex 數據同步
- 小技巧
- Pinia -- 狀態管理
- 開始使用
- pinia -- state
- pinia -- getter
- pinia -- action
- pinia -- 插件 ??
- Vue 源碼解讀
- 開發感悟
- 練手項目