如何在 Svelte 中动态定义 "value" 绑定?
How can I dynamically define a "value" bind in 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 example here.
谁能指出我正确的方向?或者,如果这完全不可能,您能否建议一种不同的方法?
我的一个想法是将字符串键放入映射中,然后字符串名称引用调用函数来计算结果,但这感觉很笨拙
首先,您不能将字符串 f1 + f2
或 ct1.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 -->
如果查看编译输出的 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,则您必须自己解析和计算表达式。
一个 over-simplified 解析 + 计算表达式的例子:REPL。 我不建议这样做。
我想我想出了一个方法来达到我想要的结果。关键见解是使用 Svelte 的 derived stores 功能来捕获字段之间的依赖关系。
您可以使用 Function constructor to create a function from a JSON string (there may be security issues with this approach—though less serious than eval()
; in my case the form config is trusted input). Then, you pass that function as a 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
(这在某种程度上破坏了练习的重点)。
我是 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.
谁能指出我正确的方向?或者,如果这完全不可能,您能否建议一种不同的方法?
我的一个想法是将字符串键放入映射中,然后字符串名称引用调用函数来计算结果,但这感觉很笨拙
首先,您不能将字符串 f1 + f2
或 ct1.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 -->
如果查看编译输出的 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,则您必须自己解析和计算表达式。
一个 over-simplified 解析 + 计算表达式的例子:REPL。 我不建议这样做。
我想我想出了一个方法来达到我想要的结果。关键见解是使用 Svelte 的 derived stores 功能来捕获字段之间的依赖关系。
您可以使用 Function constructor to create a function from a JSON string (there may be security issues with this approach—though less serious than eval()
; in my case the form config is trusted input). Then, you pass that function as a 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
(这在某种程度上破坏了练习的重点)。