數組高階方法:map、filter、reduce實戰指南

文章簡介

在HarmonyOS應用開發中,數組操作是日常開發的重要組成部分。本文將深入探討三個核心的數組高階方法:mapfilterreduce,幫助開發者掌握這些強大的數據處理工具。

官方參考資料

  • ArkTS語言介紹
  • MDN-數組

版本説明:本文所有示例基於HarmonyOS Next API 10+ 和 DevEco Studio 4.0+

前置知識

在開始學習高階方法之前,確保你已瞭解以下基礎概念:

  • 數組的基本操作
  • 箭頭函數語法
  • 類型註解基礎
  • 基本的TS/JS語法

1. map方法詳解

1.1 基礎概念

map方法用於遍歷數組並對每個元素執行指定操作,返回一個新數組。

核心特性

  • 不改變原數組
  • 返回新數組長度與原數組相同
  • 適合數據轉換場景

1.2 基本語法

// 基礎語法
const newArray = originalArray.map((currentValue, index, array) => {
  // 返回處理後的元素
});

1.3 實戰示例

示例1:數值數組轉換
// 將價格數組轉換為含税價格(税率10%)
const prices: number[] = [100, 200, 300, 400];
const pricesWithTax = prices.map(price => price * 1.1);

console.log(pricesWithTax); // [110, 220, 330, 440]
示例2:對象數組屬性提取
interface User {
  id: number;
  name: string;
  age: number;
}

const users: User[] = [
  { id: 1, name: "張三", age: 25 },
  { id: 2, name: "李四", age: 30 },
  { id: 3, name: "王五", age: 28 }
];

// 提取用户姓名數組
const userNames = users.map(user => user.name);
console.log(userNames); // ["張三", "李四", "王五"]

// 添加新屬性
const usersWithStatus = users.map(user => ({
  ...user,
  isActive: user.age > 25
}));

1.4 map方法參數詳解

參數

類型

描述

是否必選

callback

function

對每個元素執行的函數

currentValue

any

當前處理的元素

-

index

number

當前元素的索引

-

array

Array

調用map的數組本身

-

thisArg

any

執行callback時的this值

2. filter方法詳解

2.1 基礎概念

filter方法用於篩選數組中滿足條件的元素,返回新數組。

核心特性

  • 返回數組長度 ≤ 原數組長度
  • 不會改變原數組
  • 適合數據篩選場景

2.2 基本語法

const filteredArray = originalArray.filter((currentValue, index, array) => {
  // 返回true保留元素,false過濾元素
});

2.3 實戰示例

示例1:基礎數據篩選
// 篩選偶數
const numbers: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = numbers.filter(num => num % 2 === 0);

console.log(evenNumbers); // [2, 4, 6, 8, 10]
示例2:複雜對象篩選
interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
}

const products: Product[] = [
  { id: 1, name: "手機", price: 2999, category: "electronics", inStock: true },
  { id: 2, name: "書籍", price: 59, category: "education", inStock: false },
  { id: 3, name: "耳機", price: 399, category: "electronics", inStock: true },
  { id: 4, name: "筆記本", price: 15, category: "office", inStock: true }
];

// 篩選有庫存的電子產品
const availableElectronics = products.filter(product => 
  product.category === "electronics" && product.inStock
);

console.log(availableElectronics); 
// [{ id: 1, name: "手機", ... }, { id: 3, name: "耳機", ... }]

2.4 多條件篩選技巧

// 價格範圍篩選
const priceRangeFilter = (minPrice: number, maxPrice: number) => {
  return products.filter(product => 
    product.price >= minPrice && product.price <= maxPrice
  );
};

const affordableProducts = priceRangeFilter(50, 500);
console.log(affordableProducts); // 價格在50-500之間的產品

3. reduce方法詳解

3.1 基礎概念

reduce方法將數組元素通過 reducer 函數累積為單個值。

核心特性

  • 返回任意類型的單個值
  • 功能最強大的數組方法
  • 適合聚合計算場景

3.2 基本語法

const result = array.reduce((accumulator, currentValue, index, array) => {
  // 返回累積值
}, initialValue);

3.3 實戰示例

示例1:數值計算
// 計算數組總和
const numbers: number[] = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);

console.log(sum); // 15

// 找出最大值
const max = numbers.reduce((acc, curr) => Math.max(acc, curr), numbers[0]);
console.log(max); // 5
示例2:複雜數據聚合
interface OrderItem {
  product: string;
  quantity: number;
  price: number;
}

