如何将 Svelte 存储与树状嵌套对象一起使用? [英] How to use Svelte store with tree-like nested object?

查看:35
本文介绍了如何将 Svelte 存储与树状嵌套对象一起使用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Svelte 官方教程在<的文档中使用了如此复杂的对象/code>

let root = [{类型:'文件夹',name: '重要的工作内容',文件:[{ 类型:'文件',名称:'季度结果.xlsx'}]},{类型:'文件夹',name: '动物 GIF',文件:[{类型:'文件夹',name: '狗',文件:[{ 类型:'文件',名称:'treadmill.gif' },{ 类型:'文件',名称:'跳绳.gif' }]},{类型:'文件夹',name: '山羊',文件:[{ 类型:'文件',名称:'parkour.gif' },{ 类型:'文件',名称:'rampage.gif' }]},{ 类型:'文件',名称:'cat-roomba.gif' },{ 类型:'文件',名称:'duck-shuffle.gif' },{ 类型:'文件',名称:'monkey-on-a-pig.gif'}]},{ 类型:'文件',名称:'TODO.md' }];

如果这个对象需要反应性并放置在商店内,应该怎么做?应该将树包装为单个存储,还是每个文件和文件夹都是它自己的存储并相应地嵌套存储?

在这两种情况下,似乎每当顶级属性发生变化时(svelte store 认为对象的更新总是新鲜的),整个树都会被检查是否有变化?

解决方案

一些需要知道的事情...

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

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

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

<button on:click={onClick}>+</button><span>{$x.count}</span>

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

Child.svelte

<输入绑定:值/>

App.svelte

<子绑定:值/>

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

<button on:click={onClick}>+</button><span>{$x.count}</span><小时/>{#每个 $y 作为项目,i}<div><按钮:click={()=>item.count++}>$y[{i}]: +

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


因此,知道所有这些,如果您将源数据放在可写存储中并且您对 2 路绑定进行精确处理,您最终可以得到一个非常便宜的问题解决方案......(参见 REPL)

stores.js

import { 可读、可写、派生 } from 'svelte/store'//一个大的可写存储export const root = writable([{类型:'文件夹',name: '重要的工作内容',文件:[{ 类型:'文件',名称:'季度结果.xlsx'}],},{类型:'文件夹',name: '动物 GIF',文件:[{类型:'文件夹',name: '狗',文件:[{ 类型:'文件',名称:'treadmill.gif' },{类型:'文件',名称:'跳绳.gif'},],},{类型:'文件夹',name: '山羊',文件:[{ 类型:'文件',名称:'parkour.gif' },{类型:'文件',名称:'rampage.gif'},],},{ 类型:'文件',名称:'cat-roomba.gif' },{ 类型:'文件',名称:'duck-shuffle.gif' },{ type: 'file', name: 'monkey-on-a-pig.gif' },],},{ 类型:'文件',名称:'TODO.md' },])

App.svelte

<div class="hbox"><div><!-- 注意绑定到存储本身:bind=files={root} --><文件夹只读扩展绑定:files={$root} file={{ name: 'Home' }}/>

<pre>{JSON.stringify($root, null, 2)}</pre>

<风格>.hbox {显示:弹性;justify-content: 空间环绕;}</风格>

Folder.svelte

{#if 只读}<!-- NOTE 绑定必须保持引用条目"多变的(此处:`file.`)被跟踪 --><span class:expanded on:click={toggle}>{file.name}</span>{:别的}<标签><span class:expanded on:click={toggle}/><input bind:value={file.name}/>{/如果}{#if 展开}<ul>{#每个文件作为文件}<li>{#if file.type === '文件夹'}<!-- 注意#each 循环创建的中间变量(此处:本地`file` 变量)保留跟踪,但 --><svelte:self bind:file bind:files={file.files}/>{:别的}<文件绑定:文件/>{/如果}{/每个}{/如果}<风格>跨度 {填充:0 0 0 1.5em;背景: url(tutorial/icons/folder.svg) 0 0.1em 不重复;背景尺寸:1em 1em;字体粗细:粗体;光标:指针;最小高度:1em;显示:内联块;}.扩展{背景图片:网址(教程/图标/文件夹-open.svg);}ul{填充:0.2em 0 0 0.5em;边距:0 0 0 0.5em;列表样式:无;左边框:1px 实心#eee;}李{填充:0.2em 0;}</风格>

文件.svelte

<标签><span style="background-image: url(tutorial/icons/{type}.svg)";/><input bind:value={file.name}/><风格>跨度 {填充:0 0 0 1.5em;背景:0 0.1em 不重复;背景尺寸:1em 1em;}</风格>

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

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

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

The Svelte official tutorial employs such complex object in its document for <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' }
];

If this object needs to be reactive and placed inside a store, how should it be done? Should the tree be wrapped as a single store, or each file and folder is its own store and stores are nested accordingly?

In both cases, it seems whenever the top-level properties are changed (svelte store considers update from objects always fresh), the whole tree will be checked for change?

解决方案

A few things to know...

The $ prefix notations for stores also works to assign a new value to a writable store:

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

  const x = writable(0)

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

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

<span>{$x}</span>

This also works to write to a single prop of an object, or individual items in an array:

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

From a parent component, you can bind a variable to a prop of a child component:

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

Note: bindings only works when it's the same variable. That is, you can't put the bound variable in an intermediate variable, and have Svelte keep tracking this binding. Svelte does keep tracking individual props of objects (as long as they're referenced from the originally bound variable -- with dot notation), and items of arrays though, notably in {#each} loops:

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


And so, knowing all this, if you put your source data in a writable store and you are precise with your 2-way bindings, you can end up with a pretty cheap solution to your question... (See in 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>

Note, however, that this might not be the most efficient solution.

The reason is that is that any change to any part of the store will be detected as a change to the whole store, and so Svelte will have to propagate and revalidate the change to every consumers (components) or this data. We're not necessarily talking about some heavy processing, because Svelte still knows the data graph and will short-circuit most of the propagation very early with very cheap and chirurgically targeted if tests. But still, the complexity of the processing will grow linearly (albeit slowly) with the size of the object in the store.

In some cases where the data can be very big or something (maybe allow for lazy fetching of the nested nodes?), you may want to elaborate around the techniques demonstrated in the above examples. For example, you could cap the algorithmic complexity (cost) of processing a change by wrapping the recursive nodes in your data (i.e. the files prop in the above example) each in a writable store. Yes, that would be stores in stores (high order stores?). This would surely be a little delicate to wire together, but that would theoretically give you near infinite scalability, because every change will only propagate to the siblings of the impacted node, instead of the whole tree.

这篇关于如何将 Svelte 存储与树状嵌套对象一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