我相信许多同学都会下意识写出以下代码:
//vue
watch(formModel, (reqData, _, invalidate) => {
const handle = request(reqData).then(() => {
// ... some logic
})
invalidate(() => {
handle.cancel
})
})
对啊,监听数据,执行副作用,然后在组件无效时,清除副作用
别说这个没问题,甚至有没有写这个代码的习惯,都会被用来鉴定开发水平,更有甚者,promise 取消回调的问题,都是一个面试考点
但是如果我说这个逻辑是错的呢?来看看下面一段加了点新逻辑的情况:
//vue
const parentData = inject('parentData')
watch(formModel, (reqData, _, invalidate) => {
const handle = request(reqData).then(() => {
// ... some local logic
parentData.requestCount ++
})
invalidate(() => {
handle.cancel()
})
})
请求回调之后,将注入数据的requestCount+1
请问,逻辑上,请求完成没有?
完成了
但是为什么要取消请求的回调?上其他组件拿到错误的数据呢?
因为……组件没了?
组件销毁之后,取消请求回调,是产品经理告诉你的逻辑么?
不是……,是程序运行逼着你写的逻辑
那这就是 —— 废逻辑!
出现这些问题的原因是什么呢?
事件,只能在源头处取消,不能靠取消回调掩耳盗铃!
clearTimeout, removeEventlistener 正确,并且:
// vue
let end = false
watchEffect(()=>{
// 闭包可以访问
/*
* if(end) clearTimeout(timer)
* 错误,阻碍逻辑运行
*/
// ...some logic
if(end) clearTimeout(timer)
// 正确,逻辑得以运行
})
onBeforeUnmount(()=>{
end = true
})
当然,直接用框架提供的方式更好,这里只是说明一下逻辑:
// vue
watchEffect(invalidate=>{
invalidate(()=>{
clearTimeout(timer)
})
})
一句话重申一下:
对于异步事件来说:泼出去的水,已经泼出去了!
不能因为组件销毁与否,阻碍逻辑正常运行
只能取消事件监听
不可取消事件回调
视图逻辑解耦
问题的根源:
那你说不对,如果回调不取消,产生一些意外变更,怎么办?
你的意外变更
只有可能是 生命周期相关的逻辑
而现在,需要你忘掉生命周期
不要利用生命周期触发业务逻辑
不用生命周期?
是的,可以试试 React,完全没有生命周期,一样可以开发:
// react
function SomeCompo(){
useEffect(()=>{
// 这不是生命周期回调,只是一个当前组件自有事件的初始化发生器
// 依赖数组为空,代表这个视图被创建,即会执行的逻辑
// 虽然可以等价生命周期,但是不建议这么做,在这里引用任何状态都会报错(未加入依赖数组)
},[])
useEffect(()=>{
// useEffect 只代表一系列值跟随另一一列值变化(变化的函数关系)
setOtherDataChangeAfterData('dataChanged')
},[data])
}
这个和上一代前端框架的开发模式差距太大(使用 ng+rx 的同学可能比较适应)
难道上一代前端框架利用生命周期无法实现某些逻辑么?
是的,很多逻辑还真的实现不了:
// vue
onMounted(()=>{
request(someData).then((res)=>{
localData.value = res
})
})
这是一个简单的,发送请求的逻辑,咋一看好像没什么问题
我们来跨组件一下:
// vue
// parent
let base = ref(null)
requestBaseData((res)=>{
base.value = res
})
provide('parentBase', {base})
// child
const {base} = inject('parentBase')
onMounted(()=>{
request(base.value).then((res)=>{
localData.value = res
})
})
子组件请求时,base 是 null!
mounted 发请求?初始化?显然在跨组件逻辑下,是绝对会扑街的!
因为子组件的请求参数,对父组件的数据产生了依赖!
应该的写法是:
// vue
// child
const {base} = inject('parentBase')
watch(base, (res)=>{
request(base.value).then((res)=>{
localData.value = res
}),
{
immediate: false // 默认 false 避开 base 默认值
}
})
那用 mounted 绑定 ref 呢?
一个例子击败你:
// vue
<template>
<div ref="someRef" v-if="base"/>
</template>
ref 一样会产生依赖!
不过,一样可以通过响应式回调解决问题:
// vue
const {base} = inject('parentBase')
const someRef = ref(null)
watch(base,(base)=>{
if(!base) return
console.log(someRef.value)
},{
flush: 'post' // flush post 为视图变化回调
})
// react
const {base} = useContext(ParentBase)
const someRef = useRef(null)
// useLayoutEffect 而不是 useEffect
useLayoutEffect(()=>{
if(!base) return
console.log(someRef.current)
},[base, someRef])
// angular
@ViewChild('someRef')
someRef: EelementRef
constructor(parentBase){
parentBase$
.pipe(
filter(res=>res),
// ng 的变检由事件驱动(zone会代理所有浏览器事件,promise等),与流同步进行
// 变更会在下个事件循环完成
// delay(0)即可获取相应视图
delay(0)
).subscribe(()=>{
console.log(someRef.el)
})
}
绕过所有生命周期,所有变更根据业务逻辑和状态触发
应用就会变成这个样子:
同时,这也是我说为什么 MVVM 已死的原因
不是说 MVVM 这个模式已死,而是说 ——
现在这个时代,再用生命周期,你就输了~
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!