【HarmonyOS】React Native 实战项目与 Redux Toolkit 状态管理实践

【HarmonyOS】React Native 实战项目与 Redux Toolkit 状态管理实践

作者 :千问 AI
发布时间 :2026 年 2 月 19 日
标签:HarmonyOS、React Native、RNOH、Redux Toolkit、状态管理、ArkTS


前言

随着 HarmonyOS NEXT 在 2026 年全面成熟,React Native for OpenHarmony(RNOH)已成为跨平台开发的重要选择。然而,在鸿蒙平台上构建大型应用时,状态管理 依然是核心挑战。

本文将带你从零开始,使用 Redux Toolkit 构建一个完整的 HarmonyOS React Native 实战项目,涵盖状态管理架构、原生模块集成、性能优化等核心内容。


一、项目概述

1.1 项目目标

构建一个 鸿蒙电商应用,包含以下核心功能:

模块 功能描述 状态管理需求
用户模块 登录、注册、个人信息 用户认证状态、Token 管理
商品模块 商品列表、详情、搜索 商品数据缓存、筛选状态
购物车模块 添加、删除、数量调整 购物车实时同步
订单模块 下单、支付、订单查询 订单状态流转
设置模块 主题、语言、通知 全局配置状态

1.2 技术栈

复制代码
┌─────────────────────────────────────────────────────────┐
│                      应用层 (App Layer)                   │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐  │
│  │  React      │  │  Redux      │  │  React Router   │  │
│  │  Components │  │  Toolkit    │  │  Navigation     │  │
│  └─────────────┘  └─────────────┘  └─────────────────┘  │
├─────────────────────────────────────────────────────────┤
│                    桥接层 (Bridge Layer)                  │
│         (JavaScript ↔ ArkTS 原生桥接)                     │
├─────────────────────────────────────────────────────────┤
│                    原生层 (Native Layer)                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐  │
│  │  ArkUI      │  │  网络模块   │  │  存储模块       │  │
│  │  组件       │  │  HTTP/DNS   │  │  Preferences    │  │
│  └─────────────┘  └─────────────┘  └─────────────────┘  │
└─────────────────────────────────────────────────────────┘

1.3 开发环境

组件 版本
DevEco Studio 6.0.2
HarmonyOS SDK API 12+
React Native 0.78+
Node.js 18.x
Redux Toolkit 2.0+
React Redux 9.0+

二、项目初始化

2.1 创建项目

bash 复制代码
# 使用鸿蒙模板创建 React Native 项目
npx react-native@0.78 init HarmonyShop --template react-native-template-harmony

# 进入项目目录
cd HarmonyShop

# 安装核心依赖
npm install @reduxjs/toolkit react-redux
npm install @react-navigation/native @react-navigation/stack
npm install react-native-gesture-handler react-native-reanimated
npm install react-native-async-storage @react-native-async-storage/async-storage

# 安装鸿蒙特定依赖
npm install @rnoh/react-native-openharmony

2.2 项目结构

复制代码
HarmonyShop/
├── App.tsx                          # 应用入口
├── src/
│   ├── store/                       # Redux 状态管理
│   │   ├── index.ts                 # Store 配置
│   │   ├── slices/                  # Slice 定义
│   │   │   ├── userSlice.ts
│   │   │   ├── cartSlice.ts
│   │   │   ├── productSlice.ts
│   │   │   └── appSlice.ts
│   │   ├── hooks/                   # 自定义 Hooks
│   │   │   └── useAppDispatch.ts
│   │   └── middleware/              # 中间件
│   │       └── persistence.ts
│   ├── components/                  # 可复用组件
│   │   ├── common/
│   │   └── features/
│   ├── screens/                     # 页面组件
│   │   ├── HomeScreen.tsx
│   │   ├── ProductDetailScreen.tsx
│   │   ├── CartScreen.tsx
│   │   └── ProfileScreen.tsx
│   ├── navigation/                  # 导航配置
│   │   └── AppNavigator.tsx
│   ├── services/                    # API 服务
│   │   ├── api.ts
│   │   └── storage.ts
│   ├── types/                       # TypeScript 类型
│   │   └── index.ts
│   └── utils/                       # 工具函数
│       └── helpers.ts
├── entry/                           # HarmonyOS 原生工程
│   └── src/main/ets/
├── package.json
└── oh-package.json5

三、Redux Toolkit 核心配置

3.1 Store 配置

