如何在 svelte 中创建动态树结构?

How to create dynamic tree structure in svelte?

我需要一个 svelte 树视图,每个节点都有复选框。

我无法获得这样的功能:如果我们检查一个父节点,那么它的所有(只有它的)子节点是否是文件夹的文件,所有这些(子)自动检查,并且稍后取消选中其中任何一个,那么父项也应该取消选中。我怎样才能做到这一点?

下面是我从 here 中遵循的代码。我尝试了一些东西但没有运气。

App.svelte-

<script>
    import Folder from './Folder.svelte';

    let root = [
        {
            name: 'Important work stuff',
            files: [
                { name: 'quarterly-results.xlsx' }
            ]
        },
        {
            name: 'Animal GIFs',
            files: [
                {
                    name: 'Dogs',
                    files: [
                        { name: 'treadmill.gif' },
                        { name: 'rope-jumping.gif' }
                    ]
                },
                {
                    name: 'Goats',
                    files: [
                        { name: 'parkour.gif' },
                        { name: 'rampage.gif' }
                    ]
                },
                { name: 'cat-roomba.gif' },
                { name: 'duck-shuffle.gif' },
                { name: 'monkey-on-a-pig.gif' }
            ]
        },
        { name: 'TODO.md' }
    ];
</script>

<Folder name="Home" files={root} expanded/>

File.svelte-

<script>
    export let name;
    $: type = name.slice(name.lastIndexOf('.') + 1);
</script>

<span style="background-image: url(tutorial/icons/{type}.svg)">{name}</span>

<style>
    span {
        padding: 0 0 0 1.5em;
        background: 0 0.1em no-repeat;
        background-size: 1em 1em;
    }
</style>

Folder.svelte-

<script>
    import File from './File.svelte';

    export let expanded = false;
    export let name;
    export let files;
    let checkedState = true;

    function toggle() {
        expanded = !expanded;
    }
    
    function onCheckboxChanged(e){
        console.log("out: "+document.getElementById("cb1").checked)
    }
</script>

<input type="checkbox" on:change={onCheckboxChanged} id="cb1" bind:checked={checkedState}><span class:expanded on:click={toggle}>{name}</span>

{#if expanded}
    <ul>
        {#each files as file}
            <li>
                {#if file.files}
                    <svelte:self {...file}/>
                {:else}
                    <input type="checkbox" on:change={onCheckboxChanged} id="cb1" bind:checked={checkedState}><File {...file}/>
                {/if}
            </li>
        {/each}
    </ul>
{/if}

<style>
    span {
        padding: 0 0 0 1.5em;
        background: url(tutorial/icons/folder.svg) 0 0.1em no-repeat;
        background-size: 1em 1em;
        font-weight: bold;
        cursor: pointer;
    }

    .expanded {
        background-image: url(tutorial/icons/folder-open.svg);
    }

    ul {
        padding: 0.2em 0 0 0.5em;
        margin: 0 0 0 0.5em;
        list-style: none;
        border-left: 1px solid #eee;
    }

    li {
        padding: 0.2em 0;
    }
</style>

  1. 删除文件组件旁边的复选框中的绑定。这将防止您单击文件的复选框并检查整个文件夹的情况。相反,您只需要在检查 Folder 时将状态传递给子 File 组件。所以像这样更改代码:
<!-- <input type="checkbox" bind:checked={checkedState}> -->
<input type="checkbox" checked={checkedState}>
  1. 然后你需要将文件夹的选中状态传递给所有子文件夹组件(所以当你选中文件夹时,里面的文件夹也会被选中)。你可以通过使 checkedState 成为一个道具,而不仅仅是常规变量,然后在调用 svelte:self 组件时传递这个道具
<script>
    // let checkedState = true;
    export let checkedState = true;
</script>

<!-- <svelte:self {...file}/> -->
<svelte:self {...file} {checkedState} />
  1. 之后只剩下一个问题案例。当所有子组件都被(未)选中时,父组件也应该被(未)选中。我真的看不到用当前结构实现这个东西的可能性。我建议您在文件组件中移动复选框,然后在文件对象(在 App.svelte 中)中创建字段 checkedselected,这样您就可以轻松管理任何子组件中的所有状态。从根文件夹组件中提取数据时也更容易读取此值(例如,如果您需要将此数据发送到服务器)。添加道具后,您当然应该使用对象属性,而不是组件的状态。祝你好运!

P.S:不要在组件中使用静态 ID,在每个循环中更是如此。这将使您的输出 html 无效。

当我尝试构建与您尝试构建的相同的 TreeView 组件时,我遇到了这个问题。 我用 compnent event.

实现了你想要的 dynamic 功能

这是demo, which also use svelte:self and inspired by this REPL

这种方法的要点是:

  1. 子组件没有责任计算树的状态,它只需要表示它。
  2. 当子节点的状态发生变化时,让父组件进行整个树的状态计算。