用React Native从头开始构建一个电商应用

1,155 阅读9分钟

移动应用开发商从事各种项目,包括客户可以用来在线购买产品的电子商务移动应用。世界上智能手机用户的数量每天都在急剧增加,因此,每个产品销售公司都可能希望通过电子商务移动应用程序提供便利的购买服务。

这些应用程序由几个通用屏幕组成,如产品列表、产品详情页、购物车、结账区和注册/登录页面。一些移动应用程序还可能有客户评论、个人设置和许多其他功能的空间。像其他企业应用程序一样,移动应用程序也需要发布到Google Play和苹果应用商店,以达到广泛的客户受众。

GitHub上的电子商务React Native应用与从头开始建立自己的应用

移动应用开发者通常倾向于用跨平台的框架来构建他们的应用,以便快速交付项目。这就是为什么React Native是构建跨平台移动应用的流行框架的部分原因--它让开发者在基于React的开发环境中轻松构建原生应用。

大多数自由开发者首先寻找开源的React Native电商应用模板,并根据用户的要求对其进行定制。但GitHub上现有的React Native电商应用模板大多使用了一点复杂的Redux,以达到集中状态管理的目的,而其中很多项目都包含了一些你不需要的东西。

考虑到这一点,从头开始创建你自己的React Native电商应用模板可能是一个更明智的决定。在本教程中,我将解释如何做到这一点。

我们要建立什么?

我们将建立一个具有几个通用(因此可定制)功能的电子商务移动应用。该应用程序有以下三个屏幕。

  1. 产品屏幕
    产品屏幕列出了几个产品,并作为应用程序的主页。每个产品项目都有一个标题、价格和图片。The demo products screen
  2. 产品详情 屏幕
    当用户在产品页面上点击一个特定的产品项目时,应用程序会将用户带到产品详情屏幕。产品详情屏幕显示了关于当前产品的所有信息。还有一个将产品添加到购物车的按钮;当用户点击添加到购物车按钮时,购物车图标上的物品数量会被更新。The product details screen
  3. 购物篮屏幕
    这个屏幕显示所有添加到购物篮的产品的摘要。它将显示一个带有产品名称、数量、小计和总数的产品列表。
    The shopping cart screen

用React Native构建电商应用

让我们创建一个新的React Native项目。在本教程中,我将使用Expo CLI来构建电商应用。

你可以使用React Native CLI或Expo CLI来构建React Native应用,但如果你使用React Native CLI,你就需要自己设置开发者环境。例如,如果你需要在Android设备上测试你的应用程序,你需要设置所需的Android SDK。

另一方面,Expo CLI可以让你开发React Native应用程序,而不需要在你的电脑上安装任何移动开发SDKs。

设置一个新的React Native项目

运行下面的命令来安装Expo CLI。确保你的Node.js和npm都有一个较新的LTS版本。

注意:如果你在Ubuntu操作系统上构建,nvm是一个很好的工具,可以安装来跟踪和更新到最新的Node LTS版本。

 npm install -g expo-cli

下面的命令将创建一个新的React Native项目。

expo init e-commerce-app

如果它要求提供模板,请选择空白模板选项。之后,运行下面的命令,开始开发应用程序。

npm start
# or
yarn start 

现在你可以看到你的代码变化与Expo Go移动应用一起上线。(这在Play Store和App Store上都有。)我将使用安卓设备演示该应用程序。

用你的手机扫描上述命令给出的二维码,打开你的应用程序。

创建产品列表

我们正在创建一个多屏幕的移动应用程序,而产品列表是应用程序的一个屏幕。我们可以使用React Native导航扩展来构建多屏应用。

用以下命令安装流行的React Navigation包

npm install @react-navigation/native
expo install react-native-screens react-native-safe-area-context
npm install @react-navigation/native-stack

产品信息通常是通过网络API检索的。但是,现在,让我们创建一个模拟的API服务来获取几个产品,并让我们的移动应用程序运行。在./services/ProductsService.js 文件中添加以下代码。

const PRODUCTS = [
    {
        id: 100,
        name: 'ReactProX Headset',
        price: 350,
        image: require('../assets/products/headset-100.jpg'),
        description: 'A headset combines a headphone with microphone. Headsets are made with either a single-earpiece (mono) or a double-earpiece (mono to both ears or stereo).'
    },
    {
        id: 101,
        name: 'FastLane Toy Car',
        price: 600,
        image: require('../assets/products/car-101.jpg'),
        description: 'A model car, or toy car, is a miniature representation of an automobile. Other miniature motor vehicles, such as trucks, buses, or even ATVs, etc. are often included in this general category.'
    },
    {
        id: 102,
        name: 'SweetHome Cupcake',
        price: 2,
        image: require('../assets/products/cake-102.jpg'),
        description: 'A cupcake (also British English: fairy cake; Hiberno-English: bun; Australian English: fairy cake or patty cake[1]) is a small cake designed to serve one person.'
    }
];
export function getProducts() {
    return PRODUCTS;
}
export function getProduct(id) {
    return PRODUCTS.find((product) => (product.id == id));
}

