目录
副作用(Side Effect)
纯粹计算背后的效果【我们做UI时非常需要】
副作用的封装(伪代码)
副作用失效(Invalidate)问题
API:WatchEffect
Coding:WatchEffect【演示代码】
/src/examples/WatchExamplese.tsx【watchEffect】
API:Watch
Coding:Watch
/src/examples/WatchExamplese.tsx【watch】
小结
副作用(Side Effect)
- 副作用是计算之外的行为
- view = f(props)with effect[]
function SomeComponent(a, b) {window.location.href = '...' // 副作用const c = ref(0) // 副作用watchEffect(..) // 副作用return <div>{a+b+c.value}</div>
}
纯粹计算背后的效果【我们做UI时非常需要】
- 点击Tab时,切换了`activeKey`,同时改变当前url(history.replace)
- 删除文件时操作目录结构,同时写入磁盘
副作用的封装(伪代码)
根据数据变化,触发副作用
function HomePage {watch(activeKey, () => {history.repace(...) })return () => {return <tabs>...</tabs> }
}
副作用失效(Invalidate)问题
- 快速切换页面的场景
const currentIndex = ref("")
// 点击打开A
currentIndex.value = "A"
// 点击打开B
currentIndex.value = "B"
// A还没打开,就继续执行打开B,这时候就是A副作用失效
API:WatchEffect
- 目标:监听副作用,并封装副作用
- 监听哪些副作用?
- reactive值变化(track,tigger)
- 页面渲染
- watchEffect会依赖用到的reactive值(类似渲染函数)
Coding:WatchEffect【演示代码】
- 常规用法
- watchEffect的依赖收集
- 执行时机
- 副作用失效问题
/src/examples/WatchExamplese.tsx【watchEffect】
import { ref, defineComponent, watchEffect, onUnmounted } from 'vue'
export const WatchExample01 = defineComponent({setup() {// 常规用法const count = ref(0)// effect (效果)?? --副作用(side effect)// 提供副作用,封装副作用,也在依赖副作用,会先执行一次,收集依赖watchEffect(() => { // 副作用中用到了,才能追踪到// watchEffect的依赖收集console.log('im running...') // 单写打印,不会执行,因为没有依赖副作用,追踪不到// console.log('im running...', count.value) // 会执行,因为有依赖document.title = "count:" + count.value // 会执行,因为有依赖// history.replaceState({}, "", "/count/" + count.value)})return () => {return <div><button onClick={() => {count.value++}}>+</button>{count.value}</div>}}
})
export const WatchExample02 = defineComponent({setup() {// 执行时机const count = ref(0)// effect (效果)?? --副作用(side effect)// 提供副作用,封装副作用,也在依赖副作用,会先执行一次,收集依赖watchEffect(() => { // 副作用中用到了,才能追踪到console.log('im running...') // 单写打印,不会执行,因为没有依赖副作用,追踪不到// console.log('im running...', count.value) // 会执行,因为有依赖// document.title = "count:" + count.value // 会执行,因为有依赖// history.replaceState({}, "", "/count/" + count.value)}, {flush: "sync" // post 后于render执行; pre 先于render执行; sync先于render执行})// 执行时间线 sync【同步依赖变化第一时间执行】
// ... tick【vue闲时执行】 ... pre ... render ... postreturn () => {console.log('render...')return <div><button onClick={() => {count.value++}}>+</button>{count.value}</div>}}
})
export const WatchExample03 = defineComponent({setup() {// 副作用失效问题const count = ref(0)// effect (效果)?? --副作用(side effect)// 提供副作用,封装副作用,也在依赖副作用,会先执行一次,收集依赖watchEffect((onInvalidate) => { // 副作用中用到了,才能追踪到console.log(count.value)let I = setInterval(() => {count.value++}, 1000)// onInvalidate第二次执行时会清除前一次的I,关于副作用失效的问题onInvalidate(() => {// 如果没有它的话,会越来越快clearInterval(I)})// console.log('im running...') // 单写打印,不会执行,因为没有依赖副作用,追踪不到})// 执行时间线 sync【同步依赖变化第一时间执行】 ... tick【vue闲时执行】 ... pre ... render ... postreturn () => {return <div>{count.value}</div>}}
})
export const WatchExample04 = defineComponent({setup() {// 副作用失效问题const count = ref(0)let I = setInterval(() => {count.value++}, 1000)onUnmounted(() => { // 组件卸载时候执行的钩子clearInterval(I)})// effect (效果)?? --副作用(side effect)// 提供副作用,封装副作用,也在依赖副作用,会先执行一次,收集依赖// const stop = watchEffect((onInvalidate) => { // 副作用中用到了,才能追踪到// console.log(count.value)// let I = setInterval(() => {// count.value++// }, 1000)// // onInvalidate第二次执行时会清除前一次的I,关于副作用失效的问题// onInvalidate(() => {// 如果没有它的话,会越来越快// clearInterval(I)// })// // console.log('im running...') // 单写打印,不会执行,因为没有依赖副作用,追踪不到// })// stop() // 只执行一次// 执行时间线 sync【同步依赖变化第一时间执行】 ... tick【vue闲时执行】 ... pre ... render ... postreturn () => {return <div>{count.value}</div>}}
})
API:Watch
- 目标:监听某个reactive值的副作用/监听某个属性变化
- 有针对性的监听语义更明确(推荐不用watchEffect)
Coding:Watch
- 监听某个reactive值,封装副作用
- 监听属性变化。依赖问题
- 执行时机的问题
/src/examples/WatchExamplese.tsx【watch】
import { ref, defineComponent, watch } from 'vue'export const WatchExample05 = defineComponent({// 监听某个reactive值,封装副作用setup() {const count = ref(0)// 指定响应式值的监听,不需要如watchEffect开始执行一次进行依赖收集watch(count, () => {console.log('count', count)})setTimeout(() => {count.value++}, 1000)return () => <div>{count.value}</div>}
})
export const WatchExample06 = defineComponent({setup() {const a = ref(0)const b = ref(0)// const c = ref(a.value + b.value)watch([a, b], (x, y) => {// c.value = a.value + b.value // 第二种写法// x是旧值,y是新值console.log(x, y)})setInterval(() => {a.value += 0.2}, 500)setInterval(() => {b.value += 0.7}, 500)return () => <div>{a.value + b.value}{/* {c.value} */}</div>}
})
export const WatchExample07 = defineComponent({// 监听属性变化。依赖问题setup() {const greetings = ref("hello")setTimeout(() => {greetings.value = "world!"}, 1000)return () => {return <div><Item text={greetings.value} /></div>}}
})
// const Item = ({ text }: {
// text: string
// }) => {
// return <div>{text}</div>
// }
// 需要text变化时进行计算
const Item = defineComponent({props: {text: {type: String}},setup(props) {// 子组件监听属性变化// const textRef = ref(props.text) // watch(textRef, ()=>{不执行,setup只执行一次})// let x = 1// watch(()=> x, ()=>{不触发,不是响应式值,vue不知道怎么触发})watch(() => props.text, (to, from) => {console.log("prop changed to", to, "old value is", from)})// setTimeout(() => {// x = 2// }, 1000)return () => {return <div>{props.text}</div>}}
})
export const WatchExample08 = defineComponent({// 执行时机的问题setup() {const c = ref(0)watch(c, () => { // 存在调度时间,会延迟一个周期执行console.log("herep---------")}, {"immediate": true // 通常不会设置// 设置为true,表示c变化之后,马上执行,例如c是某个事件的开关})return () => {return <div onClick={() => {c.value++}}>{c.value}</div>}}
})
小结
- 为什么不推荐用watchEffect?watch语法很有针对性,明确指出响应式值,减少耦合
- 如何理解watch依赖副作用又触发副作用?watch监听响应式值变动,执行我们写的副作用,依赖的是vue这个渲染引擎,页面内容渲染也是副作用