Каждая конкретная разработка внешнего кода использует данные и предоставляет им линзы. Любые усилия разработчика, которые не ставят перед собой цель, приводят к расточительному использованию программного обеспечения.

Эта статья посвящена этой чистой цели — получить данные, предоставить линзы. И реализовать такую ​​логику с помощью 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