了解打字稿仿制药
介绍
干。不要重复自己。 Dry是一个缩写,成为工程基础知识的代名词。使用干燥,我们确保我们不会在存储库中反复重复的相同代码行;的确,当不可避免的需要添加一个调整时,我们遇到的困境是必须返回到每个单词重复的代码以单独更新。
。重复代码是我们在与客户互动时很快面临的挑战,我们需要使用React Hooks实现卡组件选择功能。我们必须重复自己的原因是,在多种情况下,我们需要实现卡选择状态以处理多种数据类型(string
,number
等)。在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;
}
Bike
和TennisRacquet
的类型都大不相同,但是它们具有共同点。要找到我们的TennisRacquets
和Bikes
的平均成本,我们有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
。如果我们的countSumMap
对Bike[]
进行了论证,那么它将能够在将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,
},
];
};
继续Bikes
和TennisRacquets
的概念,如果我们有两个不同的自行车和球拍列表,我们可以使用上述代码示例来处理两个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或类型缩小,并且在其他类型正在请求相同功能的情况下实现可重复性。
参考
致谢
特别感谢这项参与和学习背后的出色团队:Bryan,Ivan,Shreyas,Maggie,Bret,Michael,Michael和Maarten。Getting to Know TypeScript Generics帖子首先出现在CSE Developer Blog。