Как я могу динамически определить привязку значений в Svelte?

Я новичок в Svelte и пытаюсь использовать его для написания одностраничного приложения, которое будет отображать форму с некоторыми значениями полей, динамически вычисляемыми на основе других полей. В идеале я хотел бы управлять отрисовкой формы с помощью статических файлов конфигурации JSON (чтобы упростить создание новых форм с помощью других инструментов, выводящих JSON).

Я хочу иметь возможность динамически определять отношения между полями формы, чтобы, когда пользователь вводит значения, вычисленные поля автоматически пересчитывались.

Я хотел бы получить что-то похожее на это (но, очевидно, это не работает):

<script>
let objFormConfig = JSON.parse(`{
    "formElements": [
        {
              "id": "f1",
              "label": "First value?"
        },
        {
              "id": "f2",
              "label": "Second value?"
        },
        {
               "id": "f2",
               "label": "Calculated automatically",
               "computed": "f1 + f2"
        }
    ]
}`);
</script>
<form>
{#each objFormConfig.formElements as item}
    <div>
        <label for="{item.id}">{item.label}
        {#if item.computed}
            <input type=number id={item.id} value={item.computed} readonly/>
        {:else}
            <input type=number id={item.id} bind:value={item.id}/>
        {/if}
        </label>
    </div>
{/each}
</form>

Действующий (нерабочий) пример REPL здесь.

Может кто-то указать мне верное направление? Или, если это совершенно невозможно, не могли бы вы предложить другой подход?

Одна из моих идей - добавить на карту строковые ключи, а затем строковые имена, ссылающиеся на функции, которые вызываются для вычисления результата, но это кажется неуклюжим


person bjmc    schedule 16.09.2020    source источник
comment
У меня есть два предложения: 1) использовать компоненты элемента формы с такими свойствами, как id, label .... и 2) и использовать специальный элемент ‹svelte: component) для вашей динамической конфигурации.   -  person voscausa    schedule 17.09.2020
comment
Спасибо! Это упрощенный минимальный пример, и я использую настраиваемый компонент ‹Field /› (следуя этому примеру svelte.dev/repl/3f161dd253624d4ea7a3b8b9e5763e96?version=3.21.0) в реальном проекте. Не могли бы вы объяснить, как ‹svelte: component› поможет в этом случае использования?   -  person bjmc    schedule 17.09.2020
comment
Следуя примеру: ‹svelte: component ..› используется в Field.svelte для настройки и запуска конфигурации json как динамических компонентов. См. Строку 13 Field.svelte.   -  person voscausa    schedule 17.09.2020
comment
Это решает часть проблемы: создание элементов формы из JSON, но не устанавливает реактивность между этими элементами, с чем я действительно борюсь. Я не понимаю, как указать {field3.value = field1.value + field2.value} в JSON, а затем заставить Svelte сделать эти отношения живыми.   -  person bjmc    schedule 17.09.2020
comment
Пример снова решает эту проблему с помощью реактивного хранилища; использование привязок магазина для привязки значений формы;   -  person voscausa    schedule 17.09.2020
comment
Йо придется во многом разобраться (как новичок). Сначала станьте знакомым со Svelte. Вам будет непросто построить что-то, что удовлетворит все ваши текущие и будущие потребности.   -  person voscausa    schedule 17.09.2020
comment
В этом примере автор сохраняет значения в реактивном хранилище, но на самом деле они не демонстрируют реактивного поведения между полями. Если вы посмотрите на CheckBox.svelte, строка 9, они пишут только console.log - он фактически не оценивает fDisable как код. Снятие флажка не отключает TextArea.   -  person bjmc    schedule 17.09.2020
comment
Вам будет непросто построить что-то, что удовлетворит все ваши текущие и будущие потребности.   -  person voscausa    schedule 17.09.2020
comment
@voscausa Спасибо за вашу помощь! Я получил рабочий результат с некоторыми вашими предложениями.   -  person bjmc    schedule 29.09.2020
comment
Отлично. Мне нравится динамическое производное хранилище, в котором используется динамический обратный вызов (с сгенерированным кодом для функции обратного вызова). Конструктор функций был для меня новым, вы добились большого прогресса.   -  person voscausa    schedule 30.09.2020


Ответы (2)


Прежде всего, вы не можете иметь строку f1 + f2 или ct1.fValue=='', передавать ее в { expression }, bind:, class:, use:, on: и ожидать, что она сработает.

Потому что Svelte так не работает.

Svelte является компилятором.

Когда ты пишешь

<!-- expression -->
{ name + name }

<!-- or bind: -->
<input bind:value="{name}" />

<!-- or dynamic attribute -->
<input disabled="name === ''" />

<!-- or many more -->

REPL

если вы посмотрите на скомпилированный выходной JS, вы не увидите строку name + name, name или name === ''. Какая бы переменная ни использовалась, анализируется и преобразуется.

