• 赚钱入口【需求资源】限时招募流量主、渠道主,站长合作;【合作模式】CPS长期分成,一次推广永久有收益。主动打款,不扣量;

如何使用React创建一个电子商务网站

JavaScript rin, seun 6个月前 (06-15) 69次浏览 0个评论

react创建电子商务网站

由于电子商务Web应用程序的大多数性能优化都是与前端相关的,因此在本教程中将使用突出的,以前端为中心的框架React(通常因其简洁和优雅而被首选)。

在创建一个基本的电子商务网站时,我们还将利用React Context作为状态管理框架(例如Redux和MobX)的替代方法。

另外,在此应用程序中将显示处理身份验证和购物车管理的基本方法。以下是我们将要构建的屏幕截图:

如何使用React创建一个电子商务网站

本教程假定您具有JavaScript和JSX的基本知识。

先决条件

在构建这个应用程序的过程中,我们将使用反应上下文来管理我们的应用程序数据,因为它“提供了一种通过组件树数据,而不必在每个级别手动传递下来的道具”,如轮廓的docs

要构建该应用程序,您还需要一台具有所需应用程序的计算机,用于启动和运行React应用程序-计算机上的Node> = 8.10和npm> = 5.6

像这样创建一个React项目:

npx create-react-app e-commerce

在这个应用程序中,我们还使用React Router来处理路由。要安装此模块,请运行以下命令:

npm install react-router-dom

最后,我们将使用Bulma开源CSS框架来设置此应用程序的样式。要安装此程序,请运行以下命令:

npm install bulma

入门

首先,我们需要将样式表添加到我们的应用程序中。为此,我们将添加一个import语句以将该文件包含在应用程序文件夹中的index.js文件中src。这会将样式表应用于应用程序中的所有组件。以下是import语句的摘要:

// ./index.js
...
import "bulma/css/bulma.css";
...

在此应用程序中,因为我们不使用后端API,所以我们为该应用程序定义了一个数据集。数据集包含应用程序的用户和初始产品列表:

// ./Data.js
export default {
  users: [
    { username: "regular", accessLevel: 1, password: "password" },
    { username: "admin", accessLevel: 0, password: "password" }
  ],
  initProducts: [
    {
      name: "shoes",
      stock: 10,
      price: 399.99,
      shortDesc: "Nulla facilisi. Curabitur at lacus ac velit ornare lobortis.",
      description:
        "Cras sagittis. Praesent nec nisl a purus blandit viverra. Ut leo. Donec quam          elis, ultricies nec, pellentesque eu, pretium quis, sem. Fusce a quam."
    },
    ...moreProducts
  ]
};

上下文设置

在通常需要上下文的复杂应用程序中,可以有多个上下文,每个上下文都有其数据和方法,这些数据和方法与需要数据和方法的一组组件有关。例如,可以有一个ProductContext用于处理使用产品相关数据的组件,另一个ProfileContext用于处理与身份验证和用户数据有关的数据。但是,为了使事情尽可能简单,我们将仅使用一个上下文实例。

为了创建上下文,我们Context.js在应用程序的根目录中创建一个文件:

// ./Context.js

import React from "react";
const Context = React.createContext({});
export default Context;

这将创建上下文,并将上下文数据初始化为一个空对象。接下来,我们需要创建一个组件包装器,用于包装使用Context数据和方法的组件:

    // ./withContext.js

import React from "react";
import Context from "./Context";
const withContext = WrappedComponent => {
  const WithHOC = props => {
    return (
      <Context.Consumer>
        {context => <WrappedComponent {...props} context={context} />}
      </Context.Consumer>
    );
  };
  WithHOC.WrappedComponent = WrappedComponent;
  return WithHOC;
};
export default withContext;

上面的代码导出一个函数withContext,该函数使用先前创建的Context的Consumer Component属性。消费者组件将与上下文关联的数据传输到其子组件。

应用程式开发

接下来,我们需要设置App.js文件。在这里,我们将处理应用程序的导航,并定义其数据和管理方法。

首先,让我们设置应用程序导航:

//./src/App.js

import React, { Component } from "react";
import { Switch, Route, Link, BrowserRouter as Router } from "react-router-dom";
import data from "./Data";
import Context from "./Context";
export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.routerRef = React.createRef();
  }
  ...
  ...
  ...

  render() {
    return (
      <Context.Provider
        value={{
          ...this.state,
          removeFromCart: this.removeFromCart,
          addToCart: this.addToCart,
          login: this.login,
          addProduct: this.addProduct,
          clearCart: this.clearCart,
          checkout: this.checkout
        }}
      >
        <Router ref={this.routerRef}>
          <div className="App">
            <nav
              className="navbar container"
              role="navigation"
              aria-label="main navigation"
            >
              <div className="navbar-brand">
                <b className="navbar-item is-size-4 ">ecommerce</b>
                <a
                  role="button"
                  class="navbar-burger burger"
                  aria-label="menu"
                  aria-expanded="false"
                  data-target="navbarBasicExample"
                  onClick={e => {
                    e.preventDefault();
                    this.setState({ showMenu: !this.state.showMenu });
                  }}
                >
                  <span aria-hidden="true"></span>
                  <span aria-hidden="true"></span>
                  <span aria-hidden="true"></span>
                </a>
              </div>
              <div className={`navbar-menu ${
                  this.state.showMenu ? "is-active" : ""
                }`}>
                <Link to="/products" className="navbar-item">
                  Products
                </Link>
                {this.state.user && this.state.user.accessLevel < 1 && (
                  <Link to="/add-product" className="navbar-item">
                    Add Product
                  </Link>
                )}
                <Link to="/cart" className="navbar-item">
                  Cart
                  <span
                    className="tag is-primary"
                    style={{ marginLeft: "5px" }}
                  >
                    {Object.keys(this.state.cart).length}
                  </span>
                </Link>
                {!this.state.user ? (
                  <Link to="/login" className="navbar-item">
                    Login
                  </Link>
                ) : (
                  <a className="navbar-item" onClick={this.logout}>
                    Logout
                  </a>
                )}
              </div>
            </nav>
            <Switch>
              <Route exact path="/" component={Component} />
              <Route exact path="/login" component={Component} />
              <Route exact path="/cart" component={Component} />
              <Route exact path="/add-product" component={Component} />
              <Route exact path="/products" component={Component} />
            </Switch>
          </div>
        </Router>
      </Context.Provider>
    );
  }
}
如何使用React创建一个电子商务网站

Learn PHP for free!

Make the leap into server-side programming with a comprehensive cover of PHP & MySQL.

Normally RRP $11.95 Yours absolutely free

我们的App组件将负责初始化应用程序数据,还将定义操作这些数据的方法。在这里,我们使用上下文提供程序组件定义上下文数据和方法。数据和方法作为属性传递value到Provider组件上,以替换上下文创建时给定的对象。(请注意,该值可以是任何数据类型。)我们传递状态值和一些我们将很快定义的方法。

接下来,我们构建应用程序导航。为此,我们需要使用Router组件包装我们的应用程序,该组件可以是BrowserRouter(如本例所示)或HashRouter。接下来,我们使用Switch and Route组件定义应用程序的路由。我们还创建应用程序的导航菜单,每个链接都使用React Router模块中提供的Link组件。我们还routerRef对Router组件添加了引用,以使我们能够从该App组件访问路由器。

如何使用React创建一个电子商务网站

用户认证

在下一步中,我们将处理用户身份验证。首先,我们需要在App组件构造函数中初始化用户。如下所示:

//./src/App.js
...  
  constructor(props) {
    super(props);
    this.state = {
      user: null
    };
  }
...

接下来,通过将用户设置为组件安装,确保在应用程序启动时加载了用户,如下所示:

//.src/App/.js
...
componentDidMount() {
    let user = localStorage.getItem("user");
    user = user ? JSON.parse(user) : null;
    this.setState({ user });
  }
...

在这里,我们将最后一个用户会话从本地存储加载到状态(如果存在)。接下来,我们定义附加到Context的login和logout方法:

//./src/App.js
...
  login = (usn, pwd) => {
    let user = data.users.find(u => u.username === usn && u.password === pwd);
    if (user) {
      this.setState({ user });
      localStorage.setItem("user", JSON.stringify(user));
      return true;
    }
    return false;
  };
  logout = e => {
    e.preventDefault();
    this.setState({ user: null });
    localStorage.removeItem("user");
  };
...

登录方法检查用户名和密码组合是否与阵列中的任何用户匹配,并在找到状态后设置状态用户。它还将用户添加到本地存储以实现持久性。注销将从状态存储和本地存储中清除用户。

接下来,我们创建登录组件。首先,我们创建一个components文件夹,在其中放置所有组件,然后创建一个Login.js文件。该组件利用上下文数据。为了能够访问这些数据和方法,必须使用withContext我们之前创建的方法对其进行包装。

// ./src/components/Login.js

import React, { Component, Fragment } from "react";
import withContext from "../withContext";
import { Redirect } from "react-router-dom";
class Login extends Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "",
      password: ""
    };
  }
  handleChange = e =>
    this.setState({ [e.target.name]: e.target.value, error: "" });

  login = () => {
    const { username, password } = this.state;
    if (!username || !password) {
      return this.setState({ error: "Fill all fields!" });
    }
    let loggedIn = this.props.context.login(username, password);
    if (!loggedIn) {
      this.setState({ error: "Invalid Credentails" });
    }
  };
  render() {
    return !this.props.context.user ? (
      <Fragment>
        <div className="hero is-primary ">
          <div className="hero-body container">
            <h4 className="title">Login</h4>
          </div>
        </div>
        <br />
        <br />
        <div className="columns is-mobile is-centered">
          <div className="column is-one-third">
            <div className="field">
              <label className="label">User Name: </label>
              <input
                className="input"
                type="text"
                name="username"
                onChange={this.handleChange}
              />
            </div>
            <div className="field">
              <label className="label">Password: </label>
              <input
                className="input"
                type="password"
                name="password"
                onChange={this.handleChange}
              />
            </div>
            {this.state.error && (
              <div className="has-text-danger">{this.state.error}</div>
            )}
            <div className="field is-clearfix">
              <button
                className="button is-primary is-outlined is-pulled-right"
                onClick={this.login}
              >
                Submit
              </button>
            </div>
          </div>
        </div>
      </Fragment>
    ) : (
      <Redirect to="/products" />
    );
  }
}
export default withContext(Login);

该组件呈现一个具有两个输入的表单,以收集用户登录凭据。提交输入后,组件将调用登录方法,该方法通过上下文传递。如果用户已经登录,该模块还确保重定向到产品页面。我们需要更新该App.js组件以使用Login组件:

// ./src/App.js
...
import Login from "./components/Login";
...
...
    <Route exact path="/login" component={Login} />
...

如何使用React创建一个电子商务网站

产品和购物车

接下来,我们创建产品页面,该页面还将用作应用程序登录页面。该页面将使用两个新组件。第一个是ProductList.js,它将显示页面正文,另一个是ProductItem.js列表中每个产品的组件。在继续构建这些组件之前,我们需要将产品加载到该App组件中。首先,我们在构造函数中为产品的默认值设置一个空数组:

//./src/App.js
...  
  constructor(props) {
    super(props);
    this.state = {
      user: null,
      products: []
    };
  }
...

然后,我们需要像登录用户一样将产品加载到组件安装架上:

//.src/App/.js
...
  componentDidMount() {
    let user = localStorage.getItem("user");
    let products = localStorage.getItem("products");

    user = user ? JSON.parse(user) : null;
    products = products ? JSON.parse(products) : data.initProducts;

    this.setState({ user, products });
  }
...

在上面的代码片段中,我们尝试从本地存储中加载产品,以防对初始产品列表进行更改。如果不存在,我们将从应用程序样本数据中加载默认产品列表。

在创建产品列表组件之前,让我们创建一个界面,为具有正确权限的用户创建产品。首先,让我们定义添加产品的方法。我们将在App组件中执行此操作,如下所示:

//.src/App/.js
...
  addProduct = (product, callback) => {
    let products = this.state.products.slice();
    products.push(product);
    localStorage.setItem("products", JSON.stringify(products));
    this.setState({ products }, () => callback && callback());
  };
...

此方法接收产品对象并将其附加到产品数组,然后将其保存到应用程序状态和本地存储。它还收到一个回调函数,以在成功添加产品后执行。

现在,我们可以继续制作添加产品组件:

//.src/components/AddProduct.js

import React, { Component, Fragment } from "react";
import withContext from "../withContext";
import { Redirect } from "react-router-dom";
const initState = {
  name: "",
  price: "",
  stock: "",
  shortDesc: "",
  description: ""
};
class AddProduct extends Component {
  constructor(props) {
    super(props);
    this.state = initState;
  }
  save = e => {
    e.preventDefault();
    const { name, price, stock, shortDesc, description } = this.state;
    if (name && price) {
      this.props.context.addProduct(
        {
          name,
          price,
          shortDesc,
          description,
          stock: stock || 0
        },
        () => this.setState(initState)
      );
    } else {
      this.setState({ error: "Please Enter name and price" });
    }
  };
  handleChange = e =>
    this.setState({ [e.target.name]: e.target.value, error: "" });
  render() {
    const { name, price, stock, shortDesc, description } = this.state;
    const { user } = this.props.context;
    return !(user && user.accessLevel < 1) ? (
      <Redirect to="/" />
    ) : (
      <Fragment>
        <div className="hero is-primary ">
          <div className="hero-body container">
            <h4 className="title">Login</h4>
          </div>
        </div>
        <br />
        <br />
        <form onSubmit={this.save}>
          <div className="columns is-mobile is-centered">
            <div className="column is-one-third">
              <div className="field">
                <label className="label">Product Name: </label>
                <input
                  className="input"
                  type="text"
                  name="name"
                  value={name}
                  onChange={this.handleChange}
                  required
                />
              </div>
              <div className="field">
                <label className="label">Price: </label>
                <input
                  className="input"
                  type="number"
                  name="price"
                  value={price}
                  onChange={this.handleChange}
                  required
                />
              </div>
              <div className="field">
                <label className="label">Available in Stock: </label>
                <input
                  className="input"
                  type="number"
                  name="stock"
                  value={stock}
                  onChange={this.handleChange}
                />
              </div>
              <div className="field">
                <label className="label">Short Description: </label>
                <input
                  className="input"
                  type="text"
                  name="shortDesc"
                  value={shortDesc}
                  onChange={this.handleChange}
                />
              </div>
              <div className="field">
                <label className="label">Description: </label>
                <textarea
                  className="textarea"
                  type="text"
                  rows="2"
                  style={{ resize: "none" }}
                  name="description"
                  value={description}
                  onChange={this.handleChange}
                />
              </div>
              {this.state.error && (
                <div className="error">{this.state.error}</div>
              )}
              <div className="field is-clearfix">
                <button
                  className="button is-primary is-outlined is-pulled-right"
                  type="submit"
                  onClick={this.save}
                >
                  Submit
                </button>
              </div>
            </div>
          </div>
        </form>
      </Fragment>
    );
  }
}
export default withContext(AddProduct);

该组件提供了一个表单,用于收集必要的信息,并确保用户已登录并具有创建产品所需的所需访问级别。它有一个save方法,该方法使用用户的输入来创建产品对象,并addProduct从上下文中调用该方法并传递产品,并使用回调方法来重置输入字段。

如何使用React创建一个电子商务网站

现在,我们可以将该组件添加到该App组件中以进行路由:

// ./src/App.js
...
import Login from "./components/AddProduct";
...
...
    <Route exact path="/add-product" component={AddProduct} />
...

接下来,我们继续创建该Productlist组件,如下所示:

// ./src/components/ProductList

import React, { Component, Fragment } from "react";
import ProductItem from "./ProductItem";
import withContext from "../withContext";
const ProductList = props => {
  const { products } = props.context;
  return (
    <Fragment>
      <div className="hero is-primary">
        <div className="hero-body container">
          <h4 className="title">Our Products</h4>
        </div>
      </div>
      <br />
      <div className="container">
        <div className="column columns is-multiline">
          {products && products.length ? (
            products.map((product, index) => (
              <ProductItem
                product={product}
                key={index}
                addToCart={props.context.addToCart}
              />
            ))
          ) : (
            <div className="column">
              <span className="title has-text-grey-light">
                No product found!
              </span>
            </div>
          )}
        </div>
      </div>
    </Fragment>
  );
};
export default withContext(ProductList);

由于列表取决于数据的上下文,因此我们也将其与withContext函数包装在一起。该组件使用ProductItem我们尚未创建的组件来渲染产品。它还将一个addToCart方法从上下文(我们也要定义)传递给ProductItem。这消除了直接在ProductItem组件中使用上下文的需要。接下来,我们创建ProductItem组件:

./src/components/ProductItem.js

import React, { Component } from "react";
const ProductItem = props => {
  const { product } = props;
  return (
    <div className=" column is-half">
      <div className="box">
        <div className="media">
          <div className="media-left">
            <figure className="image is-64x64">
              <img
                src="https://bulma.io/images/placeholders/128x128.png"
                alt="Image"
              />
            </figure>
          </div>
          <div className="media-content">
            <b style={{ textTransform: "capitalize" }}>
              {product.name}{" "}
              <span className="tag is-primary">${product.price}</span>
            </b>
            <div>{product.shortDesc}</div>
            {product.stock > 0 ? (
              <small>{product.stock + " Available"}</small>
            ) : (
              <small className="has-text-danger">Out Of Stock</small>
            )}
            <div className="is-clearfix">
              <button
                className="button is-small is-outlined is-primary   is-pulled-right"
                onClick={() =>
                  props.addToCart({
                    id: product.name,
                    product,
                    amount: 1
                  })
                }
              >
                Add to Cart
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};
export default ProductItem;

该元素在罐头上显示产品,还提供一个操作按钮以将产品添加到用户的购物车中。

如何使用React创建一个电子商务网站

接下来,我们创建购物车对象和购物车页面。首先,让我们在App组件中创建购物车,并确保在App组件加载时从本地存储加载现有的购物车:

//./src/App.js
...  
  constructor(props) {
    super(props);
    this.state = {
      user: null,
      products: [],
      Cart: {}
    };
  }
...
...
  componentDidMount() {
    let user = localStorage.getItem("user");
    let products = localStorage.getItem("products");
    let cart = localStorage.getItem("cart");


    products = products ? JSON.parse(products) : data.initProducts;
    user = user ? JSON.parse(user) : null;
    cart = cart? JSON.parse(cart) : {};

    this.setState({ user, products, cart });
  }
...

接下来,我们需要定义购物车功能。首先,我们创建addToCart方法:

//./src/App.js
...
  addToCart = cartItem => {
    let cart = this.state.cart;
    if (cart[cartItem.id]) {
      cart[cartItem.id].amount += cartItem.amount;
    } else {
      cart[cartItem.id] = cartItem;
    }
    if (cart[cartItem.id].amount > cart[cartItem.id].product.stock) {
      cart[cartItem.id].amount = cart[cartItem.id].product.stock;
    }
    localStorage.setItem("cart", JSON.stringify(cart));
    this.setState({ cart });
  };
...

此方法使用商品ID作为购物车对象的键附加商品。我们使用购物车的对象而不是数组来简化数据检索。此方法检查购物车对象是否存在具有该钥匙的物品。如果有,增加数量;否则,它将创建一个新条目。第二条if语句确保用户添加的项目不能超过可用项目。然后,该方法将购物车保存到状态,并通过上下文将其传递到应用程序的其他部分。最后,该方法将更新的购物车保存到本地存储以实现持久性。

接下来,我们定义removeFromCart从用户购物车中删除特定产品并从用户购物车clearCart中删除所有产品的方法:

//./src/App.js
...
  removeFromCart = cartItemId => {
    let cart = this.state.cart;
    delete cart[cartItemId];
    localStorage.setItem("cart", JSON.stringify(cart));
    this.setState({ cart });
  };
  clearCart = () => {
    let cart = {};
    localStorage.removeItem("cart");
    this.setState({ cart });
  };
...

removeCart方法使用提供的产品密钥删除产品。然后,它会相应地更新应用程序状态和本地存储。该clearCart方法在状态下将购物车重置为空对象,并删除本地存储上的购物车条目。

如何使用React创建一个电子商务网站

现在,我们继续制作购物车用户界面。与产品列表类似,我们使用两个元素来实现此目的:第一个元素Cart.js呈现页面布局,第二个组件使用购物车列表CartItem.js

// ./src/components/Cart.js

import React, { Fragment } from "react";
import withContext from "../withContext";
import CartItem from "./CartItem";
const Cart = props => {
  const { cart } = props.context;
  const cartKeys = Object.keys(cart || {});
  return (
    <Fragment>
      <div className="hero is-primary">
        <div className="hero-body container">
          <h4 className="title">My Cart</h4>
        </div>
      </div>
      <br />
      <div className="container">
        {cartKeys.length ? (
          <div className="column columns is-multiline">
            {cartKeys.map(key => (
              <CartItem
                cartKey={key}
                key={key}
                cartItem={cart[key]}
                removeFromCart={props.context.removeFromCart}
              />
            ))}
            <div className="column is-12 is-clearfix">
              <br />
              <div className="is-pulled-right">
                <button
                  onClick={props.context.clearCart}
                  className="button is-warning "
                >
                  Clear cart
                </button>{" "}
                <button
                  className="button is-success"
                  onClick={props.context.checkout}
                >
                  Checkout
                </button>
              </div>
            </div>
          </div>
        ) : (
          <div className="column">
            <div className="title has-text-grey-light">No item in cart!</div>
          </div>
        )}
      </div>
    </Fragment>
  );
};
export default withContext(Cart);

Cart组件还将方法从上下文传递到CartItem。该Cart组件循环遍历上下文购物车对象值的数组,并CartItem为每个对象返回一个。它还提供了一个按钮来清除用户购物车。接下来是CartItem组件,该组件非常类似于该ProductItem组件,但有一些细微的变化:

// ./src/components/CartItem.js

import React, { Component } from "react";
const CartItem = props => {
  const { cartItem, cartKey } = props;
  const { product, amount } = cartItem;
  return (
    <div className=" column is-half">
      <div className="box">
        <div className="media">
          <div className="media-left">
            <figure className="image is-64x64">
              <img
                src="https://bulma.io/images/placeholders/128x128.png"
                alt="Image"
              />
            </figure>
          </div>
          <div className="media-content">
            <b style={{ textTransform: "capitalize" }}>
              {product.name}{" "}
              <span className="tag is-primary">${product.price}</span>
            </b>
            <div>{product.shortDesc}</div>
            <small>{`${amount} in cart`}</small>
          </div>
          <div
            className="media-right"
            onClick={() => props.removeFromCart(cartKey)}
          >
            <span className="delete is-large"></span>
          </div>
        </div>
      </div>
    </div>
  );
};
export default CartItem;

该组件显示产品信息和所选项目数。它还提供了一个按钮,用于从购物车中取出产品。现在,我们需要将Cart组件导入App组件并进行路由,如下所示:

// ./src/App.js
...
import Login from "./components/Cart";
...
...
    <Route exact path="/cart" component={Cart} />
...

最后,我们需要在App组件中添加checkout方法:

// ./src/Appjs
...
  checkout = () => {
    if (!this.state.user) {
      this.routerRef.current.history.push("/login");
      return;
    }
    const cart = this.state.cart;
    const products = this.state.products.map(p => {
      if (cart[p.name]) {
        p.stock = p.stock - cart[p.name].amount;
      }
      return p;
    });
    this.setState({ products });
    this.clearCart();
  };
...

此方法检查用户是否登录,然后再继续。如果用户未登录,它将使用我们附加到该Router组件的路由器参考重定向用户。

通常,在常规电子商务站点中,将在这进行计费过程,但是对于我们的应用程序,我们仅假设用户已付款,因此将其购买的商品从可用商品列表中删除。

这样,我们就成功完成了基本的购物车。

如何使用React创建一个电子商务网站

结论

在本文的过程中,我们使用React Context在多个组件之间移动数据和方法。尽管通过将这些数据作为prop传递可以达到相同的结果,但是在多个嵌套元素上移动数据可能会变得很麻烦。此应用程序绝不是最终产品,可以加以改进。例如,当操作完成或正在进行时,可以花费更多的努力来警告用户。该应用程序只是为了展示使用React构建电子商务应用程序有多么容易。

该应用程序的代码可在GitHub上获得

是否想深入研究React?在SitePoint Premium上查看React设计模式和最佳实践以及大量其他React资源

喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址