Я только что перешёл с React(+Redux) на Svelte и столкнулся с кучей проблем, которые не могу решить самостоятельно. Основной из них — привязка глубоко вложенных объектов.
Предположим, мы получили структуру данных, похожую на структуру @hgl Как использовать хранилище Svelte с древовидным вложенным объектом?. Благодаря ответу @rixo я понял, как связать компоненты «Папка» и «Файл» на одной странице, когда я отображаю все сразу и нет маршрутизации. Но что, если я хочу создать отдельную страницу для каждой папки и получать к ней доступ через какой-нибудь id
(давайте считать, что name
prop как id
, поскольку он уникален в данной модели), например:
UPD Вот REPL из исходного вопроса
Поэтому я хочу привязать к тегам моей страницы не корневой, а вложенный объект. Мои мысли:
Я могу попытаться сделать реактивное объявление (НЕ рассчитанное prop🤦, хотя семантически это так) на моей странице с помощью $
, например:
// ./src/folders/[id]/+page.svelte
<script lang = "ts">
const urlParams = new URLSearchParams(window.location.search);
const id = urlParams.get("id");
import { root } from './stores.js'
$: thisFolder = $root.find((f) => f.name === id) // root level search
?? $root.map((pf) => pf.files?.find((f) => f.name === id)).find((f) => f) // 2nd level search, might continue if needed...
if (!thisFolder) {
eror(404, { message: "folder not found", });
}
</script>
<div>
<span>Folder name: </span>
<input bind:value = {thisFolder.name}/> // !!! binding here would not work
</div>
Но это не сработает, я думаю, это связано с тем, что мы не используем здесь ту же переменную, а используем реактивную (расчетную). Он отреагирует на изменения в магазине, но не передаст их обратно в магазин. Пожалуйста, поправьте меня, если я ошибаюсь. UPD Вот REPL
Другой вариант — создать хранилище Devived, в которое я передам id
и получу соответствующую папку, но, насколько я знаю, производные хранилища не являются привязываемыми, более того, я понятия не имею, как передать параметр id
в производное хранилище. UPD вот REPL
Моя третья задача — прослушивать изменения во входных данных и обновлять объекты, например, on:change
, но мне интересно, можно ли это сделать только с помощью привязок (также именно так я бы сделал это в React, поэтому для меня это выглядит нереактивным)
Другой подход состоит в том, чтобы сделать объекты максимально простыми, чтобы каждое перенаправление URL-адреса загружалось только в соответствии с папкой id
, но это также похоже на то, как будто я сделал бы это в реакции, и это также не совсем подходит для моего приложения.
Похоже, неделю назад я видел соответствующую реализацию для своей борьбы здесь, на SO, но не могу найти эту тему, так как
UPD 1 Добавлено несколько REPL, похоже, первый подход правильный, позже проверю и обновлю вопрос.
UPD 2 Вот подход @hackape REPL, это работает, но немного не так, как я ожидал. Если я изменю значение в одном компоненте, другой будет повторно отображаться только после того, как я изменю какое-либо значение, а представление JSON вообще не обновится. Полагаю, это потому, что id
вышел наружу derived
, но не уверен. Продолжайте расследование...
🤔 А знаете ли вы, что...
С Svelte можно создавать прогрессивные веб-приложения (PWA) с поддержкой оффлайн-режима.
Обновление: я вижу ваше $:
реактивное объявление решение, оно работает нормально. Мой ответ посвящен тому, как решить проблему с помощью магазина.
Магазин не является чем-то лучшим или чем-то еще, вы можете использовать любой подход, который считаете нужным. Использование хранилища имеет больше смысла, когда вам нужно инкапсулировать часть логики для совместного использования кода.
Вот как можно создать нужное производное хранилище.
<script>
import { derived } from 'svelte/store';
import { root } from './stores.js'
import Folder from "./Folder.svelte";
function findFolder(root, id) {
let target = root.find(child => child.name === id);
if (target) return target;
return root.reduce((found, child) => {
if (found) return found;
if (child.type === 'folder') {
return findFolder(child.files, id);
}
return null;
}, null)
}
const specificFolder = id => derived(root, $root => findFolder($root, id))
const goatsFolder = specificFolder(id);
const id = "Goats"
</script>
<span>Goats folder</span>
<Folder files = {$goatsFolder.files} expanded = {true} />
<div>{JSON.stringify($goatsFolder)}</div>
<br />
<span>Complete store</span>
<Folder files = {$root} expanded = {true} />
<div>{JSON.stringify($root)}</div>
Хранилище derived
из "svelte/store"
lib не может быть привязано (например, <Folder bind:files = {$goatsFolder.files}>
не будет работать), поскольку производное хранилище по своей конструкции является хранилищем только для чтения, к нему не прикреплен метод set
.
Но этот недостаток можно легко исправить, превратив его в доступное для записи хранилище, к которому можно привязаться, поскольку svelte store — это всего лишь контракт интерфейса. Это не зависит от реализации. Пока вы соблюдаете контракт, вы можете «обмануть» среду выполнения.
store = {
subscribe: (subscription: (value: any) => void) => (() => void),
set?: (value: any) => void
}
Исправление обезьян или даже создание собственной реализации магазина — все это законные решения и приветствуются. Подробнее читайте в документации о «Договоре магазина». Вот просто обезьяний патчер:
const specificFolder = id => {
let setter = null;
const store = derived(root, ($root, _set) => {
_set(findFolder($root, id));
setter = _set;
});
// monkey patch to add a `set` method.
store.set = (newValue) => {
setter(newValue);
// notify upstream root store
root.update($root => $root);
};
return store
}