watch & watchEffect 的基本用法及实现
watch
watch的基本用法
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <script type="module"> import { reactive, effect, watch} from '../../../node_modules/@vue/runtime-dom/dist/runtime-dom.esm-browser.js' const state = reactive({name: 'gzy' , address: { n : 401 }}) watch(state, (newVal, oldVal)=>{ console.log('数据变化了',newVal, oldVal) }, { flush: 'sync' }) state.name = 'ggggg' console.log('数据变化outer')
watch(()=> state.name, (newVal, oldVal)=>{ console.log('数据变化了',newVal, oldVal) }, { flush: 'sync' }) state.name = 'ggggg' console.log('数据变化outer')
</script>
|
例子中可以看出
- watch 常见用法就是监控一个函数或者响应式对象 根据返回值的变化触发对应的函数
- 监控一个响应式对象,性能会比较差,因为会访问响应式对象中的所有属性,进行取值操作
- 监控一个函数,函数执行时去访问响应对象中的某个属性
- 默认watch函数是异步执行的,传入参数 { flush:’sync’ } 表示同步执行watch
watch 的实现
watch 可以看做 effect() + scheduler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| export function watch(source, cb){
let getter; if(isReactive(source)){ getter = () => traverse(source) }else if(isFunction(source)){ getter = source } let oldVal; const effect = new ReactiveEffect(getter,()=>{ const newVal = effect.run() cb(newVal,oldVal) oldVal = newVal }) oldVal = effect.run() }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function traverse(value,seen = new Set()) { if(!isObject(value)){ return value }
if(seen.has(value)){ return value } seen.add(value) for(const key in value){ traverse(value[key],seen) } return value }
|
1 2 3 4
| export const isFunction = value => { return typeof value === 'function' }
|
watchEffect
watchEffect 基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { reactive, effect, watch, watchEffect} from '../../../node_modules/@vue/runtime-dom/dist/runtime-dom.esm-browser.js'
const state = reactive({name: 'gzy' , address: { n : 401 }})
watchEffect(()=>{ app.innerHTML = state.name }) setTimeout(() => { state.name = 'gggg' },1000)
|
watchEffect 的实现
可以发现watchEffect的使用方法和effect一样,而且和watch的区别只是是否传递了 cb , 所以我们可以对watch进行如下更改来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| export function watch(source,cb,options){ return dowatch(source,cb,options) }
export function watchEffect(source,options){ return dowatch(source,null,options) }
export function dowatch(source, cb){ let getter; if(isReactive(source)){ getter = () => traverse(source) }else if(isFunction(source)){ getter = source } let oldVal; const job = () => { if(cb){ const newVal = effect.run() cb(newVal,oldVal) oldVal = newVal }else{ effect.run() } } const effect = new ReactiveEffect(getter,job) oldVal = effect.run() }
|
- 把原来的watch修改为dowatch
- 新增对应的watch和watchEffect方法,内部调用dowatch并对参数进行区分
- dowatch内部看是否传递了cb, 传递了获取新的值和老的值传递给cb,没有传递说明是watchEffect,只需要运行自身就可以了
cleanup
下一次watch函数执行前清理掉上一次的watch
1 2 3 4 5 6 7 8 9 10 11 12 13
| const state = reactive({name: 'gzy' , address: { n : 401 },age:1})
watch(()=> state.name, (newVal, oldVal, onCleanup)=>{ let flag = true onCleanup(function(){ flag = false }) flag && (app.innerHTML = r) }, { flush: 'sync' }) state.age = '11' state.age = '111' state.age = '1111'
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export function dowatch(source, cb){ let clear let onCleanup = (fn) => { clear = fn } const job = () => { if(cb){ const newVal = effect.run() cb(newVal,oldVal,onCleanup) oldVal = newVal }else{ effect.run() } } }
|