typescript 复制代码
// src/store/index.ts
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import {
  persistStore,
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';

// 导入 Slice
import userReducer from './slices/userSlice';
import cartReducer from './slices/cartSlice';
import productReducer from './slices/productSlice';
import appReducer from './slices/appSlice';

// 持久化配置
const persistConfig = {
  key: 'root',
  version: 1,
  storage: AsyncStorage,
  whitelist: ['user', 'cart', 'app'], // 需要持久化的 reducer
};

// 合并 Reducer
const rootReducer = combineReducers({
  user: userReducer,
  cart: cartReducer,
  product: productReducer,
  app: appReducer,
});

// 应用持久化
const persistedReducer = persistReducer(persistConfig, rootReducer);

// 创建 Store
export const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        // 忽略 redux-persist 的 action
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
      // 鸿蒙平台性能优化
      immutableCheck: {
        warnAfter: 128,
      },
    }),
  // 启用 Redux DevTools(开发环境)
  devTools: __DEV__,
});

// 创建 Persistor
export const persistor = persistStore(store);

// 类型导出
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

3.2 自定义 Hooks

typescript 复制代码
// src/store/hooks/useAppDispatch.ts
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from '../index';

// 类型安全的 dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>();

// 类型安全的 selector
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

四、Slice 开发实战

4.1 用户模块 Slice

typescript 复制代码
// src/store/slices/userSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { authService } from '../../services/api';

// 用户状态类型
interface UserState {
  userInfo: UserInfo | null;
  token: string | null;
  isLoading: boolean;
  error: string | null;
  isLoggedIn: boolean;
}

interface UserInfo {
  id: string;
  username: string;
  email: string;
  avatar?: string;
  phone?: string;
}

// 初始状态
const initialState: UserState = {
  userInfo: null,
  token: null,
  isLoading: false,
  error: null,
  isLoggedIn: false,
};

