如何在 svelte 中制作累积过滤系统(如亚马逊侧边栏)

How to make a cumulative filter system in svelte (like amazon sidebar)

我有一个显示 post 列表的简洁页面,我需要让用户能够按多种条件过滤该列表,例如:年份、标签、类别等。所有这些条件应该是并发的,就像亚马逊网站侧边栏一样。

使用派生商店,我的一个过滤器工作正常,但我仍然无法同时应用 2 或 3 个过滤器。

对于“年”过滤器,我有以下代码:

import { readable, writable, derived } from "svelte/store";

export let data;
let posts = readable(data.posts);

const extractColumn = (arr, column) => arr.map((x) => x[column]);
const allYears = extractColumn(data.posts, "year");
const years = readable ([...new Set(allYears)]);
const filter = writable({ year: null });

const filteredPosts = derived([filter, posts], ([$filter, $posts]) => {
        if (!$filter.year) return $posts;
        return $posts.filter((x) => x.year === $filter.year);
    });

因此,基于 posts 列表,我可以获得过滤器的年份数组,然后使用简单的 select 触发过滤操作,这工作得很好。

<select bind:value={$filter.year}>
    {#each $years as year}
        <option value={year}>{year}</option>
    {/each}
</select>

正如我在开头所说的,关键是如何为这个场景增加复杂性,将一个简单的过滤器变成一个系统,具有可变数量的动态累积过滤器。

感谢任何想法。

您可以使用 Object.entries 遍历对象的道具 将它与括号符号结合起来阅读 props obj[prop] 放入数组 reduce 以使其更简单,您将得到:

return Object.entries($filter)
  .reduce(
    (pre, [key, value]) => pre.filter(x => x[key] == value), 
    $posts
  );

另外,您实际上可以将数组存储在过滤器中,这样您就可以过滤多个值:

{
  "year": [2022],
  "colour": ['red', 'green']
}

在这种情况下,请改用 pre.filter(x => values.includes(x[key]))

为了使其更加通用,我将从 'automatically' 生成过滤器选项开始

    const filterOptions = derived(posts, $posts => {

        // set the columns you'd like to have a filter for
        const columns = ['year', 'language']
        //  const columns = Object.keys($posts[0]) // or change to this to have a filter for every property

        return columns.reduce((filterOptions, column) => {
            filterOptions[column] = extractUniqueColumnValues($posts, column)
            return filterOptions
        }, {})
    })

    function extractUniqueColumnValues (arr, column) {
        return Array.from(new Set(arr.map((x) => x[column])))
    } 

并像这样显示它们

{#each Object.entries($filterOptions) as [column, values]}
{column}
<select bind:value={$filter[column]}>
    <option value={null} ></option>
    {#each values as value}
    <option value={value}>{value}</option>
    {/each}
</select>
{/each}

filter 可以简单地是

const filter = writable({});

(它最初通过 select 值绑定设置为 {year: null, language: null}

过滤帖子的一种方法可能是这个(或使用 reduce,如 Stephane Vanraes 的答案所示)

const filteredPosts = derived([filter, posts], ([$filter, $posts]) => {
        Object.entries($filter).forEach(([key, value]) => {
            if(value !== null) $posts = $posts.filter(x => x[key] == value)
        })      
        return $posts
    });

这是在此基础上构建的 REPL