Svelte应用程序中的状态管理

136 阅读6分钟

**TL;DR:**作为一个前端开发者,最关键的技能之一是如何在你的应用程序中管理状态。将应用程序的状态与数据变化同步并管理不同组件之间的共享状态的能力是构建现代前端应用程序的一个强烈要求。一些状态管理库已经被开发出来,某些框架有一个事实上的状态管理库,它们自然能很好地工作(例如,ReactjsRedux)。

在本教程中,你将学习如何在Svelte应用程序中使用Svelte的内置状态管理进行状态管理。

我们将建立什么

在本教程中,你将在一个用Svelte构建的购物车中实现状态管理。你将从一个预制的电子商务应用开始,该应用销售不同类型的商品。然而,这个应用程序还不完整,因为它的购物功能还没有实现,即向购物车添加产品和管理购物车物品。使用Svelte商店,你将为该应用程序添加以下功能。

  • 从中心状态的商店加载产品
  • 向购物车中添加物品
  • 显示添加到购物车中的物品
  • 计算购物车中商品的总价格
  • 增加和减少购物车中商品的数量
  • 从购物车中删除物品

前提条件

要完成这个练习,有几个要求。

所有这些都准备好了,你就可以继续进行练习了。

克隆演示电子商务项目

首先,你将克隆项目的起点并运行它。运行下面的命令,在你的机器上克隆一个电子商务项目的副本。

git clone --single-branch --branch base-project https://github.com/coderonfleek/svelte-store

一旦你在你的系统上有了这个项目,进入项目的根目录,用下面的命令安装依赖。

cd svelte-store
npm install

安装好所有的依赖项后,你就可以用下面的命令运行该项目。

npm run dev

这个命令将启动一个本地开发服务器,地址是 http://localhost:5000.在你的浏览器上导航到这个地址,你会看到下面显示的页面。

"Homepage - E-commerce App"

目前,当你在任何一个产品上点击Add to Cart ,该按钮没有任何作用。该主页的源代码位于以下文件中 src/views/Home.svelte.目前,产品是从页面上的一个products 数组加载的,使用一个Product 组件来显示产品。

