This is my component architecture (relevant component in bold):
App
Attributes
Evaluator
Commander
StyleGrid
DataGrid
This is part of my merged reducers:
Root
appState
isImageFocused (boolean)
...
When isImageFocused changes, the color of the following image changes:
The image is part of the Evaluator component. Everything works as expected, nevertheless, I've noticed the entire Attributes's component-subtree gets re-evaluated (shadow DOM comparison, right?) for that small change. As far as I understand, this means Attributes, Evaluator, StyleGrid and DataGrid components are all re-evaluated (even if not re-painted).
Is there a way to avoid those re-evaluations on small changes?
I am asking because I saw a performance hit when I ran the app with mrdoob's stats.js tool.
I thought of using 'shouldComponentUpdate' but that means every time I add a sibling for Evaluator I need to tell it not to update when isImageFocused is updated.
I wish there was something such as "silentProps" which would enable me to pass props through the component tree to end leafs without causing a massive re-evaluations. Is there?
Related
I've got a complex component which does all its rendering in a render function. There are multiple parts to this, and different bits of the view get rendered - one of these things is a filter bar, showing the filters that have been applied.
What I'm noticing happening, is if I apply a filter which in turn presents this bar, it causes everything else to be fully re-rendered. This is causing a number of other issues and I need to try and stop it from happening.
I've never come across this issue when using normal templates as Vue seems to handle these very intelligently, but I have no idea how to tackle this. The only thing I can think of is setting a key on each thing I don't want re-rendered but not sure if this will a) solve the problem, and b) be possible for the content that is passed in through a slot
Has anyone else faced this issue, and if so how can it be solved?
I had a similar issue when using vuetify text inputs in a complex component which was causing the app to slow down drastically.
In my search I found this link which was specific to vuetify:
high performance impact when using a lot of v-text-field
then found out that this is actually a vue thing from this GitHub issue:
Component with slot re-renders even if the slot or component data has not changed
and there is plan to improve this in it is tracked here (vue 3 should resolve this issue):
Update slot content without re-rendering rest of component
so after reading through all these I found some workarounds that helped me a lot to boost the performance of my app, I hope these will help you as well:
divide that complex component into smaller ones specially when there is some bit of code that changes data that bounds to template causing re-rendering (put them in their own component)
I moved all data layer control to the vuex store, instead of using v-model every where and passing data as events and props, all the data is updating in the store through an action and read from the store through a getter. (from data I mean somethings that is being looped in the template in a v-for, API results, and so on... all of them is being set, updated and read through the store. my components still have the data object but only for the things related to the style and template control like a boolean to control a modal or an imported icon which is used in the template and alikes)
lastly I wrote a function called lazyCaller which its job is to update the values in the store with a delay (when immediate data update isn't necessary) to avoid rapid updates comping from something like a text input (with out this every key stroke trigger the value update action)
In our experience, thinking about how the UI should look at any given moment, rather than how to change it over time, eliminates a whole class of bugs.
From React Docs
From my understanding, this means that React only updates what's necessary, rather than destroying and re-constructing the entire DOM tree again. Am I wrong?
Can anyone please help me understand the quoted statement?
Thanks.
From my understanding, this means that React only updates what's necessary, rather than destroying and re-constructing the entire DOM tree again. Am I wrong?
If you want to know the short answer, I have to say it is true, React will update the necessary elements in DOM whenever it needed.
But if you want to know how it's done, and when React will update the DOM and its element I have to it is varying to different several things, like project architecture, using proper methods (proper hooks in functional component eg. useCallback, useMemo, and so on) and so on.
When it truly gets rerender then?
As far as I know, there are two ways React finds out when to rerender the DOM.
Passing elements to ReactDOM.render
Update a state
What is ReactDOM.render?
This will call the render() method from react-dom (Where we usually import it as ReactDOM) and it will render a React element into the DOM in the supplied container and return a reference to the component (or returns null for stateless components). Also if the React element was previously rendered into the container, this will perform an update on it and only mutate the DOM as necessary to reflect the latest React element.
What does state mean?
The state object is where you store property values that belong to the component. So when you got a component and that component has their own specific variables where changing them should affect the DOM you should use it, then whenever state gets changes the component will be updated.
So what I even talk about project architecture and this stuff, when I didn't mention anything about it above?
Let's say we got a project with one parent component and 3 child component just like this:
Component 1
- |- Component 2
- - |- Component 3
- - - |- Component 4
So whenever you a state in Component 4 all of the DOM elements will be get rerendered, why then? Because Component 4 is a child of Component 3 and so on, so when the child state gets change it will force the parent to rerender then the whole DOM will rerender once per state change.
Final Note
So, at last, we should be always considered a good architecture and hierarchy for our project or when it necessarily use built-in methods like useMemo to avoid such a thing.
See the following Stackblitz: https://stackblitz.com/edit/angular-psqzbo?file=src%2Fapp%2Fhello.component.ts
Notice that there are two bindings to the width member: one is in the template, and the other is a host binding. The host binding is commented out right now. Notice that no ExpressionChanged error is being thrown-- this is because this.cdr.detectChanges() is being called after we update width in ngAfterViewInit.
Now uncomment the host binding. Observe that an ExpressionChanged error is thrown. Why? What makes these bindings different? Is this a bug?
EDIT: This is not a dupe of the linked question. I know why detectChanges is needed here, my question is why it is not working on the host binding. Please re-read.
This error occurs for you because you are making a change that invalidates the previously rendered component view.
Quote from this documentation
Angular's unidirectional data flow rule forbids updates to the view after it has been composed. Both of these hooks fire after the component's view has been composed.
What this means for your situation:
When Angular first renders/composes the view of this component (prior ngAfterViewInit) the width of the element is set to an initial value. This code changes the width of an element, which causes a change to the view.
I think this example using background color makes this more obvious. In the first pass rendering the view, the color is red (and you can briefly see this on the screen). In your situation, the width binding is undefined on the first pass through.
Then, the ngAfterViewInit causes a change that makes the previously created view invalid, changing the width or the color, triggering the error. I translate this error as Angular saying "I did a bunch of work and made a view that was perfect, then you did something that made that work worthless. You shouldn't do that, because it interrupts some performance optimizations/assumptions I have".
This can be fixed by ensuring the component change happens after the ngAfterViewInit method has finished running, by using setTimeout. Fixed example Or by ensuring the component change happens before the view is rendered by moving it to ngOnInit.
You may notice that I do not have detectChanges in the examples I created. This is intentional, as that is a red herring and actually has no relation to the problem (though you correctly state this is necessary for the template binding to work). When the host binding is commented out in your example, there is no expression problem because this.width has no impact on the rendered view of the component.
There is no issue when the binding is in the template because this is causing changes to the content of the component - not the component's own view.
Speculation / Things I don't fully understand:
I believe this behavior boils down to changes to the Shadow DOM vs Light DOM (I'm making guesses based on info from this SO question). At the time ngAfterViewInit is running, I believe there is already an empty tag <my-component></my-component> in the Light DOM (IE actually on the page). Changing the child content does not cause an issue, because at this point it doesn't actually exist on the page and is part of the Shadow DOM. However, changing a host binding triggers a change to the real elements on the page (part of the Light DOM) - hence the error.
I've wrote a plugin for Vue and have some issues on rerendering. In fact there is a new filter, which translate a given text by the global defined language. When the language changes, the text should be translated again. Cause there is no event queue for filters here, I want to force all components to rerender themselves, if the language changes. So the filter function would be evaluated new.
I know that I can rerender a component itself with vm.$forceUpdate(). But how can I told the whole Vue component tree to rerender? Cause this only happens, if the user change the language, the performance issues should not be a problem, cause at least the user do not have to reload the whole page and can do this offline.
Any suggestions?
You can use a key attribute in the component root. Change it and the component tree will be rerendered.
Per docs (bold is mine):
It can also be used to force replacement of an element/component
instead of reusing it. This can be useful when you want to:
Properly trigger lifecycle hooks of a component
Trigger transitions
For example:
<transition>
<span :key="text">{{ text }}</span>
</transition>
When text changes, the <span> will always be replaced instead of
patched, so a transition will be triggered.
The ReactJS website states that,
When the data changes, React conceptually hits the "refresh" button, and knows to only update the changed parts.
What does this mean exactly?
How is this achieved?
How is this different from what AngularJS does?
Reactjs create a virtual DOM and does not touch to the real DOM. Then when some of DOM changed, it will use diff algorithm to find out what is different from previous state of DOM elements, finally just replace that changes. You can see this link for that algorithm to have more understanding. And you have to know what is shadow DOM as well.
Here's a great resource explaining why ReactJS was developed and how it differs from other frameworks like AngularJS:
http://www.quora.com/Pete-Hunt/Posts/Facebooks-React-vs-AngularJS-A-Closer-Look
"A lot of the heavyweight contenders for MVVM frameworks have a hard time rendering large amounts of data, like in lists and such. React doesn’t have that problem, as it renders only what’s changed.
For example, if a user is viewing a list of 100 items rendered with React, and he or she changes the third one down somehow, only that item gets rerendered, leaving the other 99 items unchanged."
(https://www.codementor.io/reactjs/tutorial/react-vs-angularjs)