const orderItems: OrderItem[] = [
  { product: "手機", quantity: 1, price: 2999 },
  { product: "耳機", quantity: 2, price: 399 },
  { product: "保護殼", quantity: 1, price: 59 }
];

// 計算訂單總金額
const totalAmount = orderItems.reduce((total, item) => {
  return total + (item.quantity * item.price);
}, 0);

console.log(totalAmount); // 3856

// 按產品分類統計
const productStats = orderItems.reduce((stats, item) => {
  if (!stats[item.product]) {
    stats[item.product] = { totalQuantity: 0, totalRevenue: 0 };
  }
  stats[item.product].totalQuantity += item.quantity;
  stats[item.product].totalRevenue += item.quantity * item.price;
  return stats;
}, {} as Record<string, { totalQuantity: number; totalRevenue: number }>);

4. 方法鏈式組合實戰

4.1 數據處理管道

在實際開發中,我們經常需要組合使用這些方法:

interface Employee {
  id: number;
  name: string;
  department: string;
  salary: number;
  yearsOfExperience: number;
}

const employees: Employee[] = [
  { id: 1, name: "張三", department: "技術部", salary: 15000, yearsOfExperience: 3 },
  { id: 2, name: "李四", department: "技術部", salary: 18000, yearsOfExperience: 5 },
  { id: 3, name: "王五", department: "市場部", salary: 12000, yearsOfExperience: 2 },
  { id: 4, name: "趙六", department: "技術部", salary: 22000, yearsOfExperience: 8 },
  { id: 5, name: "錢七", department: "人事部", salary: 10000, yearsOfExperience: 1 }
];

// 複雜數據處理:技術部員工,經驗3年以上,提取姓名和調整後薪資(+10%)
const processedData = employees
  .filter(emp => emp.department === "技術部" && emp.yearsOfExperience >= 3)
  .map(emp => ({
    name: emp.name,
    adjustedSalary: Math.round(emp.salary * 1.1), // 薪資調整10%
    experience: emp.yearsOfExperience
  }))
  .reduce((result, emp) => {
    result.totalAdjustedSalary += emp.adjustedSalary;
    result.employees.push(emp);
    return result;
  }, { totalAdjustedSalary: 0, employees: [] as Array<{name: string; adjustedSalary: number; experience: number}> });

console.log(processedData);

4.2 性能優化技巧

// 避免在循環中重複計算
const optimizedProcess = (data: Employee[]) => {
  // 先過濾,減少後續處理的數據量
  return data
    .filter(emp => emp.department === "技術部")
    .map(emp => {
      // 複雜計算只執行一次
      const bonus = calculateBonus(emp.yearsOfExperience);
      const adjustedSalary = emp.salary + bonus;
      
      return {
        ...emp,
        adjustedSalary,
        bonus
      };
    });
};

// 模擬獎金計算函數
const calculateBonus = (experience: number): number => {
  return experience * 500; // 每年經驗500元獎金
};

5. HarmonyOS特定應用場景

5.1 UI數據綁定

在HarmonyOS的ArkUI開發中,數組方法常用於數據處理:

// 在HarmonyOS組件中使用
@Component
struct ProductList {
  @State products: Product[] = [
    { id: 1, name: "HarmonyOS手機", price: 3999, category: "electronics", inStock: true },
    { id: 2, name: "智能手錶", price: 1299, category: "electronics", inStock: true },
    { id: 3, name: "平板電腦", price: 2599, category: "electronics", inStock: false }
  ];

  build() {
    Column() {
      // 使用filter和map準備顯示數據
      ForEach(this.products
        .filter(product => product.inStock)
        .map(product => ({ ...product, displayPrice: `¥${product.price}` })), 
        (item: Product & { displayPrice: string }) => {
          Text(item.name)
            .fontSize(16)
          Text(item.displayPrice)
            .fontSize(14)
            .fontColor(Color.Gray)
        }
      )
    }
  }
}

5.2 狀態管理數據處理

// 在AppStorage或狀態管理中處理數組數據
class ShoppingCartService {
  private items: CartItem[] = [];

  // 添加商品到購物車
  addItem(newItem: CartItem) {
    this.items = [...this.items, newItem];
  }

  // 計算總價
  getTotalPrice(): number {
    return this.items.reduce((total, item) => total + item.price * item.quantity, 0);
  }