// 异步 Thunk:用户登录
export const login = createAsyncThunk(
  'user/login',
  async (credentials: { username: string; password: string }, { rejectWithValue }) => {
    try {
      const response = await authService.login(credentials);
      // 存储 Token 到鸿蒙安全存储
      await authService.saveToken(response.token);
      return response;
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

// 异步 Thunk:获取用户信息
export const fetchUserInfo = createAsyncThunk(
  'user/fetchUserInfo',
  async (_, { rejectWithValue }) => {
    try {
      const response = await authService.getUserInfo();
      return response;
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

// 异步 Thunk:用户登出
export const logout = createAsyncThunk(
  'user/logout',
  async (_, { rejectWithValue }) => {
    try {
      await authService.clearToken();
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    // 同步 Action:清除错误
    clearError(state) {
      state.error = null;
    },
    // 同步 Action:更新用户信息
    updateUserInfo(state, action: PayloadAction<Partial<UserInfo>>) {
      if (state.userInfo) {
        state.userInfo = { ...state.userInfo, ...action.payload };
      }
    },
  },
  extraReducers: (builder) => {
    builder
      // 登录
      .addCase(login.pending, (state) => {
        state.isLoading = true;
        state.error = null;
      })
      .addCase(login.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isLoggedIn = true;
        state.token = action.payload.token;
        state.userInfo = action.payload.user;
      })
      .addCase(login.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload as string;
      })
      // 获取用户信息
      .addCase(fetchUserInfo.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(fetchUserInfo.fulfilled, (state, action) => {
        state.isLoading = false;
        state.userInfo = action.payload;
        state.isLoggedIn = true;
      })
      .addCase(fetchUserInfo.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload as string;
      })
      // 登出
      .addCase(logout.fulfilled, (state) => {
        state.userInfo = null;
        state.token = null;
        state.isLoggedIn = false;
        state.error = null;
      });
  },
});

export const { clearError, updateUserInfo } = userSlice.actions;
export default userSlice.reducer;

4.2 购物车模块 Slice

typescript 复制代码
// src/store/slices/cartSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { cartService } from '../../services/api';

// 购物车商品类型
interface CartItem {
  id: string;
  productId: string;
  name: string;
  price: number;
  quantity: number;
  image?: string;
  selected?: boolean;
}

// 购物车状态类型
interface CartState {
  items: CartItem[];
  isLoading: boolean;
  error: string | null;
  totalAmount: number;
  totalCount: number;
}

const initialState: CartState = {
  items: [],
  isLoading: false,
  error: null,
  totalAmount: 0,
  totalCount: 0,
};

// 异步 Thunk:获取购物车
export const fetchCart = createAsyncThunk(
  'cart/fetchCart',
  async (_, { rejectWithValue }) => {
    try {
      const response = await cartService.getCart();
      return response.items;
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

// 异步 Thunk:添加商品到购物车
export const addToCart = createAsyncThunk(
  'cart/addToCart',
  async (product: { productId: string; quantity: number }, { rejectWithValue, getState }) => {
    try {
      const state = getState() as any;
      const token = state.user.token;
      const response = await cartService.addToCart(product, token);
      return response.item;
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

// 异步 Thunk:更新商品数量
export const updateQuantity = createAsyncThunk(
  'cart/updateQuantity',
  async (params: { itemId: string; quantity: number }, { rejectWithValue }) => {
    try {
      const response = await cartService.updateQuantity(params);
      return response.item;
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

// 异步 Thunk:删除商品
export const removeFromCart = createAsyncThunk(
  'cart/removeFromCart',
  async (itemId: string, { rejectWithValue }) => {
    try {
      await cartService.removeFromCart(itemId);
      return itemId;
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    // 同步 Action:清空购物车
    clearCart(state) {
      state.items = [];
      state.totalAmount = 0;
      state.totalCount = 0;
    },
    // 同步 Action:切换商品选中状态
    toggleSelect(state, action: PayloadAction<string>) {
      const item = state.items.find((i) => i.id === action.payload);
      if (item) {
        item.selected = !item.selected;
      }
    },
    // 同步 Action:全选/取消全选
    toggleSelectAll(state, action: PayloadAction<boolean>) {
      state.items.forEach((item) => {
        item.selected = action.payload;
      });
    },
  },
  extraReducers: (builder) => {
    builder
      // 获取购物车
      .addCase(fetchCart.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(fetchCart.fulfilled, (state, action) => {
        state.isLoading = false;
        state.items = action.payload;
        state.totalCount = action.payload.reduce((sum, item) => sum + item.quantity, 0);
        state.totalAmount = action.payload.reduce(
          (sum, item) => sum + item.price * item.quantity,
          0
        );
      })
      .addCase(fetchCart.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload as string;
      })
      // 添加商品
      .addCase(addToCart.fulfilled, (state, action) => {
        const existingItem = state.items.find(
          (item) => item.productId === action.payload.productId
        );
        if (existingItem) {
          existingItem.quantity += action.payload.quantity;
        } else {
          state.items.push(action.payload);
        }
        state.totalCount += action.payload.quantity;
        state.totalAmount += action.payload.price * action.payload.quantity;
      })
      // 更新数量
      .addCase(updateQuantity.fulfilled, (state, action) => {
        const index = state.items.findIndex(
          (item) => item.id === action.payload.id
        );
        if (index !== -1) {
          const oldItem = state.items[index];
          state.items[index] = action.payload;
          state.totalCount += action.payload.quantity - oldItem.quantity;
          state.totalAmount +=
            (action.payload.quantity - oldItem.quantity) * action.payload.price;
        }
      })
      // 删除商品
      .addCase(removeFromCart.fulfilled, (state, action) => {
        const item = state.items.find((i) => i.id === action.payload);
        if (item) {
          state.totalCount -= item.quantity;
          state.totalAmount -= item.price * item.quantity;
        }
        state.items = state.items.filter((i) => i.id !== action.payload);
      });
  },
});

export const { clearCart, toggleSelect, toggleSelectAll } = cartSlice.actions;
export default cartSlice.reducer;

4.3 商品模块 Slice

typescript 复制代码
// src/store/slices/productSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { productService } from '../../services/api';

interface Product {
  id: string;
  name: string;
  price: number;
  originalPrice?: number;
  image: string;
  description: string;
  stock: number;
  category: string;
  rating: number;
}

interface ProductState {
  products: Product[];
  featuredProducts: Product[];
  selectedProduct: Product | null;
  isLoading: boolean;
  error: string | null;
  filters: {
    category: string;
    priceRange: [number, number];
    sortBy: 'price_asc' | 'price_desc' | 'rating' | 'default';
  };
  pagination: {
    page: number;
    pageSize: number;
    total: number;
  };
}

const initialState: ProductState = {
  products: [],
  featuredProducts: [],
  selectedProduct: null,
  isLoading: false,
  error: null,
  filters: {
    category: 'all',
    priceRange: [0, 10000],
    sortBy: 'default',
  },
  pagination: {
    page: 1,
    pageSize: 20,
    total: 0,
  },
};

// 异步 Thunk:获取商品列表
export const fetchProducts = createAsyncThunk(
  'product/fetchProducts',
  async (params: { page: number; filters?: any }, { rejectWithValue }) => {
    try {
      const response = await productService.getProducts(params);
      return response;
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

// 异步 Thunk:获取商品详情
export const fetchProductDetail = createAsyncThunk(
  'product/fetchProductDetail',
  async (productId: string, { rejectWithValue }) => {
    try {
      const response = await productService.getProductDetail(productId);
      return response;
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

// 异步 Thunk:获取推荐商品
export const fetchFeaturedProducts = createAsyncThunk(
  'product/fetchFeaturedProducts',
  async (_, { rejectWithValue }) => {
    try {
      const response = await productService.getFeaturedProducts();
      return response;
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

const productSlice = createSlice({
  name: 'product',
  initialState,
  reducers: {
    // 更新筛选条件
    updateFilters(state, action) {
      state.filters = { ...state.filters, ...action.payload };
      state.pagination.page = 1; // 重置页码
    },
    // 清除筛选
    clearFilters(state) {
      state.filters = initialState.filters;
    },
    // 设置选中商品
    setSelectedProduct(state, action) {
      state.selectedProduct = action.payload;
    },
    // 清除选中商品
    clearSelectedProduct(state) {
      state.selectedProduct = null;
    },
  },
  extraReducers: (builder) => {
    builder
      // 获取商品列表
      .addCase(fetchProducts.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(fetchProducts.fulfilled, (state, action) => {
        state.isLoading = false;
        state.products = action.payload.products;
        state.pagination = action.payload.pagination;
      })
      .addCase(fetchProducts.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload as string;
      })
      // 获取商品详情
      .addCase(fetchProductDetail.fulfilled, (state, action) => {
        state.selectedProduct = action.payload;
      })
      // 获取推荐商品
      .addCase(fetchFeaturedProducts.fulfilled, (state, action) => {
        state.featuredProducts = action.payload;
      });
  },
});

export const {
  updateFilters,
  clearFilters,
  setSelectedProduct,
  clearSelectedProduct,
} = productSlice.actions;
export default productSlice.reducer;

4.4 应用配置 Slice

typescript 复制代码
// src/store/slices/appSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface AppState {
  theme: 'light' | 'dark' | 'system';
  language: 'zh-CN' | 'en-US';
  notifications: boolean;
  version: string;
  isFirstLaunch: boolean;
}

const initialState: AppState = {
  theme: 'system',
  language: 'zh-CN',
  notifications: true,
  version: '1.0.0',
  isFirstLaunch: true,
};

const appSlice = createSlice({
  name: 'app',
  initialState,
  reducers: {
    setTheme(state, action: PayloadAction<'light' | 'dark' | 'system'>) {
      state.theme = action.payload;
    },
    setLanguage(state, action: PayloadAction<'zh-CN' | 'en-US'>) {
      state.language = action.payload;
    },
    toggleNotifications(state) {
      state.notifications = !state.notifications;
    },
    setFirstLaunch(state, action: PayloadAction<boolean>) {
      state.isFirstLaunch = action.payload;
    },
  },
});

export const { setTheme, setLanguage, toggleNotifications, setFirstLaunch } =
  appSlice.actions;
export default appSlice.reducer;

五、组件集成与使用

5.1 Provider 配置

tsx 复制代码
// App.tsx
import React from 'react';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './src/store';
import AppNavigator from './src/navigation/AppNavigator';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';

export default function App() {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <GestureHandlerRootView style={{ flex: 1 }}>
          <SafeAreaProvider>
            <AppNavigator />
          </SafeAreaProvider>
        </GestureHandlerRootView>
      </PersistGate>
    </Provider>
  );
}

5.2 购物车页面

tsx 复制代码
// src/screens/CartScreen.tsx
import React, { useEffect } from 'react';
import {
  View,
  Text,
  FlatList,
  Image,
  TouchableOpacity,
  StyleSheet,
  Alert,
} from 'react-native';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import {
  fetchCart,
  updateQuantity,
  removeFromCart,
  toggleSelect,
  toggleSelectAll,
  clearCart,
} from '../store/slices/cartSlice';
import { addToOrder } from '../store/slices/orderSlice';

const CartScreen: React.FC = () => {
  const dispatch = useAppDispatch();
  const { items, totalAmount, totalCount, isLoading } = useAppSelector(
    (state) => state.cart
  );
  const { isLoggedIn } = useAppSelector((state) => state.user);

  useEffect(() => {
    if (isLoggedIn) {
      dispatch(fetchCart());
    }
  }, [isLoggedIn, dispatch]);

  const handleQuantityChange = (itemId: string, quantity: number) => {
    if (quantity < 1) {
      Alert.alert('提示', '商品数量不能小于 1');
      return;
    }
    dispatch(updateQuantity({ itemId, quantity }));
  };

  const handleRemove = (itemId: string) => {
    Alert.alert('确认删除', '确定要删除该商品吗?', [
      { text: '取消', style: 'cancel' },
      {
        text: '删除',
        style: 'destructive',
        onPress: () => dispatch(removeFromCart(itemId)),
      },
    ]);
  };

  const handleCheckout = () => {
    const selectedItems = items.filter((item) => item.selected);
    if (selectedItems.length === 0) {
      Alert.alert('提示', '请选择要结算的商品');
      return;
    }
    dispatch(addToOrder({ items: selectedItems, totalAmount }));
  };

  const renderCartItem = ({ item }: any) => (
    <View style={styles.cartItem}>
      <TouchableOpacity
        onPress={() => dispatch(toggleSelect(item.id))}
        style={styles.checkbox}
      >
        <View
          style={[
            styles.checkboxInner,
            item.selected && styles.checkboxChecked,
          ]}
        />
      </TouchableOpacity>

      <Image source={{ uri: item.image }} style={styles.itemImage} />

      <View style={styles.itemInfo}>
        <Text style={styles.itemName} numberOfLines={2}>
          {item.name}
        </Text>
        <Text style={styles.itemPrice}>¥{item.price.toFixed(2)}</Text>
      </View>

      <View style={styles.quantityControl}>
        <TouchableOpacity
          style={styles.quantityBtn}
          onPress={() => handleQuantityChange(item.id, item.quantity - 1)}
        >
          <Text style={styles.quantityBtnText}>-</Text>
        </TouchableOpacity>
        <Text style={styles.quantityText}>{item.quantity}</Text>
        <TouchableOpacity
          style={styles.quantityBtn}
          onPress={() => handleQuantityChange(item.id, item.quantity + 1)}
        >
          <Text style={styles.quantityBtnText}>+</Text>
        </TouchableOpacity>
      </View>

      <TouchableOpacity
        style={styles.removeBtn}
        onPress={() => handleRemove(item.id)}
      >
        <Text style={styles.removeBtnText}>删除</Text>
      </TouchableOpacity>
    </View>
  );

  if (isLoading) {
    return (
      <View style={styles.loadingContainer}>
        <Text>加载中...</Text>
      </View>
    );
  }

  if (items.length === 0) {
    return (
      <View style={styles.emptyContainer}>
        <Text style={styles.emptyText}>购物车空空如也</Text>
        <Text style={styles.emptyHint}>快去挑选心仪的商品吧</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>购物车 ({totalCount})</Text>
        <TouchableOpacity
          onPress={() => dispatch(toggleSelectAll(true))}
          style={styles.selectAllBtn}
        >
          <Text style={styles.selectAllText}>全选</Text>
        </TouchableOpacity>
      </View>

      <FlatList
        data={items}
        renderItem={renderCartItem}
        keyExtractor={(item) => item.id}
        contentContainerStyle={styles.listContent}
      />

      <View style={styles.footer}>
        <View style={styles.totalInfo}>
          <Text style={styles.totalText}>合计:</Text>
          <Text style={styles.totalAmount}>¥{totalAmount.toFixed(2)}</Text>
        </View>
        <TouchableOpacity style={styles.checkoutBtn} onPress={handleCheckout}>
          <Text style={styles.checkoutBtnText}>结算</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 16,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  headerTitle: {
    fontSize: 18,
    fontWeight: '600',
  },
  selectAllBtn: {
    padding: 8,
  },
  selectAllText: {
    color: '#007DFF',
    fontSize: 14,
  },
  listContent: {
    padding: 16,
  },
  cartItem: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 12,
    marginBottom: 12,
  },
  checkbox: {
    width: 24,
    height: 24,
    borderRadius: 12,
    borderWidth: 2,
    borderColor: '#ddd',
    justifyContent: 'center',
    alignItems: 'center',
    marginRight: 12,
  },
  checkboxInner: {
    width: 14,
    height: 14,
    borderRadius: 7,
    backgroundColor: 'transparent',
  },
  checkboxChecked: {
    backgroundColor: '#007DFF',
  },
  itemImage: {
    width: 80,
    height: 80,
    borderRadius: 8,
    marginRight: 12,
  },
  itemInfo: {
    flex: 1,
  },
  itemName: {
    fontSize: 14,
    color: '#333',
    marginBottom: 4,
  },
  itemPrice: {
    fontSize: 16,
    fontWeight: '600',
    color: '#FF5000',
  },
  quantityControl: {
    flexDirection: 'row',
    alignItems: 'center',
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 4,
    overflow: 'hidden',
  },
  quantityBtn: {
    width: 32,
    height: 32,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f5f5f5',
  },
  quantityBtnText: {
    fontSize: 18,
    color: '#333',
  },
  quantityText: {
    width: 40,
    textAlign: 'center',
    fontSize: 14,
  },
  removeBtn: {
    marginLeft: 12,
    padding: 8,
  },
  removeBtnText: {
    color: '#999',
    fontSize: 12,
  },
  footer: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: 16,
    backgroundColor: '#fff',
    borderTopWidth: 1,
    borderTopColor: '#eee',
  },
  totalInfo: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  totalText: {
    fontSize: 14,
    color: '#666',
  },
  totalAmount: {
    fontSize: 20,
    fontWeight: '600',
    color: '#FF5000',
    marginLeft: 8,
  },
  checkoutBtn: {
    backgroundColor: '#FF5000',
    paddingHorizontal: 32,
    paddingVertical: 12,
    borderRadius: 24,
  },
  checkoutBtnText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  emptyContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  emptyText: {
    fontSize: 18,
    color: '#999',
    marginBottom: 8,
  },
  emptyHint: {
    fontSize: 14,
    color: '#ccc',
  },
});

export default CartScreen;

5.3 商品列表页面

tsx 复制代码
// src/screens/HomeScreen.tsx
import React, { useEffect } from 'react';
import {
  View,
  Text,
  FlatList,
  Image,
  TouchableOpacity,
  StyleSheet,
  RefreshControl,
} from 'react-native';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import {
  fetchProducts,
  fetchFeaturedProducts,
  updateFilters,
  setSelectedProduct,
} from '../store/slices/productSlice';
import { addToCart } from '../store/slices/cartSlice';

const HomeScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
  const dispatch = useAppDispatch();
  const { products, featuredProducts, isLoading, filters, pagination } =
    useAppSelector((state) => state.product);
  const { isLoggedIn } = useAppSelector((state) => state.user);

  useEffect(() => {
    dispatch(fetchProducts({ page: 1 }));
    dispatch(fetchFeaturedProducts());
  }, [dispatch]);

  const handleRefresh = () => {
    dispatch(fetchProducts({ page: 1 }));
  };

  const handleProductPress = (product: any) => {
    dispatch(setSelectedProduct(product));
    navigation.navigate('ProductDetail', { productId: product.id });
  };

  const handleAddToCart = (product: any) => {
    if (!isLoggedIn) {
      navigation.navigate('Login');
      return;
    }
    dispatch(addToCart({ productId: product.id, quantity: 1 }));
  };

  const renderFeaturedItem = ({ item }: any) => (
    <TouchableOpacity
      style={styles.featuredItem}
      onPress={() => handleProductPress(item)}
    >
      <Image source={{ uri: item.image }} style={styles.featuredImage} />
      <Text style={styles.featuredName} numberOfLines={1}>
        {item.name}
      </Text>
      <Text style={styles.featuredPrice}>¥{item.price.toFixed(2)}</Text>
    </TouchableOpacity>
  );

  const renderProductItem = ({ item }: any) => (
    <TouchableOpacity
      style={styles.productItem}
      onPress={() => handleProductPress(item)}
    >
      <Image source={{ uri: item.image }} style={styles.productImage} />
      <View style={styles.productInfo}>
        <Text style={styles.productName} numberOfLines={2}>
          {item.name}
        </Text>
        <View style={styles.productMeta}>
          <Text style={styles.productPrice}>¥{item.price.toFixed(2)}</Text>
          <Text style={styles.productStock}>库存{item.stock}</Text>
        </View>
        <TouchableOpacity
          style={styles.addCartBtn}
          onPress={(e) => {
            e.stopPropagation();
            handleAddToCart(item);
          }}
        >
          <Text style={styles.addCartBtnText}>加入购物车</Text>
        </TouchableOpacity>
      </View>
    </TouchableOpacity>
  );

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>鸿蒙商城</Text>
      </View>

      <FlatList
        data={products}
        renderItem={renderProductItem}
        keyExtractor={(item) => item.id}
        numColumns={2}
        contentContainerStyle={styles.listContent}
        refreshControl={
          <RefreshControl refreshing={isLoading} onRefresh={handleRefresh} />
        }
        ListHeaderComponent={
          featuredProducts.length > 0 ? (
            <View style={styles.featuredSection}>
              <Text style={styles.sectionTitle}>热门推荐</Text>
              <FlatList
                data={featuredProducts}
                renderItem={renderFeaturedItem}
                keyExtractor={(item) => item.id}
                horizontal
                showsHorizontalScrollIndicator={false}
              />
            </View>
          ) : null
        }
        onEndReached={() => {
          if (pagination.page < Math.ceil(pagination.total / pagination.pageSize)) {
            dispatch(fetchProducts({ page: pagination.page + 1 }));
          }
        }}
        onEndReachedThreshold={0.5}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  header: {
    padding: 16,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  headerTitle: {
    fontSize: 20,
    fontWeight: '600',
  },
  listContent: {
    padding: 12,
  },
  featuredSection: {
    marginBottom: 16,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: '600',
    marginBottom: 12,
    paddingHorizontal: 4,
  },
  featuredItem: {
    width: 120,
    marginRight: 12,
    backgroundColor: '#fff',
    borderRadius: 8,
    padding: 8,
  },
  featuredImage: {
    width: 104,
    height: 104,
    borderRadius: 4,
    marginBottom: 8,
  },
  featuredName: {
    fontSize: 12,
    color: '#333',
    marginBottom: 4,
  },
  featuredPrice: {
    fontSize: 14,
    fontWeight: '600',
    color: '#FF5000',
  },
  productItem: {
    flex: 1,
    margin: 6,
    backgroundColor: '#fff',
    borderRadius: 12,
    overflow: 'hidden',
  },
  productImage: {
    width: '100%',
    height: 160,
    backgroundColor: '#f0f0f0',
  },
  productInfo: {
    padding: 12,
  },
  productName: {
    fontSize: 14,
    color: '#333',
    marginBottom: 8,
    height: 40,
  },
  productMeta: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
  },
  productPrice: {
    fontSize: 16,
    fontWeight: '600',
    color: '#FF5000',
  },
  productStock: {
    fontSize: 12,
    color: '#999',
  },
  addCartBtn: {
    backgroundColor: '#007DFF',
    paddingVertical: 6,
    paddingHorizontal: 12,
    borderRadius: 4,
    alignSelf: 'flex-start',
  },
  addCartBtnText: {
    color: '#fff',
    fontSize: 12,
  },
});

export default HomeScreen;

六、鸿蒙原生模块集成

6.1 安全存储模块

typescript 复制代码
// entry/src/main/ets/modules/SecureStorageModule.ets
import { preferences } from '@ohos.data.preferences';
import { cryptoFramework } from '@ohos.security.cryptoFramework';

@NativeModule
export class SecureStorageModule {
  private pref: preferences.Preferences | null = null;
  private crypto: cryptoFramework.CryptoFramework | null = null;

  @NativeMethod
  async init(): Promise<void> {
    // 初始化加密框架
    this.crypto = cryptoFramework.getCryptoFrameworkSync({
      alg: 'AES256',
      user: 'user_id',
    });

    // 初始化偏好设置
    this.pref = await preferences.getPreferences({
      name: 'secure_storage',
    });
  }

  @NativeMethod
  async setItem(key: string, value: string): Promise<void> {
    if (!this.pref || !this.crypto) {
      throw new Error('SecureStorage not initialized');
    }

    // 加密数据
    const encrypted = await this.crypto.encrypt(
      { data: value },
      { alg: 'AES256', mode: 'CBC', padding: 'PKCS7' }
    );

    // 存储加密数据
    await this.pref.put(key, encrypted.data.toString());
    await this.pref.flush();
  }

  @NativeMethod
  async getItem(key: string): Promise<string | null> {
    if (!this.pref || !this.crypto) {
      throw new Error('SecureStorage not initialized');
    }

    const encrypted = await this.pref.get(key, '');
    if (!encrypted) {
      return null;
    }

    // 解密数据
    const decrypted = await this.crypto.decrypt(
      { data: encrypted },
      { alg: 'AES256', mode: 'CBC', padding: 'PKCS7' }
    );

    return decrypted.data.toString();
  }

  @NativeMethod
  async removeItem(key: string): Promise<void> {
    if (!this.pref) {
      throw new Error('SecureStorage not initialized');
    }

    await this.pref.delete(key);
    await this.pref.flush();
  }

  @NativeMethod
  async clear(): Promise<void> {
    if (!this.pref) {
      throw new Error('SecureStorage not initialized');
    }

    await this.pref.clear();
    await this.pref.flush();
  }
}

6.2 JS 层调用

typescript 复制代码
// src/services/storage.ts
import { NativeModules } from 'react-native';

const { SecureStorageModule } = NativeModules;

export const secureStorage = {
  async init(): Promise<void> {
    await SecureStorageModule.init();
  },

  async setItem(key: string, value: string): Promise<void> {
    await SecureStorageModule.setItem(key, value);
  },

  async getItem(key: string): Promise<string | null> {
    return await SecureStorageModule.getItem(key);
  },

  async removeItem(key: string): Promise<void> {
    await SecureStorageModule.removeItem(key);
  },

  async clear(): Promise<void> {
    await SecureStorageModule.clear();
  },
};

七、性能优化

7.1 Redux 性能优化

typescript 复制代码
// src/store/middleware/performance.ts
import { Middleware } from '@reduxjs/toolkit';

// 性能监控中间件
export const performanceMiddleware: Middleware = (store) => (next) => (
  action
) => {
  const startTime = performance.now();
  const result = next(action);
  const endTime = performance.now();

  const duration = endTime - startTime;
  if (duration > 16) {
    // 超过一帧时间,记录警告
    console.warn(`Slow action: ${action.type} took ${duration.toFixed(2)}ms`);
  }

  return result;
};

// 在 store 配置中使用
// middleware: (getDefaultMiddleware) =>
//   getDefaultMiddleware().concat(performanceMiddleware)

7.2 选择器优化

typescript 复制代码
// src/store/selectors/cartSelectors.ts
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from '../index';

// 基础选择器
const selectCart = (state: RootState) => state.cart;

// 记忆化选择器:获取选中商品
export const selectSelectedItems = createSelector(
  [selectCart],
  (cart) => cart.items.filter((item) => item.selected)
);

// 记忆化选择器:获取选中商品总金额
export const selectSelectedTotal = createSelector(
  [selectSelectedItems],
  (items) =>
    items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);

// 记忆化选择器:获取购物车商品数量
export const selectCartCount = createSelector(
  [selectCart],
  (cart) => cart.items.length
);

7.3 鸿蒙特定优化

优化项 说明 预期提升
使用原生存储 替代 AsyncStorage 40-60%
减少桥接调用 批量处理数据 30-50%
启用 ArkUI 渲染 利用原生组件 50-70%
预加载数据 应用启动时初始化 20-30%

八、常见问题与解决方案

Q1: Redux 状态持久化失败?

解决方案

typescript 复制代码
// 确保正确配置 redux-persist
const persistConfig = {
  key: 'root',
  storage: AsyncStorage, // 鸿蒙使用兼容的存储
  whitelist: ['user', 'cart'],
};

Q2: 状态更新不触发重新渲染?

解决方案

  1. 确保使用 createSlice 而非手动创建 reducer
  2. 使用 useAppSelector 而非 useSelector
  3. 检查对象引用是否变化

Q3: 异步 Action 错误处理?

解决方案

typescript 复制代码
// 使用 createAsyncThunk 的 rejectWithValue
export const login = createAsyncThunk(
  'user/login',
  async (credentials, { rejectWithValue }) => {
    try {
      // ...
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

九、效果图

十、总结与展望

本文完整展示了 HarmonyOS React Native 项目中 Redux Toolkit 状态管理 的实战应用,涵盖:

  • ✅ Store 配置与持久化
  • ✅ 四大核心 Slice 开发
  • ✅ 组件集成与使用
  • ✅ 鸿蒙原生模块集成
  • ✅ 性能优化策略
  • ✅ 常见问题解决

随着 RNOH 生态的持续完善,Redux Toolkit 在鸿蒙平台的使用体验将越来越流畅。建议开发者:

  1. 优先使用 Redux Toolkit 获得更好的类型安全和开发体验
  2. 合理使用持久化 平衡性能与数据一致性
  3. 关注鸿蒙原生能力 充分利用平台特性

参考资料


欢迎交流:如有问题或建议,欢迎在评论区留言讨论!

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

相关推荐
lbb 小魔仙2 小时前
【HarmonyOS】RN_of_HarmonyOS实战项目_电话号码输入
华为·harmonyos
果粒蹬i2 小时前
【HarmonyOS】RN_of_HarmonyOS实战项目_搜索框样式
华为·harmonyos
松叶似针2 小时前
Flutter三方库适配OpenHarmony【secure_application】— 测试策略与用例设计
flutter·harmonyos
lbb 小魔仙3 小时前
【HarmonyOS】RN_of_HarmonyOS实战项目_密码显示隐藏
华为·harmonyos
平安的平安3 小时前
【OpenHarmony】React Native鸿蒙实战:NetInfo 网络状态详解
网络·react native·harmonyos
果粒蹬i3 小时前
【HarmonyOS】RN_of_HarmonyOS实战项目_自动完成功能
华为·harmonyos
Betelgeuse763 小时前
【Flutter For OpenHarmony】 项目结项复盘
华为·交互·开源软件·鸿蒙
平安的平安3 小时前
【OpenHarmony】React Native鸿蒙实战:SecureStorage 安全存储详解
安全·react native·harmonyos
松叶似针3 小时前
Flutter三方库适配OpenHarmony【secure_application】— 错误处理与异常边界
flutter·harmonyos