У меня есть стройная страница для отображения списка сообщений, мне нужно дать пользователю возможность фильтровать этот список по нескольким условиям, например: год, теги, категория и т. д. Все эти условия должны быть параллельными, как и на веб-сайте Amazon. боковая панель.
Используя производное хранилище, я получил один фильтр, работающий нормально, но все же мне не удается применить 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);
});
Итак, на основе списка сообщений я могу получить массив лет для фильтра, а затем с помощью простого выбора запустить действие фильтрации, и это работает довольно хорошо.
<select bind:value = {$filter.year}>
{#each $years as year}
<option value = {year}>{year}</option>
{/each}
</select>
Как я сказал в начале, суть в том, как усложнить этот сценарий, чтобы превратить простой фильтр в систему с переменным количеством динамических накопительных фильтров.
Спасибо за любую идею.
Вы можете перебрать реквизит объекта с помощью Object.entries
Объедините это со скобками, чтобы прочитать реквизит obj[prop]
Добавьте в массив сокращение, чтобы сделать его проще, и вы получите:
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]))
вместо этого.
Вы можете сначала отфильтровать записи, прежде чем отправлять их в сокращение: Object.entries($filter).filter(([_, value]) => value).reduce(....)
чтобы отфильтровать ключи, которые не имеют значения.
Как «эквивалентная альтернатива» if-else или лучший способ предотвратить двойное return
внутри сокращения?
это альтернатива, лично мне нравится, как она устраняет необходимость в if-else при сокращении.
Чтобы сделать это более универсальным, я бы начал с «автоматического» создания параметров фильтра.
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({});
(Изначально установлено значение {year: null, language: null}
с помощью привязок выбора значения)
Одним из способов фильтрации сообщений может быть это (или использование reduce
, как показывает ответ Стефана Ванраеса)
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
});
Вот РЕПЛ, построенный на этом
Разве не следует проверять, установлен ли фильтр, чтобы было значение для фильтрации? Лайк
if (value) { pre.filter(x => x[key] == value) } else { pre }
Потому что иначе, если фильтр не установлен и все значения равныnull
, пост не показывается?