了解打字稿仿制药
#javascript #react #frameworks #cse

了解打字稿仿制药

介绍

干。不要重复自己。 Dry是一个缩写,成为工程基础知识的代名词。使用干燥,我们确保我们不会在存储库中反复重复的相同代码行;的确,当不可避免的需要添加一个调整时,我们遇到的困境是必须返回到每个单词重复的代码以单独更新。

重复代码是我们在与客户互动时很快面临的挑战,我们需要使用React Hooks实现卡组件选择功能。我们必须重复自己的原因是,在多种情况下,我们需要实现卡选择状态以处理多种数据类型(stringnumber等)。在ctrl+c, ctrl+p例程中,我们决定重构我们的组件更可维护和适当地使用,以应对自定义挂钩的全部反应能力,同时仍然允许我们将我们的类型安全保持在TypeScript.

领域

因此,我们有机会利用TypeScript Generics来保持干燥,提高可维护性并保持反应可重复使用的组件的建议模式。

下面,这篇文章将带您介绍TypeScript Generics可以帮助您干燥代码的示例,同时演示通用的打字稿函数示例和一个反应自定义挂钩示例,该示例是在我们的客户参与过程中使用的更紧密方法。<<<<<<<<<<<<<<<<<<<<<<<< /p>

打字稿通用概述

打字稿通用是创建可重复使用的组件的一种方式,可以与不同类型的数据一起使用而不是一个。它允许您指定代表执行代码时将使用的实际类型的占位符类型。例如,您可以将数组定义为Array<T>,其中t是您想要的任何类型。

TLDR样本

如果您喜欢玩示例,请参阅此Code Sandbox React Hook Example

问题示例

让我们说,我们有两个函数几乎可以执行相同的操作,但是参数可能具有多种类型。可以说,我们有一家生意,我们出售自行车网球球拍。现在,这两个项目都不一样,尽管它们都附加了price标签。

所以我们有一辆自行车type

type Bike = {
  price: number;
  name: string;
  productType: number; //enum 1 - Electric, 2 - Mechanical
  tires: string;
  weight: number;
  length: number;
  height: number;
}

和一个网球球拍type

type TennisRacquet = {
  name: string;
  price: number;
  brand: number; // enum 1 - Babolat, 2 - Head, 3- Wilson
  weight: number;
  gripSize: number;
  size: number;
  racquetStrings: []string;
}

BikeTennisRacquet的类型都大不相同,但是它们具有共同点。要找到我们的TennisRacquetsBikes的平均成本,我们有2个功能:

// Average Bike Cost
const averageBikeCost: number = (bikes: Bike[]) => {
    const sum: number = bikes.map(bike => bike.price).reduce((val, sum) => val + sum, 0);
    const average = sum / bikes.length;
    return average
}

// Average Tennis Racquet Cost
const averageTennisRacquetCost: number = (tennisRacquets: TennisRacquet[]) => {
    const sum: number = tennisRacquets.map(tennisRacquet => tennisRacquet.price).reduce((val, sum) => val + sum, 0);
    const average = sum / tennisRacquets.length;
    return average
}

除了类型外,这两个功能看起来都相同。我们可以将这两个功能结合在一起,毫无困难,因为两者都使用price

const averageProductCost: number = (products: TennisRacquet[] | Bike[]) => {
    const sum: number = products.map(product => product.price).reduce((val, sum) => val + sum, 0);
    const average = sum / products.length;
    return average
}

但是,要引入更复杂的功能,以获取每种类型的球拍和自行车的平均值的映射,例如所有电动自行车的平均成本以及所有Babolat球拍的平均成本。

如果我们想将其包装在一个函数中,Countsummap,我们将不得不如下所示键入窄。

现在想象一下,如果我们添加我们的产品清单(例如,Shoes),是否要通过另一种类型扩展它。我们会大大增加这个示例,这会感到非常重复

const countSumMap: Map<string, { count: number; sum: number }> = (prods: Tennis[] | Bike[]) => {
    const newMap = new Map<string, { count: number; sum: number }>();
    if ("tires" in prods[0]) {
        prods.map(prod => {
          if (newMap.has(prod.productType)){
              const { count, sum } = newMap.get(prod.productType)
              newMap.set(prod.productType, { count: count + 1, sum: prod.price + sum });
          } else {
              newMap.set(prod.productType, { count: 1, sum: prod.price });
          }
        })
    } else if ("handleSize" in prods[0]) {
        prods.map(prod => {
            if (newMap.has(prod.racquetBrand)) {
                const { count, sum } = newMap.get(prod.racquetBrand)
                newMap.set(prod.racquetBrand, { count: count + 1, sum: prod.price + sum });
            } else {
                newMap.set(prod.racquetBrand, { count: 1, sum: prod.price });
            }
        })
    } else if ("shoeSize" in prods[0]) {
        // ... conditional logic for type Shoe 
    }
    return newMap
}