正如你所看到的,上述服务将作为一个模拟的API服务器来检索产品信息。getProducts 函数列出了所有现有的产品,而getProduct 函数则返回给定产品标识符的产品细节。请确保将你的产品样本图片存储在assets/products 目录中。

现在我们有了产品数据,我们需要为单个产品项目创建一个可重复使用的组件,因为产品列表中有多个产品。将以下代码添加到./components/Product.js

import React from 'react';
import {Text, Image, View, StyleSheet, TouchableOpacity} from 'react-native';
export function Product({name, price, image, onPress}) {
  return (
    <TouchableOpacity style={styles.card} onPress={onPress}>
      <Image
        style={styles.thumb}
        source={image}
      />
      <View style={styles.infoContainer}>
        <Text style={styles.name}>{name}</Text>
        <Text style={styles.price}>$ {price}</Text>
      </View>
    </TouchableOpacity>
  );
}
const styles = StyleSheet.create({
  card: {
    backgroundColor: 'white',
    borderRadius: 16,
    shadowOpacity: 0.2,
    shadowRadius: 4,
    shadowColor: 'black',
    shadowOffset: {
      height: 0,
      width: 0,
    },
    elevation: 1,
    marginVertical: 20,
  },
  thumb: {
    height: 260,
    borderTopLeftRadius: 16,
    borderTopRightRadius: 16,
    width: '100%',
  },
  infoContainer: {
    padding: 16,
  },
  name: {
    fontSize: 22,
    fontWeight: 'bold',
  },
  price: {
    fontSize: 16,
    fontWeight: '600',
    marginBottom: 8,
  },
});

产品组件负责在提供名称、价格和图片属性时渲染一个产品。这个组件接受一个触摸事件回调,打开产品的详细信息屏幕。你可能已经注意到了,还应用了一些造型规则,使产品条目显示为一个略带圆角的矩形。

建立我们的产品列表

由于我们已经完成了产品组件,我们现在可以通过重用产品组件开始构建我们的产品列表界面。将以下代码添加到./screens/ProductsList.js

import React, {useEffect, useState} from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { Product } from '../components/Product.js';
import { getProducts } from '../services/ProductsService.js';
export function ProductsList ({navigation}) {
function renderProduct({item: product}) {
    return (
      <Product {...product} 
      onPress={() => {
        navigation.navigate('ProductDetails', {
          productId: product.id,
        });
      }}
      />
    );
  }

  const [products, setProducts] = useState([]);

  useEffect(() => {
    setProducts(getProducts());
  });

  return (
    <FlatList
      style={styles.productsList}
      contentContainerStyle={styles.productsListContainer}
      keyExtractor={(item) => item.id.toString()}
      data={products}
      renderItem={renderProduct}
    />
  );
}
const styles = StyleSheet.create({
  productsList: {
    backgroundColor: '#eeeeee',
  },
  productsListContainer: {
    backgroundColor: '#eeeeee',
    paddingVertical: 8,
    marginHorizontal: 8,
  },
});

产品列表组件从我们之前创建的模拟API服务中获取产品列表数据。之后,它将通过渲染多个产品组件实例来显示产品项目。

在每个产品被渲染之前,我们通过onPress 道具传递一个导航回调。一旦选择了一个特定的产品项目,导航回调就会显示产品的详细信息屏幕。

现在,我们的产品列表界面已经准备好了。

开发购物车图标和上下文

用户应该能够从产品详情屏幕上将当前打开的产品添加到购物车中。现在,我们需要在产品详情界面之前实现购物车的逻辑。

在这种情况下,当用户更新购物车时,我们需要更新购物车的摘要图标(在屏幕的右上角)。此外,购物车摘要屏幕还列出了购物车中的物品。

我们必须将购物车数据存储在一个全局位置,并且我们需要从不同的地方更新/检索它们。React的Context API对于这种情况是一个很好的解决方案,因为它提供了一个简单的方法来拥有一个全局状态,而不像其他状态管理解决方案。

用下面的代码为CartContext.js 中的购物车创建一个上下文。

