vue v-html6,vue.js - Poor performance in Vue v2.6 - Stack Overflow

There's a lot to cover here...

A component's template will be compiled down to a render function.

When Vue needs to render a component it will call that render function. The render function is not directly responsible for rendering anything. Instead it returns a tree of VNodes that describe what should be rendered.

Those VNodes come in two types. Some represent DOM elements, while others represent child components.

Before calling the render function, Vue will start tracking. Any reactive data that is read while the render function is running will be added to a list of dependencies. Vue will stop the tracker when the render function finishes.

Vue does not directly track how the data is used. All it records is what data was read when render ran. Trying to keep track at a more granular level would be very complicated and in most cases would not make much difference to performance. In fact, the complexity of the tracking process would likely have a detrimental impact on performance in most cases. However, as I'll come to later, you can give Vue a nudge in the right direction so that it does know which dependencies are used where.

For web development more generally, the biggest performance bottleneck is manipulating the DOM. That is typically much, much slower than doing anything else. By carefully batching updates and only making the minimum of changes the impact can be reduced to keep everything fast. There are various ways this can be done but Vue uses a virtual DOM, much like several other frameworks.

When a component is updated, Vue will run the render function to generate a new tree of VNodes. It will then use a diffing algorithm to compare the old VNodes to the new VNodes and use that to figure out what the minimal set of changes are to update the DOM.

Generating the VNodes and diffing them isn't free. It's much cheaper than regenerating all of the DOM from scratch each time, but it still takes time. Vue 3 includes a lot of improvements to speed up both the generation and diffing of VNodes. Most of those improvements target specific cases that are not relevant to the code in the question.

Ultimately, frameworks like Vue cannot compete on performance with hand-crafted JavaScript. A general purpose framework can never be faster that code written to do one specific task.

In practice, Vue is usually fast enough for real use cases and the benefits come from maintainable code. Writing hand-crafted JavaScript may be theoretically faster but beyond a certain point it becomes too difficult to write code that way, leading to problems with both correctness and performance.

I don't want to give the impression that Vue isn't fast. It is. But it's always going to struggle to compete with hand-crafted JavaScript for an example like the one in the question.

That said, it should be possible to make the code in the question much faster.

As I mentioned earlier, Vue doesn't track exactly how reactive data is used within the render function. However, if you split your component up into smaller components then that does allow a finer granularity of tracking.

Each render function is tracked individually, so if data is only used in one child component then only that component will re-render when that data changes. That's a significant reduction in the number of VNodes that need to be created and diffed. It should be lightning fast.

If the parent re-renders that won't necessarily cause the child components to re-render. It might, if the props have changed, but if everything has stayed the same then the children won't re-render.

Having child components will also allow you to take advantage of computed properties. That can save some calculation overhead. Usually this is a minimal saving but it depends on how much work is involved in calculating the value.

So I suggest changing your template to something like this:

v-for="(row, idx) in rows"

:row="row"

:index="index"

:key="row.key"

/>

If you're using in-DOM templates then you'll need to make some adjustments to stop the browser from trashing it but the idea's still the same.

You'd then introduce a component called my-table-row that does all the work for a particular row.

Written this way, the parent component has a dependency on the rows array and the objects within in but it doesn't have a dependency on the individual properties of those objects. Instead, that's now moved into the child components. So if one property of one object changes it will only need to render that one child.

You can take this even further by splitting up the rows into individual cell components.

Update: Selections.

Yeah, selections are tricky when you have 3000 rows.

Multi-select would probably be easier to implement here because we could use a single object where the property keys are the row ids and the values are true or false for whether the row is selected. Written carefully the rows would end up with a dependency on just their specific property so nothing else needs to render.

Of course, we could implement single-select the same way. It would be fast but it feels like a wasteful use of data structures.

The first thing I would try is having a selected prop on each row component that is just a boolean. Something like this:

v-for="(row, idx) in rows"

:row="row"

:index="index"

:selected="row === selRow"

:key="row.key"

/>

When selRow changes it will re-render the parent table component but most of the child rows won't be touched. Yes, there is a lot of unnecessary work being done here, but it might be fast enough that we don't have to start taking extreme measures.