Вы можете прочитать мой блог Compile Svelte in your Head, чтобы понять подробнее об этом.


Теперь, что касается любого предложения о том, как сделать эту работу, я бы сначала предложил изменить файлы конфигурации JSON (если возможно):

например, если у вас есть:

{
  "formElements": [
    {
      "id": "f1",
      "label": "First value?"
    },
    {
      "id": "f2",
      "label": "Second value?"
    },
    {
      "id": "f2",
      "label": "Calculated automatically",
      "computed": {
        "type": "sum",
        "variables": ["f1", "f2"]
      }
    }
  ]
}

Затем вы можете реализовать производные поля с помощью:

<input type=number id={item.id} value={compute(item.computed)} readonly/>

Вы можете ознакомиться с этим REPL

Если изменить formConfig невозможно, вам придется самостоятельно проанализировать и оценить выражение.

Чрезмерно упрощенный пример синтаксического анализа + вычисления выражения: REPL. Я бы не рекомендовал делать это таким образом.

person Tan Li Hau    schedule 21.09.2020
comment
Спасибо, здесь много полезной информации! Я прочитаю твой пост в блоге. Одно уточнение: конфигурация JSON, которую я надеюсь использовать для создания своих форм, доступна во время компиляции. Я понимаю, что это было бы невозможно, если бы конфигурация была доступна только во время выполнения. - person bjmc; 23.09.2020
comment
@bjmc, я не совсем понял разницу между доступными во время компиляции и во время выполнения, можете подробнее рассказать об этом? - person Tan Li Hau; 24.09.2020
comment
Например, если форма JSON была получена через API в браузере пользователя после / как часть загрузки приложения Svelte, я бы рассмотрел эту среду выполнения. Но это не то, что происходит: у меня будет конфигурация формы на диске, когда я буду запускать npm run build. Я считаю, что могу реализовать это как препроцессор, если мне нужно, но мне интересно, смогу ли я сделать это внутри Svelte. - person bjmc; 24.09.2020
comment
что вы имеете в виду, когда говорите, что внутри стройная? часть стройной компиляции? - person Tan Li Hau; 25.09.2020
comment
Я предполагаю, что это произвольно, но я имел в виду, используя инструменты, которые Svelte предоставляет нам прямо из коробки, без добавления чего-то вроде github.com/sveltejs/svelte-preprocess В любом случае, мне удалось получить рабочий результат, частично используя ваши идеи. Я опубликовал свой результат как ответ от брата, и буду рад любым дальнейшим предложениям. Спасибо за помощь! - person bjmc; 26.09.2020

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

Вы можете использовать конструктор функций для создания функции из строки JSON (при таком подходе могут возникнуть проблемы с безопасностью - хотя и менее серьезные, чем eval(); в моем случае конфигурация формы является надежным вводом). Затем вы передаете эту функцию как обратный вызов в производную (), и Svelte автоматически переоценивает ее. при изменении входов.

Оттуда относительно просто определить компоненты Svelte, которые подписываются на стоимость магазина с помощью $.

Вот как выглядит мой stores.ts файл:

import { Writable, writable, derived } from 'svelte/store';
import { fields } from "./sample.json";

export const fieldMap: Map<string, Writable<number>> = new Map();

const computedFields = fields.filter((f) => f.computed);
const userFields = fields.filter((f) => !f.computed);

userFields.forEach((field) => {fieldMap[field.id] = writable(0)})

type JsonFunction = (values: number[]) => number;

for (const cf of computedFields) {
    const fromStatic = new Function(...cf.computed.args, "return " + cf.computed.body);
    const derivedFunction: JsonFunction = (values: number[]) => fromStatic(...values);
    const arg0: Writable<number> = fieldMap[cf.computed.args[0]];
    const moreArgs: Array<Writable<number>> = [];
    for (const arg of cf.computed.args.slice(1)) {
        moreArgs.push(fieldMap[arg]);
    }
    fieldMap[cf.id] = derived([arg0, ...moreArgs], derivedFunction);
};

С sample.json, который выглядит так:

{
  "fields": [
    {
      "label": "First value?",
      "id": "f1"
    },
    {
      "label": "Second value?",
      "id": "f2"
    },
    {
      "label": "Summed",
      "id": "f3",
      "computed": {
        "args": [
          "f1",
          "f2"
        ],
        "body": "f1 + f2"
      }
    },
    {
      "label": "Doubled",
      "id": "f4",
      "computed": {
        "args": [
          "f3"
        ],
        "body": "f3 * 2"
      }
    }
  ]
}

Обратите внимание, что для такого импорта напрямую из JSON требуется включение функции resolveJsonModule в Машинопись.

Здесь вы можете увидеть полный рабочий пример здесь, но Svelte REPL не похоже, еще поддерживает TypeScript. Мне пришлось удалить все аннотации типов и переименовать sample.json в sample.json.js (что несколько противоречит сути упражнения).

person bjmc    schedule 26.09.2020