如何在 Svelte 中修改父组件的子组件插槽

How to modifying child component's slots from parent components in Svelte

我正在使用这种用法在 Svelte 中开发一个 DataTable 组件:

<script>
    import DataTable from './DataTable.svelte'
    import Column from './Column.svelte'
    
    let items = [
        {id: 1, name: 'Name 1', age: 23},
        {id: 2, name: 'Name 2', age: 28},
        {id: 3, name: 'Name 3', age: 35},
        {id: 4, name: 'Name 4', age: 18},
    ] 
</script>

<DataTable {items} let:item>
        <Column title="Id">{item.id}</Column>
        <Column title="Customer">{item.name}</Column>
        <Column title="Age">{item.age}</Column>
</DataTable>

Column 组件只是作为 DataTable 组件的元定义。在 script 部分(如 this)中还有一些其他方法可以定义 columns,但为了更专注于 script 部分中的业务逻辑,我正在尝试在 script 部分减少组件的视觉效果 definitions/configurations。

因此,Column 组件中的默认 slot 内容应传递给父 DataTable 组件,然后在单元格中呈现。换句话说,父组件应该控制子组件的插槽。

DataTable.svelte:

<script>
    import { setContext, getContext, onDestroy } from 'svelte';
    
    export let items = []
    
    let columns = []
    
    setContext('columns', {
        register: column => {
            columns.push(column)
            
            onDestroy(() => {
                const i = columns.indexOf(column)
                columns.splice(i, 1)
            })
        },
        getColumns: () => {return columns}
    })
</script>
<slot />
{#if items}
    <table width="100%" border="1">
        <thead>
            <tr>
                {#each columns as column}
                    <th>
                        {column.title}
                    </th>
                {/each}
            </tr>
        </thead>
        {#each items as item1, i}
            <tr>
                {#each columns as column}
                    <td>
                        {JSON.stringify(column.$$scope.ctx[0][i])}
                    </td>
                {/each}
            </tr>
        {/each}
    </table>
{/if}

Column.svelte:

<script>
    import { getContext } from 'svelte';
    let columnProps = {...$$props}
    const { register } = getContext('columns')
    register(columnProps) 
</script>

Here is the repl.

在这一行中:{JSON.stringify(column.$$scope.ctx[0][i])} 绑定行显示正确(不确定它是否适用于所有情况,因为索引 0 是硬编码的!)。

但是,内容应该使用第一列、第二列等列的槽({item.id}{item.name} 等)呈现

如何动态渲染插槽或在父组件中渲染插槽以显示正确的插槽内容?

此外,由于在上述用法中将插槽值传递给 Column(子项中没有任何插槽),Svelte 会发出警告 (<Column> received an unexpected slot "default".),这是完全正确的!

实施此 DataTable 组件的正确方法是什么?

正确的做法可能是完全不这样做。我致力于以类似的方式实现选项卡控件,而 Svelte 似乎不太擅长与在多个地方使用的 slots/child 组件进行交互。

要使这项工作在某种程度上起作用,您可以进行一些更改:

  1. 渲染 Column 中的单元格:
<td>
    <slot />
</td>
  1. 通过在 DataTable 中插入槽来渲染整行(它已经包含所有列):
{#each items as item, i}
    <tr>
        <slot {item} />
    </tr>
{/each}
  1. 删除 DataTable<script> 标签后的流氓 <slot/>,因为它没有 item.

  2. 重新分配 columns,否则它不会更新;还要检查该列是否已注册,否则会重复(每行一个):

    register: column => {
        if (columns.find(c => c.title == column.title))
            return;

        columns = [...columns, column];
        
        onDestroy(() => {
            columns = columns.filter(c => c.title != column.title);
        })
    },

REPL

一旦您认为 title 应该是任意 DOM 内容而不仅仅是一个字符串,您可能会后悔。


你绝对不应该访问像 $$scope.ctx 这样的东西,这是一个可以随时更改的实现细节。