  // 獲取商品種類數量
  getUniqueProductCount(): number {
    return this.items
      .map(item => item.productId)
      .filter((productId, index, array) => array.indexOf(productId) === index)
      .length;
  }

  // 按分類分組
  getItemsByCategory() {
    return this.items.reduce((groups, item) => {
      const category = item.category;
      if (!groups[category]) {
        groups[category] = [];
      }
      groups[category].push(item);
      return groups;
    }, {} as Record<string, CartItem[]>);
  }
}

6. 性能考慮和最佳實踐

6.1 性能優化建議

避免的陷阱

  • 不要在render或build方法中進行復雜計算
  • 避免創建不必要的中間數組
  • 合理使用緩存機制
// 不好的做法:每次渲染都重新計算
@Component
struct BadExample {
  @State data: number[] = [1, 2, 3, 4, 5];

  build() {
    Column() {
      // 每次build都會重新計算
      ForEach(this.data.map(x => x * 2), (item: number) => {
        Text(item.toString())
      })
    }
  }
}

// 好的做法:使用計算屬性或緩存
@Component
struct GoodExample {
  @State data: number[] = [1, 2, 3, 4, 5];
  private cachedData: number[] = [];

  // 使用aboutToAppear進行預處理
  aboutToAppear() {
    this.cachedData = this.data.map(x => x * 2);
  }

  build() {
    Column() {
      ForEach(this.cachedData, (item: number) => {
        Text(item.toString())
      })
    }
  }
}

6.2 錯誤處理

// 安全的數組操作
const safeArrayOperations = <T>(array: T[] | null | undefined) => {
  // 處理可能的空值或未定義
  const safeArray = array || [];
  
  return {
    mapped: safeArray.map(item => item),
    filtered: safeArray.filter(item => !!item),
    reduced: safeArray.reduce((acc, curr) => acc, 0)
  };
};

// 在HarmonyOS組件中的錯誤邊界處理
@Component
struct SafeArrayComponent {
  @State data: number[] | null = null;

  build() {
    Column() {
      // 使用可選鏈和空值合併
      ForEach((this.data ?? []).map(item => item * 2), (item: number) => {
        Text(item.toString())
      })
    }
  }
}

7. 注意事項和重要提示

7.1 常見陷阱

map方法注意事項

  • 一定要有return語句,否則會得到undefined數組
  • 不要在有副作用的操作中使用map
  • 確保回調函數是純函數
// 錯誤示例
const wrongMap = numbers.map(num => {
  console.log(num); // 副作用!
  // 缺少return語句
});

// 正確做法
const correctMap = numbers.map(num => num * 2);

filter方法注意事項

  • 回調函數必須返回boolean值
  • 注意處理空數組情況
  • 考慮使用類型守衞
// 使用類型守衞
const mixedArray: (number | string | null)[] = [1, "hello", null, 2, "world"];

// 只保留數字類型
const numbersOnly = mixedArray.filter((item): item is number => 
  typeof item === "number"
);

reduce方法注意事項

  • 不要忘記提供初始值
  • 確保累積器和當前值類型一致
  • 在複雜對象reduce時注意引用問題
// 提供正確的初始值類型
interface Accumulator {
  sum: number;
  count: number;
}

const stats = numbers.reduce<Accumulator>((acc, curr) => {
  return {
    sum: acc.sum + curr,
    count: acc.count + 1
  };
}, { sum: 0, count: 0 }); // 明確的初始值

7.2 性能監控

在大型數組操作時,建議添加性能監控:

const measurePerformance = <T>(operation: string, fn: () => T): T => {
  const start = Date.now();
  const result = fn();
  const end = Date.now();
  console.log(`${operation} 耗時: ${end - start}ms`);
  return result;
};

// 使用示例
const largeArray = Array.from({ length: 10000 }, (_, i) => i);

const result = measurePerformance('map操作', () => 
  largeArray.map(x => x * 2)
);

總結

通過本文的學習,你應該已經掌握了:

  • map:用於數據轉換,1:1映射
  • filter:用於數據篩選,返回子集
  • reduce:用於數據聚合,返回單個值
  • 方法鏈:組合使用實現複雜數據處理

這些高階方法在HarmonyOS應用開發中極其重要,特別是在:

  • UI數據準備
  • 狀態管理
  • 業務邏輯處理
  • 數據格式化

實踐建議

  1. 在簡單循環場景優先考慮高階方法
  2. 注意性能,避免不必要的中間數組
  3. 合理使用方法