import React, {createContext, useState} from 'react';
import { getProduct } from './services/ProductsService.js';
export const CartContext = createContext();
export function CartProvider(props) {
  const [items, setItems] = useState([]);

  function addItemToCart(id) {
    const product = getProduct(id);
    setItems((prevItems) => {
      const item = prevItems.find((item) => (item.id == id));
      if(!item) {
          return [...prevItems, {
              id,
              qty: 1,
              product,
              totalPrice: product.price 
          }];
      }
      else { 
          return prevItems.map((item) => {
            if(item.id == id) {
              item.qty++;
              item.totalPrice += product.price;
            }
            return item;
          });
      }
    });
}
function getItemsCount() {
      return items.reduce((sum, item) => (sum + item.qty), 0);
  }

  function getTotalPrice() {
      return items.reduce((sum, item) => (sum + item.totalPrice), 0);
  }  

  return (
    <CartContext.Provider 
      value={{items, setItems, getItemsCount, addItemToCart, getTotalPrice}}>
      {props.children}
    </CartContext.Provider>
  );
}

上面的CarProvider 类通过暴露购物车的动作为购物车定义了一个React Context。现在,我们可以使用这个上下文实例来添加新的购物车项目,获得项目列表,并获得项目总数。

创建产品细节界面

产品详情部分显示了当前所选产品的全部信息。此外,它还有添加到购物车的按钮,可以更新购物车。

./screens/ProductDetails.js 文件中添加以下代码。

import React, {useEffect, useState, useContext} from 'react';
import {
  Text, 
  Image, 
  View, 
  ScrollView, 
  SafeAreaView, 
  Button, 
  StyleSheet
  } from 'react-native';
import { getProduct } from '../services/ProductsService.js';
import { CartContext } from '../CartContext';
export function ProductDetails({route}) {
  const { productId } = route.params;
  const [product, setProduct] = useState({});

  const { addItemToCart } = useContext(CartContext);

  useEffect(() => {
    setProduct(getProduct(productId));
  });

  function onAddToCart() {
    addItemToCart(product.id);
  }

  return (
    <SafeAreaView>
      <ScrollView>
        <Image
          style={styles.image}
          source={product.image}
        />
        <View style={styles.infoContainer}>
          <Text style={styles.name}>{product.name}</Text>
          <Text style={styles.price}>$ {product.price}</Text>
          <Text style={styles.description}>{product.description}</Text>
            <Button
            onPress={onAddToCart}
            title="Add to cart"
            / >
        </View>
      </ScrollView>
    </SafeAreaView>
  );
}
const styles = StyleSheet.create({
  card: {
    backgroundColor: 'white',
    borderRadius: 16,
    shadowOpacity: 0.2,
    shadowRadius: 4,
    shadowColor: 'black',
    shadowOffset: {
      height: 0,
      width: 0,
    },
    elevation: 1,
    marginVertical: 20,
  },
  image: {
    height: 300,
    width: '100%'
  },
  infoContainer: {
    padding: 16,
  },
  name: {
    fontSize: 22,
    fontWeight: 'bold',
  },
  price: {
    fontSize: 16,
    fontWeight: '600',
    marginBottom: 8,
  },
  description: {
    fontSize: 16,
    fontWeight: '400',
    color: '#787878',
    marginBottom: 16,
  },
});

上述屏幕根据从导航参数中收到的产品标识符,从模拟API服务中加载产品细节。然后,添加到购物车按钮的onPress 动作通过调用addItemToCart context函数来更新购物车上下文。最后,屏幕的内容被包裹在SafeAreaViewScrollView 中,以启用垂直滚动条来显示不完全适合设备视口的内容。

创建购物车摘要屏幕

每个客户都希望在结账前看到他们的订单摘要。这个电子商务应用程序有一个购物车摘要屏幕,它将列出所有的购物车项目,包括产品名称、数量、小计和最终总额。在./screens/Cart.js ,用以下代码实现购物车屏幕。