const tennisRacquetsCountPriceMap = countSumMap(tennisRacquets);
// Map(1) { 'Babolat' => { count: 1, sum: 150 } }
// ... extra logic to find average / mean price
const bikeCountPriceMap = countSumMap(bikes)
// Map(1) { 'Electric' => { count: 2, sum: 1500 } }
// ... extra logic to find average / mean price

这是打字稿仿制药派上用场的地方!

使用仿制药

我们可以使用类型的通用<T>来帮助我们减少此重复代码。

请注意下面显示的代码,这更简洁,也可以重复使用。countSumMap可用于具有price的任何对象。给出了keyName,以允许我们定义如何组织总结和计数。

const countSumMap: Map<string, { count: number; sum: number }> = <T>(prods: T[], keyName: string) => {
    if (!keyName) return new Map();

    const newMap = new Map<string, { count: number; sum: number }>();
        prods.map(prod => {
            if (newMap.has(prod[keyName])){
                const { count, sum } = newMap.get(prod[keyName])
                newMap.set(prod[keyName], { count: count + 1, sum: prod.price + sum });
            } else {
                newMap.set(prod[keyName], { count: 1, sum: prod.price });
            }
        })
    return newMap
}
// console.log(countSumMap(bikes, "productType"))
// Map(2) {
// 'Electric' => { count: 2, sum: 1500 },
// 'Mechanical' => { count: 1, sum: 3000 }
// }

countSumMap将推断<T>,作为我们作为参数的任何类型,将其作为prods。如果我们的countSumMapBike[]进行了论证,那么它将能够在将Bike推断为功能主体中使用的类型时处理逻辑。 TennisRacquet也是如此。

另一个示例âect自定义挂钩

注意:有关完整的工作示例,请参见此Code Sandbox Demo

在此示例中,假设我们正在尝试设置一个项目选择,以允许我们定义multiSelect模式,因此我们可以添加一个项目作为activeItem,或者我们可以选择multiple

>

  • 想拿着shift选择多个。

第一步是创建一个custom hook,该custom hook将使我们能够创建一个可以接收TypeScript.中任何type的React Hook,并允许在该Set中存储和修改项目。我们的自定义挂钩还将接收multiSelect布尔值,以定义我们是否要执行单个活动项目,或者多选择。

import { useState } from "react";

interface ItemState<T> {
    selectedItems: Set<T>;
}
interface ItemActions<T> {
    handleItemSelect: (item: T, multiSelectMode: boolean) => void;
}
export const useSelect = <T>(items: Set<T>): [ItemState<T>, ItemActions<T>] => {
    const [selectedItems, setSelectedItems] = useState<typeof items>(items);

    const handleItemSelect = (
        item: T,
        multiSelectMode: boolean = false
    ): void => {
        const copiedSet = new Set<T>(selectedItems);
        if (copiedSet.has(item)) {
            copiedSet.delete(item);
        } else {
            if (!multiSelectMode) {
                const newSet = new Set<T>();
                newSet.add(item);
                setSelectedItems(newSet);
                return;
            }
            copiedSet.add(item);
        }
        setSelectedItems(copiedSet);
    };

    return [
        {
            selectedItems,
        },
        {
            handleItemSelect,
        },
    ];
};

继续BikesTennisRacquets的概念,如果我们有两个不同的自行车和球拍列表,我们可以使用上述代码示例来处理两个types的选择。

网球球拍示例

import { useSelect } from "../hooks";

function TennisRacquetList({ racquets }: { racquets: ITennisRacquet[] }) {
    const [{ selectedItems }, { handleItemSelect }] = useSelect(
        new Set<string>()
    );
    return (
        <div className="card-wrapper">
            {racquets.map((racquet) => (
                <TennisRacquet
                  key={racquet.GUID}
                  isActive={selectedItems.has(racquet.GUID)}
                  tennisRacquet={racquet}
                  selectOne={handleItemSelect}
                  multiSelect={handleItemSelect}
                />
            ))}
        </div>
    );
}

export default TennisRacquetList;

自行车示例

import { useSelect } from "../hooks";

function Bikes({ bikes }: { bikes: IBike[] }) {
    const [{ selectedItems }, { handleItemSelect }] = useSelect(
        new Set<number>()
    );
    return (
        <div className="card-wrapper">
            {bikes.map((bike) => (
                <Bike
                  key={bike.id}
                  isActive={selectedItems.has(bike.id)}
                  bike={bike}
                  selectOne={handleItemSelect}
                  multiSelect={handleItemSelect}
                />
            ))}
        </div>
    );
}

export default Bikes;

概括

通用打字稿类型允许对逻辑的可重复使用功能,这可能非常相似,但仅根据类型而有所不同。我们可以使用TypeScript Generics来实现可重复使用性,而不是在此用例中创建type predicates或类型缩小,并且在其他类型正在请求相同功能的情况下实现可重复性。

参考

致谢

特别感谢这项参与和学习背后的出色团队:BryanIvanShreyasMaggieBretMichaelMichaelMaarten

Getting to Know TypeScript Generics帖子首先出现在CSE Developer Blog