But with 3000 rows, extreme measures may be necessary.

I should add that with 3000 rows you would usually use some form of lazy rendering to only show the visible rows rather than the whole lot at once. But let's continue assuming that isn't an option.

So what if rendering the parent component is still too slow, even when its children don't need to re-render?

Well, here's one idea...

I should say upfront that what I'm about to describe is pretty extreme and I have never needed to do anything this complicated to squeeze out performance in a real Vue application.

The selRow could be wrapped in an object, e.g.:

data () {

return {

selRow: {

value: null // update this value when the selection changes

}

}

}

with:

v-for="(row, idx) in rows"

:row="row"

:index="index"

:selected="selRow"

:key="row.key"

/>

When the value changes it won't trigger the parent to re-render because it doesn't have a dependency on that property.

But now we need to ensure that the child rows don't all re-render.

One way we could do that it to use a watch on selected.value to update an internal data property with a true or false value. That value would then be used within the row's template.

While the watch would be called 3000 times that would likely be very quick as it would hardly be doing anything. Only components where that internal property changed would then re-render.

This all sounds like the classic state synchronisation watch anti-pattern. The 'best practice' would be to use a computed property. However, that won't help to prevent the rows from all rendering. It doesn't matter whether a computed property's value has changed, if the upstream dependencies change then the render function is triggered. We need to use a separate data property to ensure that doesn't happen.

So the row component code for that would be something like this (in Vue 2):

data () {

return {

rowSelected: false

}

},

watch: {

'selected.value': {

immediate: true,

handler (selRow) {

this.rowSelected = selRow === this.row

}

}

}

It is a convoluted way to do things but it should be plenty fast.

As a final note: in extreme performance scenarios there is nothing wrong with using direct DOM manipulation as an escape hatch. You have to be really careful to do it right but it can be done.

Further update: Speeding up the JSFiddle example

I understand that your real use case is going to be much more complicated than the example but I'm going to have to limit my answer to the code you provided. We'll be here forever if I try to cover all the hypothetical cases you might be hitting.

This is increasingly drifting out of scope for a Stack Overflow question. This would arguably be a better fit for the Vue Forums.

That said, here are my findings from looking at your latest example.

Firstly, the console logging does not show what you think it shows. The logging shows that only one row is being updated, or two rows in the case of selection changes. The method cellClass is called once per cell, so 4 times per row. Likewise formatField is called twice per row.

I ramped it up to 10000 rows and switched the logging to something a little more helpful:

The exact timing will depend on your browser and hardware but 10000 rows was enough for me to get an obvious delay for selections and editing.

Thankfully, the slots aren't actually causing any problems here.

The logging I added gives a hint about what the problem is. Even though only one row is updating it is also updating both the root and parent components.

I don't think updating the root is a problem but the parent component is going to be doing a lot of extra work looping through 10000 rows.

So to speed it up we need to avoid triggering that parent render.

The first problem is using $set to add a new property. As Vue 2 can't track missing properties it has to assume the worst when they're added subsequently. Vue 3 uses proxies so it doesn't have this problem.

So to fix that we can pre-populate the dirty field in the row data. Using $set for a property that already exists isn't necessary but it also isn't really doing any harm so I've left that line unchanged:

You won't see any improvement for selections but the editing is dramatically faster. You'll also notice that the root and parent components aren't being updated, just the relevant row.

Moving on to selections, this can be sped up by using the wrapper object trick I outlined earlier.

The wrapper object hides the change from the root and parent components. They just see the wrapper. Only the rows read the inner value so only they will be impacted by the change:

For 10000 rows the DOM is going to be huge and I suspect much of the remaining performance drain is down to that. Maybe there is more that can be done (e.g. production build of Vue) but for me it's plenty fast enough. If I reduce it to 3000 rows it updates faster than my eyes can see.

I know very little about your application but I would add that a large DOM is likely to impact performance no matter what tools you use. The browser's layout calculations in particular. Even changes that aren't related to your table can take a long time if your layouts aren't sufficiently isolated. That is a massive topic all by itself and not directly related to Vue so I'm not going to dwell on it, just to say that it is one reason why applications use lazy rendering rather than showing thousands of rows all at once.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值