我有一个页面(此时)仅包含一个按钮。单击此按钮时,会动态加载一个组件,因此如果多次单击该按钮,页面上会出现多个组件。每个组件都是一个小表单,单击表单的保存按钮即可提交。
到目前为止一切顺利,现在,当保存组件时,
save
按钮将被隐藏,并且垃圾按钮将可见。当点击这个 garbage
按钮时,该组件应该被删除。
为此,我需要跟踪创建的组件,因此我随机创建一个字符串,将其作为属性的一部分传递。作为属性的一部分,我还传递了一个回调函数,该函数接收组件 ID,并将在单击
garbage
按钮时执行。
单击
garbage
按钮后,它将调用回调函数,该函数将通过运行过滤器从组件列表中删除该组件。
假设我有 2 个元素。在每个元素上,我显示了创建它所用的随机 ID。
如果我删除第一个元素,我希望只保留第二个元素。这是以某种方式发生的,我在屏幕上看到的第二个元素具有正确的 ID,但由于某种原因,它的数据(文本字段中的数据)不正确,但它是刚刚删除的元素的数据。
知道发生了什么事吗?
这是包含添加元素按钮的主页:
<script>
import InventoryItem from "../components/InventoryItem.svelte";
export let formData = {};
export let inventoryID;
let inventoryItemComponents = [];
// Keeps track of the number of components added to inventoryItemComponents and the same
/**
* loadNewItemForm loads a new InventoryItem component and adds it to a
* list of existing components inventoryItemComponents
*/
const loadNewItemForm = () => {
// create random string that will be used as a component identifier.
const componentID = Math.random().toString(36).slice(2);
inventoryItemComponents = [
...inventoryItemComponents,
[InventoryItem, {
formAction: "?/saveItem",
inventoryID: inventoryID,
componentID: componentID,
deleteCallbackFn: removeFromList
}]
];
}
const removeFromList = id => {
let updatedList = inventoryItemComponents.filter( inventoryComponent => inventoryComponent[1].componentID != id )
inventoryItemComponents = [...updatedList];
}
</script>
<div>
<div class="inventory-items-list">
{#each inventoryItemComponents as [component, props]}
<svelte:component this={component} {...props}>
</svelte:component>
{/each}
<!-- New item button -->
<div class="float-right">
<div class="md:p-4 flex item-center justify-center">
<button
class="btn btn-active btn-primary"
on:click={loadNewItemForm}>
New Item
</button>
</div>
</div>
</div>
</div>
这是正在加载的组件:
<script>
import {enhance} from '$app/forms';
import ProductLookup from "./ProductLookup.svelte";
export let inventoryID;
export let componentID;
export let deleteCallbackFn;
/**
* Local props.
*/
let inventoryItemID = "";
let total;
let productData;
let purchasedPrice = 0;
let quantity = 1;
// Determines whether the save button should be displayed or not.
let hideSaveBtn = false;
// Holds a list of errors.
let errors = {};
/**
* Builds the request's payload to the add-inventory-item endpoint.
* We won't perform any validation at this point.
*/
const buildPayload = () => {
return {
"inventory_id": inventoryID,
"product": productData,
"purchased_price": purchasedPrice.toString(),
"quantity": quantity,
}
}
/**
* Saves a new inventory item.
*
* @param e
* @returns {Promise<void>}
*/
const save = async e => {
e.preventDefault();
// reset all errors, so they can be repopulated again from scratch.
errors = {};
// perform call to internal API.
const response = await fetch('/api/add-inventory-item', {
method: 'POST',
body: JSON.stringify(buildPayload()),
});
const parsedResponse = await response.json()
if (parsedResponse.errors) {
errors = parsedResponse.errors;
return;
}
inventoryItemID = getInventoryItemIDFromResponse(parsedResponse);
// if the record was successfully saved then hide the save button since it shouldn't be allowed to be saved again.
hideSaveBtn = true;
}
/**
* Goes through the API response from adding an item into an inventory and
* gets the inventory item ID. This has to be done because from the API we're returning
* the whole inventory object when adding a new item, so we need to figure the ID out.
*
* @param apiResponse
*/
const getInventoryItemIDFromResponse = apiResponse => {
const inventoryItems = apiResponse.data.attributes.items;
const item = inventoryItems.filter(item => item.product.id === productData.value);
return item[0].id;
}
const deleteItem = () => {
deleteCallbackFn(componentID);
}
/*
* Reactive properties.
*/
// Recalculate total every time either purchasedPrice or quantity change.
$: total = purchasedPrice * quantity;
</script>
<div class="card w-full bg-base-100 shadow-xl mt-5">
<form method="POST" class="p-6" use:enhance>
<h2>ID: {componentID}</h2>
<div class="md:grid md:grid-cols-12 gap-0.5">
<!-- Inventory Item ID -->
<input name="inventory_item_id" type="hidden" value={inventoryItemID} />
<!-- ID -->
<input name="inventory_id" type="hidden" value={inventoryID} />
<!-- Product lookup -->
<div class="col-span-4">
<div class="md:w-full md:px-3 mb-6 md:mb-0">
<label class="block tracking-wide text-gray-700 text-xs font-bold mb-1" for="product">
Product <span class="text-red-600">*</span>
</label>
<ProductLookup
bind:value={productData}
customClass="input w-full py-4 font-medium bg-gray-100 border-gray-200 text-sm
focus:outline-none focus:border-gray-400 focus:bg-white"
id="product"
name="product"
/>
{#if errors?.product_id }
<small class="text-amber-800">{ errors?.product_id[0]}</small>
{/if}
</div>
</div>
<!-- Quantity -->
<div class="col-span-2">
<div class="md:w-full md:px-3 mb-6 md:mb-0">
<label class="block tracking-wide text-gray-700 text-xs font-bold mb-1" for="quantity">
Qty <span class="text-red-600">*</span>
</label>
<input
bind:value={ quantity }
class="input w-full md:max-w-xs py-4 font-medium bg-gray-100 border-gray-200 text-sm
focus:outline-none focus:border-gray-400 focus:bg-white"
id="quantity"
min="1"
name="quantity"
type="number"
/>
{#if errors?.quantity }
<small class="text-amber-800">{ errors?.quantity[0]}</small>
{/if}
</div>
</div>
<!-- Purchased Price -->
<div class="col-span-2">
<div class="md:w-full md:px-3 mb-6 md:mb-0">
<label class="block tracking-wide text-gray-700 text-xs font-bold mb-1" for="purchased_price">
Purchased Price <span class="text-red-600">*</span>
</label>
<input
bind:value={ purchasedPrice }
class="input w-full md:max-w-xs py-4 font-medium bg-gray-100 border-gray-200 text-sm
focus:outline-none focus:border-gray-400 focus:bg-white"
id="purchased_price"
min="0"
name="purchased_price"
type="number"
/>
{#if errors?.purchased_price }
<small class="text-amber-800">{ errors?.purchased_price[0]}</small>
{/if}
</div>
</div>
<!-- Total -->
<div class="col-span-2">
<div class="md:w-full md:px-3 mb-6 md:mb-0">
<label class="block tracking-wide text-gray-700 text-xs font-bold mb-1" for="total">
Total
</label>
<input class="input input-ghost w-full max-w-xs" disabled id="total" type="text" value={total}/>
</div>
</div>
<!-- Buttons -->
<div class="col-span-2">
<!-- Save button -->
{#if (!hideSaveBtn)}
<div class="">
<a href="#" class="flex items-center justify-center link link-hover" on:click={save}>
<svg class="w-6 h-6 text-gray-800" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 19H1.933A.97.97 0 0 1 1 18V5.828a2 2 0 0 1 .586-1.414l2.828-2.828A2 2 0 0 1 5.828 1h8.239A.97.97 0 0 1 15 2v4M6 1v4a1 1 0 0 1-1 1H1m11 8h4m-2 2v-4m5 2a5 5 0 1 1-10 0 5 5 0 0 1 10 0Z"/>
</svg>
</a>
</div>
{:else }
<div class="">
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="green" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m7 10 2 2 4-4m6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
</svg>
<a href="#" class="flex items-center justify-center link link-hover" on:click={deleteItem}>
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
<path stroke="darkred" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1 1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z"/>
</svg>
</a>
</div>
{/if}
</div>
</div>
<div class="flex-row flex">
<div class="p-2 float-left">
<span class="px-3 mb-6 text-2xs">(<span class="text-red-600">*</span>) Required</span>
</div>
</div>
</form>
</div>
{#each}
需要一把钥匙,例如
{#each inventoryItemComponents as [component, props] (props.componentID)}
除非使用密钥,否则 Svelte 不知道哪些数据属于哪个组件。删除时,last组件实例将被删除,并且每个剩余组件的 props 都会被更新。
这意味着最后一个组件的 props 是在第一个实例上设置的,因此依赖于 props 的任何反应性更新都会正确更新,但本地状态不会。