`watchEffect ` Should not runs a function immediately,please add to the queued runs by onBeforeUpdate
Version
3.2.18
Reproduction link
Steps to reproduce
const obj = reactive({})
const foo = ref()
Vue.watchEffect(() => {
foo.value = obj.deep.foo // error
})
obj.deep = {foo:'foo'}
What is expected?
副作用函数不应该立即执行,而应该挂到列队在onBeforeUpdate中统一执行,避免因传入的数据还未创建完成出现错误,或者重复执行。
What is actually happening?
现在只能将 watchEffect 放到 onMounted 勾子中执行才能避免问题
js Vue.onMounted(() => { Vue.watchEffect(() => { foo.value = obj.deep.foo // no problem }) })
即使不出现上面错误,例如:
const obj = Vue.reactive({deep:{foo:''})
const foo = Vue.ref()
Vue.watchEffect(() => {
foo.value = obj.deep.foo // double run
})
obj.deep = {foo:'foo'} // fired effect
也会造成副作用函数执行两次,而且这本就是注册一个监听勾子,可是却立即执行,不是很好理解! 虽然有watchPostEffect, 但执行时机是在更新后, 比如以下情况
const boo = ref()
const bar = ref()
watchPostEffect(() => {
bar.value = boo.value ? 'some' : 'none'
})
const thd = computed(() => {
return 'hello,' + (boo.value ? bar.value : 'world')
})
当boo 变化时 computed会执行,更新后才会触发bar变化,然后又会使computed勾子执行。所这这也不能解决问题!
It runs immediately once because it needs to collect reactive dependencies. then it will adhere to the flush
setting.
The "double run" is the intended behavior by our design.
first run in the onBeforeUpdate , Still can be collect dependencies
Running twice of create stage doesn't make sense
Running twice of create stage doesn't make sense
That totally depends on your use case, though.
- You might want to have the effect run immediately, maybe it triggers an API call you want to have going ASAP.
- you might want to have it run again if a reactive dependency did change before mounted, which might or might be changed depending on other state/effects in your component.
The way things are now, you can guard yourself against a double update with a flag like:
const isBeforeMount = ref(false)
onBeforemount(() => isBeforeMount.value = true)
watchEffect(() => {
if (isBeforeMount.value) {
//implementation.
}
})
or maybe you have a situation where you want the effect to be run after the render cycle with flush: 'post'
?
in my super form, user can be configure, example:
[
{
type: 'Input',
prop: 'name',
label: '姓名',
},
{
type: 'Input',
prop: 'age',
label: '年龄',
initialValue: 20,
},
{
type: 'Input',
prop: 'idNumber',
label: '证件号',
disabled:(formData) => formData.age<16
},
{
type:'group',
prop: 'group', // formData.group.
items:[...]
]
this formData
is build in create stage,
function build(option, parentData, formData) {
// parentData is reactive(formData[anyname]) , formData is reactivity
parentData[option.prop] = option.initalValue
const disabledRef = ref(!option.disabled)
if (typeof option.disabled === 'function') {
watchEffect(() => {
disabledRef.value = !option.disabled(formData)
})
}
// ...
}
if effect run immediately, formData is not ready yet
Maybe something like:
function build(option, parentData, formData) {
// parentData is reactive(formData[anyname]) , formData is reactivity
parentData[option.prop] = option.initalValue
const disabledRef = ref(!option.disabled)
if (typeof option.disabled === 'function') {
onBeforeMount(() => watchEffect(() => {
disabledRef.value = !option.disabled(formData)
}))
}
// ...
}
... or use watch()
instead of watchEffect()
, for example.
Point being: There are other ways to achieve what you want, and we won't do a breaking change in the behavior of watchEffect