如何在 Svelte 中使用对象正确地预先 select 多个 select 输入?

How can I correctly pre-select a multiple select input in Svelte with objects?

在此示例组件中...

<script>

  let availableValues = [
    {id: 1, name: "one"},
    {id: 2, name: "two"},
    {id: 3, name: "three"},
    {id: 4, name: "four"}
  ]

  let selectedValues = [
    {id: 1, name: "one"},
    {id: 2, name: "two"}
  ]

</script>

<select multiple bind:value={selectedValues}>
  {#each availableValues as value}
    <option value={value}>
      {value.name}
    </option>
  {/each}
</select>

值已正确 selected 并绑定到 selectedValues

然而,当第一次加载组件时,selectedValues 并没有反映在 select 输入中——换句话说,它们没有被绑定,因为 select输入似乎无法确定 selectedValues 是否与任何 availableValues 匹配。尽管如此,当 selecting(单击)多个值时,selectedValues 数组会按预期填充。

在其他框架中,可以传入一个函数来帮助“导出”selected 值,类似于 (foo) => foo.id == value.id 来解决对象相等性的歧义。在 Svelte 中,有一个类似的替代方案,使用 selected 属性,但它似乎并不能很好地解决这种特殊情况。

在 Svelte 中处理类似情况的最佳方法是什么?

更进一步,当初始 selectedValues 也恰好是外部绑定的 prop(即 <MyMultipleSelect bind:selectedValues={vals} />)时,处理此问题的合理方法是什么?

您的方法在版本 3.42.2 之前一直有效,然后发生了什么变化

Deselect all <option>s in a where the bound value doesn't match any of them

所以现在您正在比较两个不同的对象,这就是它不再起作用的原因。

这是一个解决方法,当然,如果您愿意

,您可以创建一个通过 属性 查找的函数
<script>
    let availableValues = [
        {id: 1, name: "one"},
        {id: 2, name: "two"},
        {id: 3, name: "three"},
        {id: 4, name: "four"}
    ]

    let selectedValues = [
        {id: 1, name: "one"},
        {id: 2, name: "two"}
    ]

    let selected = JSON.stringify(selectedValues)
</script>

<select multiple bind:value={selected}>
    {#each availableValues as value}
        <option value={JSON.stringify(value)}>
            {value.name}
        </option>
    {/each}
</select>

这是因为 javascript objects 相等性基于 reference(= 内存位置)而不是值。作为一个快速而有启发性的实验,打开您的浏览器控制台并评估 { a: 'foo', b: 0 } === { a: 'foo', b: 0 }。是的,false,因为这两个对象虽然值相等,但实际上是存储在内存中不同地址的不同实体。

那么,在评估多个 select 输入中预先 select 的选项时,您如何解决这个问题?嗯,你为你的 option value 属性使用标量,因为 标量 等式 基于 .

Sherif 的解决方案是一种方法(javascript 字符串,这是当您获得对象的 JSON 表示时得到的,实际上是标量),但考虑到您的对象有一个 id 属性,假设是唯一标识符,我会用它来代替:

<script>
  let availableValues = [
    {id: 1, name: "one"},
    {id: 2, name: "two"},
    {id: 3, name: "three"},
    {id: 4, name: "four"}
  ]

  let selectedIds = [1, 2]

  $: selectedValues = availableValues.filter((v) => selectedIds.includes(v.id))
  $: console.log(selectedValues)
</script>

<select multiple bind:value={selectedIds}>
  {#each availableValues as value}
    <option value={value.id}>
      {value.name}
    </option>
  {/each}
</select>

编辑:select编辑值作为外部绑定属性

原理保持不变,但不是从组件内部设置 selectedIds,而是将它们作为 prop 传递(据推测连同整套选项)。所以像这样:

// App.svelte
<script>
  import { onMount } from 'svelte'
  import MySelect from './MySelect.svelte'
  let options = []
  let selectedIds = []

  onMount(async () => {
    // set of options fetched from wherever (API, store, local/session storage, etc.), returns an array of objects similar to your 'availableValues' (each object MUST have an 'id' attribute)
    options = await getOptions()
    // currently selected values fetched from wherever (API, store, local/session storage, etc.), returns an array of ids (integers, UUID strings, etc., whatever matches the 'id' attribute of your options array)
    selectedIds = await getCurrent()
  }

  $: selectedValues = options.filter((o) => selectedIds.includes(o.id))
</script>

<MySelect {options} bind:selectedIds={selectedIds} />
<ul>
  {#each selectedValues as sv (sv.id)}
    <li>{sv.name} [id: {sv.id}]</li>
  {/each}
</ul>

//MySelect.svelte
<script>
  export let options = []   // or hardcoded default values
  export let selectedIds = []  // or hardcoded default values
</script>

<select multiple bind:value={selectedIds}>
    {#each options as option}
        <option value={option.id}>
            {option.name}
        </option>
    {/each}
</select>