Каждая конкретная разработка внешнего кода использует данные и предоставляет им линзы. Любые усилия разработчика, которые не ставят перед собой цель, приводят к расточительному использованию программного обеспечения.
Эта статья посвящена этой чистой цели — получить данные, предоставить линзы. И реализовать такую логику с помощью Svelte — настоящее счастье.
данные есть данные, что это за горстка рендомных линз? текстовый фильтр по имени, если число, показать селектор двойного диапазона, показать стек относительных значений для некоторых и раскрывающийся список для других. охватывает множество вариантов использования для любого набора данных.
Такую реализацию можно легко преобразовать в реактивную информационную панель, таблицу данных или визуализацию.
Мы берем на себя:
- ‹Фильтры› перечисляют, какие ключи фильтруются
- ‹Фильтр›, чтобы обеспечить логику ключ/значение для его изменения и возврата к ‹Фильтрам›
- ‹Результаты› для инкапсуляции предварительно мутированных данных только для чтения
<script> import {data} from './data.js' import Filters from './Filters.svelte' import Results from './Results.svelte' let filtered, textFilter = 'name'; $: filtered = filtered ? filtered : data </script> <div style="display: flex"> <Filters {data} bind:filtered {textFilter} filters={Object.keys(data[0]).filter(i=>!['_id', textFilter].includes(i))} /> <Results {filtered}/> </div>
‹Фильтры›
import Filter from './Filter.svelte' import {getType} from './getType' import {onMount} from 'svelte' export let data, filters, filtered, textFilter; let keys; $: keys = keys = filters .map(i=> ({ key: i, unfiltered: [...new Set(data.map(e=> e[i]))], options: [...new Set(filtered.map(e=> e[i]))], type: getType(data.map(e=> e[i])[0]) }) ) onMount(()=> { !textFilter ? textFilter = keys[0].key :'' }) let appliedFilters = {} const change = (e) => appliedFilters[e.detail.key] = e.detail.selected $: if (Object.keys(appliedFilters).some(i=> appliedFilters[i] && appliedFilters[i].length > 0) || value) { filtered = data .filter(i=> Object.keys(appliedFilters) .filter(e=> appliedFilters[e].length > 0 ) .every(e=> ((getType(appliedFilters[e][0]) === 'string' ? appliedFilters[e].includes(i[e]) : true) && (getType(appliedFilters[e][0]) === 'number' ? i[e] >= appliedFilters[e][0] && i[e] <= appliedFilters[e][1] : true)) ) && (textFilter && i[textFilter].toLowerCase().indexOf(value) !== -1 || !value || value && value.length === 0) ) filtered =filtered } else { filtered = data } let value; //shorthand for textinput
export let key, type, options, filtered, unfiltered, selected = [], appliedFilters; import { createEventDispatcher } from 'svelte'; import Stack from './Stack.svelte' import RangeSlider from "svelte-range-slider-pips"; const dispatch = createEventDispatcher(); function select(e) { selected = [...appliedFilters[key] ? appliedFilters[key] : [], e] dispatch('change', { key: key, selected: selected }); } import chroma from 'chroma-js' let values, keys $: values = [...new Set(filtered.map(i=>i[key]))].map(e=> ({ key: e, value: filtered.filter((i) => i[key] === e).length})) let colors = chroma.scale(['green', 'orange', 'red']).colors(3) // function deselect(e) { // selected = appliedFilters[key].filter(i=> i != e) // selected = selected // dispatch('change', { // key: key, // selected: selected // }); // } function clear(e) { selected = [] dispatch('change', { key: key, selected: selected }); } function rangeChanged(e) { // appliedFilters[key].min = e.detail.values[0] // appliedFilters[key].max = e.detail.values[1] dispatch('change', { key: key, selected: e.detail.values }); } let value; $: items = value ? options.filter(i=> i.toLowerCase().includes(value)) : options
фильтр может включать некоторое идиоматическое определение стековой диаграммы, которое можно использовать в качестве селектора
<div style="cursor: pointer; display: flex; flex-direction: row; align-items: stretch;"> {#each values.sort((a, b) => b.value - a.value) as item, i} <div on:click={item === 'All' ||appliedFilters[key]?.includes(item.key) ? clear() : select(item.key)} style={`color: white; text-align: center; margin: 1px; padding: 3px; background-color: ${colors[i]} ; width: ${item.value/values.map(i=>i.value).reduce((a, b) => a + b, 0)*100}%`}> {#if appliedFilters[key]?.includes(item.key)}x{/if} {item.key}:{item.value} </div> {/each} </div>
несмотря на то, что я уже подкинул в фильтр какую-то самоуверенную логику, постарайтесь абстрагироваться от нее или просто замените эти чертовы 5 строчек кода на свои.
Семантически будет сложно описать более абстрактный пример идеи данных и линз. В этом смысле пример обеспечивает наименьшее количество свойств, которые могут управлять им. и для меня свойства (например, доступ к родительскому компоненту) являются техническим переводом (словами) этой ядерной идеи фронтенд-разработки. Наименее сложный и лаконичный способ предоставить множество линз в наборе данных.
«… в отличие от любого другого типа техники… машины, которые мы создаем с помощью программного обеспечения, сотканы из слов».
от Coders: Создание нового племени и переделка мира Клайв Томпсон
Полный код и рабочая версия доступны здесь: https://svelte.dev/repl/a70793ac01b541a287695e4498aac52e?version=3.40.2