继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

在React中实现MVVM模式:实战指南

慕运维8079593
关注TA
已关注
手记 247
粉丝 19
获赞 62

嘿,大家好!👋 花了一段时间构建React应用后,我发现随着应用变大,保持一个整洁的架构变得至关重要。今天,我来分享一下我们在React中使用MVVM(M-V-VM)模式的经验,它帮我们团队省去了无数麻烦,让代码库更易于管理。

這兒有您需要知道的好處!来啦!🎯

1. 你的代码变得超级有组织
— 数据、逻辑和UI组件分离明确
— 代码的每一部分都有明确的任务并能胜任
— 再不用纠结“这个逻辑该放哪里”了

2. 测试变得简单
— 业务逻辑被隔离在ViewModel中
— UI组件完全是展示性的
— 可以独立测试每个部分,无需模拟整个系统

3. 超强化的复用性
— ViewModels 可在不同组件间重复使用
— 逻辑保持一致
— 减少复制粘贴,保持单一事实来源

4. 合乎逻辑的状态管理
— 应用程序中清晰的数据流
— 可预测的状态更新
— 当出现问题时更容易调试(问题总是会出现的,不是吗?)

让我们看看实际应用吧!💻

让我们构建一个简单的电子商务产品列表页面,它带有过滤器和排序功能。这里是如何用MVVM(模型-视图-视图模型)这样构建。

目录结构
    src/  
    ├── pages/  
    │ └── ProductsPage/  
    │ ├── index.tsx      # 主页面组件  
    │ ├── index.hook.ts  # 页面钩子  
    │ ├── index.store.ts # 状态管理逻辑  
    │ ├── ViewModel.ts   # ViewModel逻辑  
    │ ├── types.ts       # TypeScript类型定义  
    │ ├── components/   
    │ │ ├── ProductGrid/  
    │ │ ├── FilterPanel/  
    │ │ └── SortingOptions/  
    │ └── styles/
第一步:定义你的模型
    // models/Product.model.ts
    export interface Product {
        id: string; // 产品ID
        name: string; // 产品名称
        price: number; // 产品价格
        category: string; // 产品分类
        inStock: boolean; // 是否有库存
    }
    // 产品接口定义了产品的ID, 名称, 价格, 分类和是否有库存信息。

    export interface FilterOptions {
        category: string[]; // 分类选项
        minPrice: number; // 最小价格
        maxPrice: number; // 最大价格
        inStock: boolean; // 是否有库存
    }
    // 筛选项接口定义了分类选项, 最小价格, 最大价格和是否有库存信息。
2. 设置您的店铺
// pages/产品页面/index.store.ts
import { create } from ‘zustand’;

interface 产品页面状态接口 {
  产品: 产品[];
  筛选条件: 过滤选项;
  更新产品: (产品: 产品[]) => void;
  更新筛选条件: (筛选条件: 过滤选项) => void;
}

const 使用产品仓库 = create<产品页面状态接口>((更新) => ({
  产品: [],
  筛选条件: {
    类别: [],
    minPrice: 0,
    maxPrice: 1000,
    库存状态: false
  },
  更新产品: (产品) => 更新({ 产品 }),
  更新筛选条件: (筛选条件) => 更新({ 筛选条件 })
}));
3, 创建您的视图模型
    // pages/ProductsPage/ViewModel.ts
    class ProductsViewModel {
      private store: ProductsStore;
      private uiStore: UIStore;

      constructor(store: ProductsStore, uiStore: UIStore) {
        this.store = store;
        this.uiStore = uiStore;
      }

      public async 获取产品() {
        try {
          this.uiStore.showLoader();
          const { data } = await ProductsAPI.getProducts(this.store.filters);
          this.store.setProducts(data);
        } catch (error) {
          toast.error('获取产品时出错');
        } finally {
          this.uiStore.hideLoader();
        }
      }

      public updateFilters(filters: Partial<FilterOptions>) {
        this.store.setFilters({
          ...this.store.filters,
          ...filters
        });
      }

      public shouldShowEmptyState(): boolean {
        return !this.uiStore.isLoading && this.getFilteredProducts().length === 0;
      }

      public shouldShowError(): boolean {
        return !!this.uiStore.error;
      }

      public shouldShowLoading(): boolean {
        return this.uiStore.isLoading;
      }

      public shouldShowProductDetails(): boolean {
        return !!this.uiStore.selectedProductId;
      }
    }