<main>
  <div class="home container">
    <div class="row">
      <div class="col-md-9 pt-5">
        <div
          class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-3 row-cols-xl-3"
        >
          {#each products as product}
          <Product {product} />
          {/each}
        </div>
      </div>
      <div class="col-md-3 pt-5">
        <Cart />
      </div>
    </div>
  </div>
</main>

在右栏,一个Cart 组件用来显示已经添加到购物车的物品和它们各自的数量,目前也是硬编码的组件。Cart 组件还包括一个Checkout 按钮,点击后将导航到下面显示的完整的购物车详细信息页面。

"Cart details page - E-commerce App"

购物车详细信息页面的源代码可以在以下文件中找到 src/views/ShoppingCart.svelte.它使用一个CartItem 组件来显示任何添加到购物车中的商品,并有按钮来增加或减少购物车中商品的数量或完全删除它。Cart 组件也显示在右栏,就像它在主页上一样。目前,这些按钮在被点击时没有任何作用。

<main>
  <div class="home container">
    <div class="row">
      <div class="col-md-8 pt-5">
        {#each cart as product}
        <CartItem {product} />
        {/each}
      </div>
      <div class="col-md-4 pt-5">
        <Cart />
      </div>
    </div>
  </div>
</main>

这个页面上显示的购物车项目是从cart 数组中加载的,目前是在页面上硬编码的。更重要的是,目前点击任何一个按钮都没有任何作用。应用程序中使用的所有组件都包含在 src/components文件夹中。

创建状态管理商店

为了开始实现上节中列出的功能,你需要在应用程序中设置一个中央状态管理存储。在 文件夹中创建一个 store.jssrc 文件夹中,然后添加以下代码。

import { writable, derived } from "svelte/store";

export const products = writable([
  {
    id: 1,
    name: "Chelsea Shoes",
    price: 200,
    shortdesc: "Best Drip in the Market",
    url: "images/chelsea-shoes.png"
  },
  {
    id: 2,
    name: "Kimono",
    price: 50,
    shortdesc: "Classy, Stylish, Dope",
    url: "images/kimono.png"
  },
  {
    id: 3,
    name: "Watch",
    price: 2500,
    shortdesc: "Elegance built in",
    url: "images/rolex.png"
  },
  {
    id: 4,
    name: "Wallet",
    price: 80,
    shortdesc: "Sleek, Trendy, Clean",
    url: "images/wallet.png"
  },
  {
    id: 5,
    name: "Lady Handbags",
    price: 230,
    shortdesc: "Fabulous, Exotic, Classy",
    url: "images/handbag.png"
  },
  {
    id: 6,
    name: "Casual Shirts",
    price: 30,
    shortdesc: "Neat, Sleek, Smart",
    url: "images/shirt.png"
  }
]);

export const cart = writable([]);

上面的商店包含两个可写属性。

  • products 属性持有电子商务应用中包含的所有产品。在一个生产场景中,你想从一个远程API加载产品。
  • cart 属性是一个数组,用于保存用户添加到购物车中的物品。默认情况下这是空的。

这些属性是实现上述功能所需的全部内容。

从商店加载产品到主页

第一个任务是确保产品从商店加载,而不是硬编码在 Home.svelte主页上。找到 src/views/Home.svelte并用以下代码替换其内容。

<script>
  import Product from "../components/Product.svelte";
  import Cart from "../components/Cart.svelte";
  import { products } from "../store";
</script>

<main>
  <div class="home container">
    <div class="row">
      <div class="col-md-9 pt-5">
        <div
          class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-3 row-cols-xl-3"
        >
          {#each $products as product}
            <Product {product} />
          {/each}
        </div>
      </div>
      <div class="col-md-3 pt-5">
        <Cart />
      </div>
    </div>
  </div>
</main>

在上面的更新中,products 变量现在已经被商店属性products 所取代,该属性引用商店来加载产品。现在刷新你的主页,你不会看到任何变化,但你知道你的产品现在正从商店中加载。

将商品添加到购物车中

下一个任务是实现允许用户在主页上点击Add to Cart ,并看到它被添加到右栏的购物车小部件中的功能。如前所述,页面上显示的产品是用一个Product 组件来管理的。找到这个组件的位置 src/components/Product.svelte并用以下代码替换其内容。

<script>
  import { cart } from "../store";
  export let product;

  let inCart = false;

  $: {
    $cart.forEach((item) => {
      if (item.id == product.id) {
        inCart = true;
      }
    });
  }

  function addToCart() {
    if (!inCart) {
      let updatedCart = [...$cart, { ...product, quantity: 1 }];

      cart.set(updatedCart);
    } else {
      alert("Item already added to Cart");
    }
  }
</script>

<main>
  <div class="col mb-4">
    <div class="card">
      <img src={product.url} class="card-img-top" alt="..." />
      <div class="card-body">
        <h5 class="card-title">{product.name}</h5>
        <p class="card-text">
          ${product.price}
          <br />
          <small>
            {product.shortdesc}
          </small>
        </p>
        <button
          on:click={() => addToCart()}
          class="btn btn-primary"
          disabled={inCart}>{inCart ? "Added" : "Add to Cart"}</button
        >
      </div>
    </div>
  </div>
</main>

这个文件的更新为该组件添加了两个属性。

  • 对商店中的cart 属性的引用
  • inCart 检查使用该组件的产品是否已被添加到商店中

当点击Add to Cart 按钮时,addToCart 函数通过更新商店中的cart 属性来向购物车中添加一个商品。这个函数首先检查该产品是否已经在购物车中。如果是,就会显示一个警告,表明该产品已经被添加。如果不是,产品将被添加到购物车中。

如果产品已经被添加,inCart 属性也被用于模板中,以禁用Add to Cart 按钮。

接下来,Cart 组件需要显示已经添加到购物车中的产品,同时显示总价。找到这个 src/components/Cart.svelte文件,用下面的代码替换其内容。

<script>
  import { link } from "svelte-spa-router";
  import { cart } from "../store";

  $: totalPrice = $cart.reduce((total, next) => {
    console.log($cart);
    return total + next.quantity * next.price;
  }, 0);
</script>

<main>
  <div class="card">
    <div class="card-body">
      <h5 class="card-title">Your Cart</h5>
      {#if $cart.length == 0}
        <p>Your Cart is Empty</p>
      {/if}
    </div>
    <ul class="list-group list-group-flush">
      {#each $cart as item}
        <li
          class="list-group-item d-flex justify-content-between align-items-center"
        >
          {item.name}
          <span class="badge badge-primary badge-pill">{item.quantity}</span>
        </li>
      {/each}
      <li
        class="list-group-item d-flex justify-content-between align-items-center"
      >
        Price <b>${totalPrice}</b>
      </li>
    </ul>

    <div class="card-body">
      <a use:link href="/shop" class="btn btn-primary btn-block">Checkout</a>
    </div>
  </div>
</main>

对这个文件的更新首先替换了硬编码的购物车项目,引用商店中的那个cart 状态。一个totalPrice 属性也被用来评估当前添加到购物车中的所有物品的总成本。在模板中,cart 的长度被用来向用户显示一条信息,以便在购物车为空时向购物车添加物品。

回到浏览器,如果主页还没有被重载,就重新加载。你会看到下面的视图。注意右栏上的购物车小部件的新外观。

"Updated Cart - E-commerce App"

现在点击Add to Cart ,将至少2件物品添加到购物车中,观察购物车小部件根据你的选择进行更新,如下图。

"Items selection - E-commerce App"

改变商品数量和从购物车中删除商品

注意到添加到购物车的每个产品的默认数量是如何设置为一(1)?这是因为每一个计数都必须从1开始。在一个合适的购物车应用程序中,应该允许用户增加每个选定商品的数量。

下一个任务是添加增加和减少购物车物品数量的功能。你还将添加从购物车中完全删除一个项目的功能。

从前面显示的购物车详情页面来看,每个显示的购物车项目都有管理其数量的按钮,使用了CartItem 组件。找到这个组件的位置 src/components/CartItem.svelte并将其中的代码替换为以下内容。

<script>
  import Product from "../components/Product.svelte";
  import { cart } from "../store";

  export let product;

  let itemQuantity = 0;

  $: {
    let get_product = $cart.filter((item) => item.id == product.id);

    itemQuantity = get_product[0].quantity;
  }

  function changeQuantity(action = "add") {
    if (action == "add") {
      product.quantity = product.quantity + 1;

      updateCart(product);
    } else {
      if (product.quantity > 1) {
        product.quantity = product.quantity - 1;
        updateCart(product);
      } else {
        //Remove the item

        removeItem(product);
      }
    }
  }

  function removeItem(product) {
    let removedItemCart = $cart.filter((cartItem) => {
      return cartItem.id != product.id;
    });

    cart.set(removedItemCart);
  }

  function updateCart(product) {

    let updatedCart = $cart.map((cartItem) => {
      if (cartItem.id == product.id) {
        return product;
      }

      return cartItem;
    });

    cart.set(updatedCart);
  }
</script>

<main>
  <div class="row cart-item-row">
    <div class="col-md-6">
      <Product {product} />
    </div>
    <div class="col-md-4">
      <div class="row">
        <div class="col-md-5">
          <button
            on:click={() => changeQuantity()}
            class="btn btn-primary btn-block">+</button
          >
        </div>
        <div class="col-md-2 text-center">{product.quantity}</div>
        <div class="col-md-5">
          <button
            on:click={() => changeQuantity("remove")}
            class="btn btn-warning btn-block">-</button
          >
        </div>
      </div>
      <div class="row cart-remove-button">
        <div class="col-md-12">
          <button on:click={() => removeItem()} class="btn btn-danger btn-block"
            >Remove Item</button
          >
        </div>
      </div>
    </div>
  </div>
</main>

<style scoped>
  .cart-item-row {
    border-bottom: 1px solid #ccc;
    margin-top: 20px;
  }
  .cart-remove-button {
    margin-top: 10px;
  }
</style>

这次更新添加了两个计算属性:cart ,它是对商店中cart 状态属性的引用,以及itemQuantity ,它获得了购物车中物品的当前数量。

还添加了三个方法,它们的作用如下。

  • changeQuantity:接收一个action 参数,该参数被设置为adddecrease ,以决定是增加还是减少物品数量1。如果当前数量为1,并且该函数被要求减少物品,那么该物品将被完全删除。
  • removeItem: 从购物车中完全删除一个物品。
  • updateCart:一个用于重复使用商店中cart 的更新逻辑的实用函数

接下来,找到购物车详情页面的 src/views/ShoppingCart.svelte并用下面的代码替换其内容。

<script>
  import Cart from "../components/Cart.svelte";
  import CartItem from "../components/CartItem.svelte";

  import { cart } from "../store";
</script>

<main>
  <div class="home container">
    <div class="row">
      <div class="col-md-8 pt-5">
        {#each $cart as product}
          <CartItem {product} />
        {/each}
      </div>
      <div class="col-md-4 pt-5">
        <Cart />
      </div>
    </div>
  </div>
</main>

这个更新取代了硬编码的购物车项目,引用了商店里的cart 属性。

运行应用程序

有了这些变化,你的应用程序应该再一次被重新加载。如果重载后购物车被重置为空,请向购物车添加一些物品,然后点击 "结账",进入购物车详情页面。在这个页面上,增加和减少购物车中的一些物品,并尝试通过点击 "删除 "来删除其中的一个。你会看到购物车小部件在右栏相应地更新,如下图。

"Update Cart Items - E-commerce App"

完美

总结

在本教程中,你已经能够在Svelte应用程序中使用购物车演示应用程序实现状态管理。

状态管理涉及到一个设计思维过程,你必须决定哪些状态属性是全局性的(存在于商店中),哪些状态属性要被本地化到使用它们的各个组件中。了解哪些状态应该是本地的,哪些状态属性应该驻留在中央存储区,将有助于你做出良好的设计决策。

如果你的演示的任何部分没有达到预期的效果,我建议你再看一遍文章,看看是否有什么步骤你可能错过了。如果你需要额外的帮助,请随时在评论中联系我们。

编码愉快 :)


© 2013-2021 Auth0公司。保留所有权利。