import React, { useEffect, useState, useContext } from 'react';
import { View, Text, Button, FlatList, StyleSheet } from 'react-native';
import { CartContext } from '../CartContext';
export function Cart ({navigation}) {
const {items, getItemsCount, getTotalPrice} = useContext(CartContext);

  function Totals() {
    let [total, setTotal] = useState(0);
    useEffect(() => {
      setTotal(getTotalPrice());
    });
    return (
       <View style={styles.cartLineTotal}>
          <Text style={[styles.lineLeft, styles.lineTotal]}>Total</Text>
          <Text style={styles.lineRight}>$ {total}</Text>
       </View>
    );
  }
function renderItem({item}) {
    return (
       <View style={styles.cartLine}>
          <Text style={styles.lineLeft}>{item.product.name} x {item.qty}</Text>
          <Text style={styles.lineRight}>$ {item.totalPrice}</Text>
       </View>
    );
  }

  return (
    <FlatList
      style={styles.itemsList}
      contentContainerStyle={styles.itemsListContainer}
      data={items}
      renderItem={renderItem}
      keyExtractor={(item) => item.product.id.toString()}
      ListFooterComponent={Totals}
    />
  );
}
const styles = StyleSheet.create({
  cartLine: { 
    flexDirection: 'row',
  },
  cartLineTotal: { 
    flexDirection: 'row',
    borderTopColor: '#dddddd',
    borderTopWidth: 1
  },
  lineTotal: {
    fontWeight: 'bold',    
  },
  lineLeft: {
    fontSize: 20, 
    lineHeight: 40, 
    color:'#333333' 
  },
  lineRight: { 
    flex: 1,
    fontSize: 20, 
    fontWeight: 'bold',
    lineHeight: 40, 
    color:'#333333', 
    textAlign:'right',
  },
  itemsList: {
    backgroundColor: '#eeeeee',
  },
  itemsListContainer: {
    backgroundColor: '#eeeeee',
    paddingVertical: 8,
    marginHorizontal: 8,
  },
});

上面的代码从购物车上下文中获取购物车项目,并将其显示在一个列表中。

创建购物车图标

每个电子商务应用程序通常都会在屏幕右上角的小图标的帮助下显示当前的购物车项目数。让我们用以下代码创建一个小组件来显示当前购物车的总物品数:./components/CartIcon.js

import React, { useContext } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { CartContext } from '../CartContext';
export function CartIcon({navigation}) {
  const {getItemsCount} = useContext(CartContext);
  return (
    <View style={styles.container}>
      <Text style={styles.text} 
        onPress={() => {
          navigation.navigate('Cart');
        }}
      >Cart ({getItemsCount()})</Text>
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    marginHorizontal: 8,
    backgroundColor: 'orange',
    height: 32,
    padding: 12,
    borderRadius: 32 / 2,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    color: 'white',
    fontWeight: 'bold',
  },
});

组装主应用程序的所有组件

现在,所有的应用程序组件都准备好了,可以组装成一个电子商务移动应用程序。主应用程序组件需要包括一个导航控制器,因为我们有多个屏幕。此外,我们确实需要用购物车上下文提供者包装所有组件,因为我们使用了React Context API。

用以下代码替换你的./App.js 文件的代码,作为最后一步。

import React from 'react';
import { StyleSheet } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { ProductsList } from './screens/ProductsList.js';
import { ProductDetails } from './screens/ProductDetails.js';
import { Cart } from './screens/Cart.js';
import { CartIcon } from './components/CartIcon.js';
import { CartProvider } from './CartContext.js';
const Stack = createNativeStackNavigator();
function App() {
  return (
    <CartProvider>
      <NavigationContainer>
        <Stack.Navigator>
          <Stack.Screen name='Products' component={ProductsList} 
          options={({ navigation }) => ({
            title: 'Products',
            headerTitleStyle: styles.headerTitle,
            headerRight: () => <CartIcon navigation={navigation}/>
          })}/>
          <Stack.Screen name='ProductDetails' component={ProductDetails} 
          options={({ navigation }) => ({
            title: 'Product details',
            headerTitleStyle: styles.headerTitle,
            headerRight: () => <CartIcon navigation={navigation}/>,
          })} />
          <Stack.Screen name='Cart' component={Cart} 
          options={({ navigation }) => ({
            title: 'My cart',
            headerTitleStyle: styles.headerTitle,
            headerRight: () => <CartIcon navigation={navigation}/>,
          })} />
        </Stack.Navigator>
      </NavigationContainer>
    </CartProvider>
  );
}
const styles = StyleSheet.create({
  headerTitle: {
    fontSize: 20
  }
});
export default App;

结论和思考

本教程指导你从头开始用React Native构建一个跨平台的电商应用。我们构建了一个最小的电商应用模板,没有使用任何状态管理库。因此,用本教程的演示应用源构建一个个人应用模板是很容易的,可以放弃查看GitHub上所有准备好的电商React Native模板,这些模板可能不适合你的特定需求。(另外,如果你需要,你可以将你最喜欢的状态管理库整合到这上面)。

完整的源代码可以在我的GitHub上找到。

至于下一步,你可以实现诸如认证、客户评论、产品评论、库存可用性检查等要求。你还需要计划你的移动应用的后端,因为我们在本教程中使用了一个模拟的API服务。

如果你打算使用一个现有的电子商务API,你可以写一个服务来进行API调用。如果你打算建立一个新的电子商务API,基于Node的NoSQL后端很容易上手。

The postBuild an ecommerce app from scratch with React Nativeappeared first onLogRocket Blog.