4. 自定义挂钩(Custom Hook)
    // pages/产品页面/index.hook.ts
    const useProductsPage = () => {
      const productsStore = useProductsStore();
      const uiStore = useUIStore();
      const viewModel = new ProductsViewModel(productsStore, uiStore);

      // isRefreshing 和 refreshDone 用于处理页面特有的情况,且位于视图模型之外
      return {
        viewModel,
        isRefreshing: uiStore.isRefreshing,
        refreshDone: () => uiStore.setRefreshing(false),
      };
    };
5. 视图组件插件
// pages/ProductsPage/index.tsx
const ProductsPage: FC = () => {
  const { viewModel, isRefreshing, refreshDone } = useProductsPage();

  useEffect(() => {
    viewModel.fetchProducts();
  }, [viewModel]);

  useEffect(() => {
    if (isRefreshing) {
      viewModel.fetchProducts();
      refreshDone();
    }
  }, [isRefreshing]);

  return (
    <div className="products-page">
      <FilterPanel />
      <ProductGrid />
      <SortingOptions />
    </div>
  );
};
我不容易学到的一些最佳实践 😄
1. 让视图模型保持专注
    // 好
    class ProductsViewModel {
      fetchProducts() { /* … */ }
      updateFilters() { /* … */ }
      sortProducts() { /* … */ }
    }

    // 例如,坏 — 职责混淆
    class ProductsViewModel {
      fetchProducts() { /* … */ }
      updateUserProfile() { /* … */ }
      handleCheckout() { /* … */ }
    }
2. 正确进行清理
// 使用 useEffect 来监听 viewModel 的变化。当 viewModel 发生变化时,会创建一个新的 AbortController 实例,并使用该实例的信号来调用 viewModel 的 fetchProducts 方法获取产品数据。在组件卸载时,会调用 controller 的 abort 方法来取消正在进行的请求。
useEffect(() => {  
  const controller = new AbortController();  
  viewModel.fetchProducts(controller.signal);  

  return () => controller.abort();  
}, [viewModel]);
3. 不要缓存ViewModels
    // 好
    const viewModel = new ProductsViewModel(store);

    // 不好 — 这会破坏响应性
    const viewModel = useMemo(() => new ProductsViewModel(store), [store]);
不太完美的地方(说实话)😕

1. 更多的样板文件
— 需要编写更多的初始代码
— 需要处理更多的文件
— 新成员的学习曲线更陡峭

2. 可能有些过分
— 对于简单的 CRUD 应用来说,这可能有些过分
— 小项目可能无法充分展现其优势
— 正确设置需要一定的时间

3. 团队一致同意是必要的
— 每个人都需要理解和遵守这个模式
— 需要一致的规范
— 文档变得非常重要

结束啦🎁😊

React中的MVVM模式并不是万能药,但它真的极大地提升了我们团队的效率和代码质量。从一个小功能开始试试,看看感觉怎么样。记住,目标是让代码更易维护,让生活更轻松!

大家可以在评论区留言提问哦。祝愉快编程!🚀

优化项

请继续阅读更多内容,了解我们如何解决视图模型实例的重复问题,并让垃圾回收器保持愉快。

在这里找到实现MVVM架构的代码库here,不过建议你先读一下系列中的下一篇再动手。

通俗易懂 🚀

感谢你加入我们__In Plain English_社区!在你离开前:

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP