简体   繁体   中英

Svelte recomputes too much on change

I'm implementing an app where the user should be able to place things on a 2D grid by clicking on it, and also be able to drag the grid around. I have a functional prototype here:

https://svelte.dev/repl/ebfde34fafda4b79859f70dd8f984b12?version=3.54.0

<script>
    
    let project = {
        offsetX: 0,
        offsetY: 0,
        circles: [{
            id: 1,
            x: 50,
            y: 50,
        }]
    }
    let dragInfo = null
    
    function onBackgroundDragStart(event){
        
        dragInfo = {
            action: "moveBackground",
            dragStartProjectOffsetX: project.offsetX,
            dragStartProjectOffsetY: project.offsetY,
            startMouseX: event.clientX,
            startMouseY: event.clientY
        }
        
        const dragImage = document.createElement("img")
        dragImage.style.display = "none"
        event.dataTransfer.setDragImage(dragImage, 0, 0)
        
    }
    
    function onBackgroundDragOver(event){
        
        switch(dragInfo.action){
            
            case "moveBackground":
                
                const dragDistanceX = event.clientX - dragInfo.startMouseX
                const dragDistanceY = event.clientY - dragInfo.startMouseY
                
                project.offsetX = dragInfo.dragStartProjectOffsetX + dragDistanceX
                project.offsetY = dragInfo.dragStartProjectOffsetY + dragDistanceY
                
            break
            
        }
        
    }
    
    // Create new circle on click.
    function onClick(event){
        
        project.circles.push({
            id: project.circles.length + 1,
            x: event.clientX - project.offsetX,
            y: event.clientY - project.offsetY,
        })
        
        project.circles = project.circles
        
    }
    
    function getText(circle){
        console.log("getText() is called.")
        return circle.x.toString()[0]
    }
    
</script>

<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
    class="app"
    
    draggable="true"
    on:dragstart={onBackgroundDragStart}
    on:dragover|preventDefault={onBackgroundDragOver}
    
    on:click={onClick}
    
    style:background-position-x={`${project.offsetX}px`}
    style:background-position-y={`${project.offsetY}px`}
>
    
    {#each project.circles as circle (circle.id)}
        <div
            class="circle"
            style:transform={`translate(${
                project.offsetX + circle.x
            }px, ${
                project.offsetY + circle.y
            }px) translate(-50%, -50%)`}
        >
            {getText(circle)}
        </div>
    {/each}
    
</div>

<style>

.app{
    position: relative;
    width: 100vw;
    height: 100vw;
    background-color: silver;
    position: relative;
    background-image: linear-gradient(rgba(0, 0, 0, .1) .1em, transparent .1em), linear-gradient(90deg, rgba(0, 0, 0, .1) .1em, transparent .1em);
    background-size: 2em 2em;
    overflow: hidden;
}

.circle{
    position: absolute;
    width: 50px;
    height: 50px;
    background-color: red;
    border-radius: 50%;
    text-align: center;
}

</style>

The prototype works as expected, but Svelte recomputes too much on changes:

  1. When dragging the background, only project.offsetX and project.offsetY are updated, but in {#each project.circles...} , getText(circle) is called again for each circle, even though it just makes use of the circle.x and will never change when project.offsetX and project.offsetY
  2. Same when adding a circle ( getText(circle) is called for each circle again)

This is very problematic in my case, because getText() is in my true case another function that is very expensive to call, so it's important it's not being called unnecessary many times (it starts to lag).

I know I can get around (1) in the list above by putting the circles array in it's own variable, but that is not really a good solution, since I need to store all information about the project in one and the same object (so I later conveniently can save it in a JSON file), so I would prefer to not use that. And the problem with (2) still exists.

How can I get Svelte to not update unnecessarily much?

I do not think the problem lies with how Svelte re-renders/updates. When you modify a component, for instance in your case when you drag the grid, it is normal for Svelte to re-render the circles in order to properly repaint them in your browser. The same applies when you update your list of circles (although I am a bit surprised all circles are re-rendered when you simply add a circle considering you properly add a (circle.id) key to your #each loop?).

The solution to your expensive computation issue is to use memoization in order to cache expensive computation results, thus making subsequent function calls with identical inputs virtually free of computation cost. The exact implementation is really up to you, though you will want your expensive computation function to be pure (ie no side-effects, and you always get the same output from given inputs) for memoization to function properly.

Once you have memoization in place, expensive computation should not be an issue anymore, and neither will be Svelte's re-rendering.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM