【问题标题】:How can I dynamically define a "value" bind in Svelte?如何在 Svelte 中动态定义“值”绑定?
【发布时间】:2020-09-16 21:13:36
【问题描述】:

我是 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 example here.

谁能指出我正确的方向?或者,如果这完全不可能,您能否提出不同的方法?

我的一个想法是将字符串键放入映射中,然后字符串名称引用被调用以计算结果的函数,但这感觉很笨重

【问题讨论】:

  • 我有两个建议:1) 使用带有 id、label ... 等道具的表单元素组件和 2) 并为您的动态配置使用特殊元素
  • 谢谢!这是一个简化的最小示例,我在实际项目中使用自定义 组件(遵循此示例 svelte.dev/repl/3f161dd253624d4ea7a3b8b9e5763e96?version=3.21.0 )。你能解释一下 对这个用例有什么帮助吗?
  • 以下示例: 在 Field.svelte 中用于配置和运行 json 配置作为动态组件。请参见 Field.svelte 第 13 行。
  • 这解决了部分问题:从 JSON 创建表单元素,但它没有设置这些元素之间的反应性,这正是我真正在努力解决的问题。我看不到如何在 JSON 中指定 {field3.value = field1.value + field2.value},然后让 Svelte 建立这种关系。
  • 不错。我喜欢动态派生存储,它使用动态回调(为回调函数生成代码)。函数构造函数对我来说是新的。你取得了很大的进步。

标签: svelte svelte-3


【解决方案1】:

首先,您不能有字符串f1 + f2ct1.fValue=='',将其传递给{ expression }bind:class:use:on: 并期望它能够工作。

因为 Svelte 不是这样工作的。

苗条is a compiler

当你写作时

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

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

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

<!-- or many more -->

REPL

如果你查看编译后的输出 JS,你不会看到字符串 name + namenamename === ''。那里使用的任何变量都会被分析和转换。

您可以阅读我的博客"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我不建议这样做。

【讨论】:

  • 谢谢,这里有很多很好的信息!我会读你的博文。一个澄清:我希望用来构建我的表单的 JSON 配置在编译时可用。我知道如果配置只能在运行时访问,这可能是不可能的。
  • @bjmc 我不太明白编译时可用与运行时可用的区别,能否详细说明一下?
  • 例如,如果在加载 Svelte 应用程序之后/作为 Svelte 应用程序加载的一部分,通过用户浏览器中的 API 检索表单 JSON,那么我会考虑该运行时。但这不是正在发生的事情:当我运行npm run build 时,我将在磁盘上保存表单配置。如果需要,我相信我可以将其作为预处理器来实现,但我想知道我是否可以在 Svelte “内部”完成它。
  • 当您说“内部”苗条时,您指的是什么?苗条编译的一部分?
  • 我想这有点武断,但我的意思是使用 Svelte 为我们提供的开箱即用工具,而不添加类似 github.com/sveltejs/svelte-preprocess 之类的东西,无论如何,我设法得到了一个工作结果,部分使用你的想法。我已将我的结果作为兄弟答案发布,我欢迎任何进一步的建议。感谢您的帮助!
【解决方案2】:

我想我想出了一种方法来达到我想要的结果。关键见解是使用 Svelte 的 derived stores 功能来捕获字段之间的依赖关系。

您可以使用Function constructor 从 JSON 字符串创建函数(这种方法可能存在安全问题——尽管没有eval() 严重;在我的情况下,表单配置是受信任的输入)。然后,将该函数作为 callback to derived() 传递,Svelte 将确保在输入更改时自动重新评估它。

从那里,使用$ 定义subscribe to the store value 的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 导入需要 TypeScript 中的 enabling the resolveJsonModule 功能。

您可以看到a complete working example here,,但 Svelte REPL 似乎还不支持 TypeScript。我不得不删除所有类型注释并将sample.json 重命名为sample.json.js(这在某种程度上违背了练习的重点)。

【讨论】:

    猜你喜欢
    • 2020-07-11
    • 2021-08-15
    • 2019-03-26
    • 2017-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多