14

Svelte 官方教程在其文档中使用了如此复杂的对象<svelte:self>

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

如果这个对象需要响应并放置在 store 中,应该怎么做?树应该被包装为单个存储,还是每个文件和文件夹都是它自己的存储并且存储相应地嵌套?

在这两种情况下,似乎只要更改了顶级属性(svelte store 认为对象的更新总是新鲜的),就会检查整个树是否有更改?

4

2 回答 2

24

有几件事要知道...

商店的$前缀符号也可以为可写商店分配一个新值:

<script>
  import { writable } from 'svelte/store'

  const x = writable(0)

  const onClick = () => {
    $x = $x + 1
  }
</script>

<button on:click={onClick}>+</button>

<span>{$x}</span>

这也适用于写入对象的单个道具或数组中的单个项目:

<script>
  import { writable } from 'svelte/store'

  const x = writable({
    count: 0,
  })

  const onClick = () => {
    $x.count = $x.count + 1
  }
</script>

<button on:click={onClick}>+</button>

<span>{$x.count}</span>

从父组件中,您可以将变量绑定到子组件的 prop :

Child.svelte

<script>
  export let value
</script>

<input bind:value />

App.svelte

<script>
  import Child from './Child.svelte'

  let value = ''

  $: console.log(value)
</script>

<Child bind:value />

注意:绑定仅在它是同一个变量时才有效。也就是说,您不能将绑定变量放在中间变量中,并让 Svelte 继续跟踪此绑定。Svelte 确实会一直跟踪对象的各个道具(只要它们是从最初绑定的变量中引用的——使用点表示法),以及数组项,特别是在{#each}循环中:

<script>
  import { writable } from 'svelte/store'

  const x = writable({
    count: 0,
  })
    
  const y = writable([
    { count: 0 },
    { count: 1 },
  ])

  const onClick = () => {
    $x.count = $x.count + 1
  }
</script>

<button on:click={onClick}>+</button>

<span>{$x.count}</span>

<hr />

{#each $y as item, i}
  <div>
    <button on:click={() => item.count++}>$y[{i}]: +</button>
  </div>
{/each}

<pre>{JSON.stringify($y)}</pre>

因此,知道这一切后,如果您将源数据放在可写存储中,并且您的 2 路绑定很精确,那么您最终可以得到一个非常便宜的解决方案来解决您的问题......(参见REPL

stores.js

import { readable, writable, derived } from 'svelte/store'

// a big writable store
export const root = writable([
  {
    type: 'folder',
    name: 'Important work stuff',
    files: [{ type: 'file', name: 'quarterly-results.xlsx' }],
  },
  {
    type: 'folder',
    name: 'Animal GIFs',
    files: [
      {
        type: 'folder',
        name: 'Dogs',
        files: [
          { type: 'file', name: 'treadmill.gif' },
          { type: 'file', name: 'rope-jumping.gif' },
        ],
      },
      {
        type: 'folder',
        name: 'Goats',
        files: [
          { type: 'file', name: 'parkour.gif' },
          { type: 'file', name: 'rampage.gif' },
        ],
      },
      { type: 'file', name: 'cat-roomba.gif' },
      { type: 'file', name: 'duck-shuffle.gif' },
      { type: 'file', name: 'monkey-on-a-pig.gif' },
    ],
  },
  { type: 'file', name: 'TODO.md' },
])

App.svelte

<script>
  import { root } from './stores.js'
  import Folder from './Folder.svelte'

  $: console.log($root)
</script>

<div class="hbox">
  <div>
    <!-- NOTE binding to the store itself: bind=files={root} -->
    <Folder readonly expanded bind:files={$root} file={{ name: 'Home' }} />
  </div>
  <pre>{JSON.stringify($root, null, 2)}</pre>
</div>

<style>
  .hbox {
    display: flex;
    justify-content: space-around;
  }
</style>

Folder.svelte

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

  export let readonly = false
  export let expanded = false

  export let file
  export let files

  function toggle() {
    expanded = !expanded
  }
</script>

{#if readonly}
  <!-- NOTE bindings must keep referencing the "entry" variable 
       (here: `file.`) to be tracked -->
  <span class:expanded on:click={toggle}>{file.name}</span>
{:else}
  <label>
    <span class:expanded on:click={toggle} />
    <input bind:value={file.name} />
  </label>
{/if}

{#if expanded}
  <ul>
    {#each files as file}
      <li>
        {#if file.type === 'folder'}
          <!-- NOTE the intermediate variable created by the #each loop 
               (here: local `file` variable) preserves tracking, though -->
          <svelte:self bind:file bind:files={file.files} />
        {:else}
          <File bind: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;
        min-height: 1em;
        display: inline-block;
  }

  .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>

File.svelte

<script>
  export let file

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

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

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

但是请注意,这可能不是最有效的解决方案。

原因是对商店任何部分的任何更改都将被检测为对整个商店的更改,因此 Svelte 必须将更改传播并重新验证到每个消费者(组件)或此数据。if我们不一定要谈论一些繁重的处理,因为 Svelte 仍然知道数据图,并且会在很早的时候通过非常便宜且具有针对性的测试来短路大部分传播。但是,处理的复杂性仍然会随着存储中对象的大小线性增长(尽管速度很慢)。

在某些数据可能非常大或某些情况下(可能允许延迟获取嵌套节点?),您可能需要详细说明上述示例中演示的技术。例如,您可以通过将数据中的递归节点(即files上例中的道具)包装在可写存储中来限制处理更改的算法复杂性(成本)。是的,那将是商店中的商店(高级商店?)。这肯定会有点微妙的连接在一起,但理论上这会给你带来近乎无限的可扩展性,因为每个更改只会传播到受影响节点的兄弟姐妹,而不是整个树。

于 2020-12-02T10:25:54.497 回答
0

对自己的注意:

https://github.com/sveltejs/svelte/issues/1435#issuecomment-735233175

从具有所有全局状态的单个存储开始,然后从该主存储中分离视图。作为概念证明,我编写了一个名为 subStore 的工具。repl 的示例和链接可以在这里找到 https://github.com/bradphelan/immer.loves.svelte

https://github.com/PixievoltNo1/svelte-writable-derived#making-an-object-store-from-several-single-value-stores

于 2022-01-20T08:05:43.483 回答