-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unnecessary rendering updates when props change #1790
Comments
Have you done a benchmark to see the difference between |
@posva sorry to chime in but I think the OP's point is that the render function could involve more significant changes than in the demo. I'm curious as to how optimised things are underneath ie. the render function is called but vue doesn't actually do anything since the template doesn't need updating. Is a call to the render function when props change by design and the render itself optimised by vue internally? |
See #1181 (comment) |
@underfin thanks, I knew we had the explanation of correctness somewhere |
@underfin thanks for clarifying. Seems a behavioural change from vue2, is it mentioned in the docs? |
Much of the discussion on #1181 seems to be about The discussion of
I can think of several use cases for
Evan also comments:
I wholeheartedly agree. Perhaps avoiding the extra rendering isn't worth it in this case. I'm unclear precisely what changes would be required to check which props are used during rendering, so I'm not in a position to evaluate the trade-offs. However, that evaluation needs to be made with the understanding that use cases for 'unused' props do exist in real applications. |
I read @yyx990803 comments in #1181, but I just don't get it. He says correctness is more important than optimizations and I wholly agree with that. If a specific prop is not a dependency of rendering (there can be many good reasons for that), I don't see what issue could arise in not rendering again when said prop changes. It looks like added complexity to me: somehow those unread dependencies (props) must be added to the effect, somehow.
|
I'm gonna add one more reason to be very careful here! |
This change actually introduces a lot of pain when working with renderless provider components which are still viable in Vue 3. Provider components generally wrap your whole app and do not render anything other than the default slot. If I understand this correctly updating provider would cause a whole app VNode tree to re-render. <ProviderFoo>
<ProviderBar>
<App />
</ProviderBar>
</ProviderFoo> export default {
name: 'App',
inject: ['providedByFoo'],
mounted() {
this.providedByFoo.value = 1;
// whole app re-rendered
},
} |
@CyberAP I don't think your example would be affected as it doesn't include any props. @jods4 I did a bit of digging to try to understand this better. Here is my (very rough) explanation of what is going on. There are two ways that a component update (re-render) can be triggered in Vue 3. Firstly, if any reactive rendering dependencies change then the component will be added to the scheduler queue. This covers cases such as changes to However, changes to props are not handled that way. They are handled by the other update path. The only thing that can change props is a parent update. Rather than going through the scheduler queue the parent triggers child updates itself. The decision about whether or not to update the child is made in That checks whether the 'props' have changed but doesn't take into account how they are used. Note that the 'props' here are not props in the usual sense. These are the raw VNode props, so they also include This means that component prop changes are not the only thing that can trigger an unnecessary render. Some examples:
|
@skirtles-code Thanks for doing the digging, I hadn't looked deeply into these parts yet. So as I understand your findings, it's not a correctness issue but rather ease of implementation: That is understandable. I'll dig it myself when I have time, but do you happen to know how that second update path avoids double rendering? |
@posva Please tell me if I should create a new issue instead of commenting here. I'm facing same problem and wondering if this usecase justifies this optimization: Goal:To have an Typical usage
setup() {
const timer = useTimer()
const balance = computed(() => {
if (timer) {
// it's alwasy true, just referring to it here to fetchBalance every time the timer ticks.
// similar behavior may be achieved by combining watcher and a ref
return fetchBalance()
}
throw Error('Impossible')
})
return {
balance
}
} Implementationconst AsyncChild = {
name: 'AsyncChild',
props: {
value: {
type: Promise,
required: true,
}
},
async setup(props) {
console.log('child setup')
const result = ref()
watch(async () => {
result.value = await (props.value)
})
await props.value;
return () => {
console.log('child render')
return result.value
}
}
}
const Async = {
name: 'Async',
props: {
value: {
type: Promise,
required: true,
},
pending: {
type: String,
default: "...",
},
},
setup(props) {
console.log("async setup");
return () => {
console.log("async render");
return h(Suspense, null, {
default: () => h(AsyncChild, { value: props.value }),
fallback: () => props.pending
});
};
},
}; Outcome & analysisUsing above implementation, there are 3 renders every time the timer updates:
Reasoning:
Implementation 2We can use a scoped const Async = {
name: 'Async',
props: {
value: {
type: Promise,
required: true,
},
pending: {
type: String,
default: "...",
},
},
setup(props) {
console.log("async setup");
// Disadvantage of this implementation:
// We will have as many child component definition as the amount of Async component instances
const child = {
async setup() {
const result = ref()
watch(async () => {
result.value = await props.value // note that this props is props of the Async component, not child component
})
await props.value
return () => {
console.log('child render')
return result.value
}
}
}
return () => {
console.log("async render");
return h(Suspense, null, {
default: () => h(child),
fallback: () => props.pending
});
};
},
}; Outcome & analysisWhen timer updates:
While, this time re-render of Dream worldIf an un-referenced prop update never triggers re-render, then timer update will only trigger child components re-render upon resolution of the new fetchBalance request. Live demohttps://codesandbox.io/s/unncessary-render-on-unused-props-change-vue3-t4qov |
Version
3.0.0-rc.5
Reproduction link
https://jsfiddle.net/skirtle/oxfbL72k/3/
Steps to reproduce
render
function has been called.What is expected?
The
render
function should not be called.What is actually happening?
The
render
function is being called.The changed prop is not used by the
render
function so rendering should not be triggered.There are several use cases for props that don't require rendering updates. e.g.:
The actual performance impact will usually be small as no DOM changes should be required. It could add up though if lots of components update at once, maybe inside a list or grid component.
The text was updated successfully, but these errors were encountered: