作为前端开发人员,我们常常专注于创建漂亮的用户界面。然而,我们必须牢记,美也在于内在,完美像素的方法也应转化为我们的代码组织和结构。在本文中,我们将探讨一些基本的软件工程原则,每个前端开发人员都应该了解并在自己的项目中应用这些原则。
1. DRY(不要重复)
DRY 原则强调代码可重用性和维护的重要性。通过将通用功能提取到可重用组件、函数或模块中来避免重复代码。通过坚持 DRY 原则,您可以减少代码重复,提高可维护性,并使您的代码库更加模块化。React 鼓励组件驱动的架构,其中职责被隔离,以便于未来的开发和可扩展性。
让我们以一个简单的电子商务应用程序的产品页面为例。我们希望看到一个待售产品列表。我们可以将页面分解成更小的、可重复使用的组件。
组件:
- ProductCard:显示单个产品及其名称、价格和描述。
- ProductList:显示产品列表。
// ProductCard.js
import React from 'react';
const ProductCard = ({ product }) => {
return (
<div>
<h2>{product.name}</h2>
<p>Price: ${product.price}</p>
<p>Description: {product.description}</p>
</div>
);
};
export default ProductCard;
// ProductList.js
import React, { useState } from 'react';
import ProductCard from './ProductCard';
const ProductList = () => {
const [products, setProducts] = useState([
{ id: 1, name: 'Product 1', price: 9.99, description: 'Description 1' },
{ id: 2, name: 'Product 2', price: 19.99, description: 'Description 2' },
// ...
]);
return (
<div>
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
};
export default ProductList;
在这个示例中,我们可以看到,通过将有关产品的逻辑分离到 ProductCard
组件中,我们可以在 ProductList
组件的 map
功能中重复使用这些逻辑,从而避免为列表页面中的每个产品项目重复编写代码。
2. SOLID 原则
SOLID 是一个缩写词,代表面向对象设计的五个关键原则:
- 单一职责原则(SRP):每个模块或类应该只有一个更改的理由。
- 开放/封闭原则(OCP):软件实体应该对扩展开放,对修改关闭。
- 里氏替换原则(LSP):子类型应该可以替换其基本类型,而不改变程序的正确性。
- 接口隔离原则 (ISP):不应强迫客户端依赖于他们不使用的接口。
- 依赖倒置原则(DIP):高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
让我们看一下如何在 React TypeScript 组件中应用里氏替换原则 (LSP):
// Vehicle.ts
interface Vehicle {
drive(): void;
name: string;
}
// Car.ts
class Car implements Vehicle {
constructor(private name: string) {
this.name = name;
}
drive(): void {
console.log(`Driving a ${this.name}`);
}
}
// Motorcycle.ts
class Motorcycle implements Vehicle {
constructor(private name: string) {
this.name = name;
}
drive(): void {
console.log(`Riding a ${this.name}`);
}
}
// App.tsx
import React from 'react';
import { Vehicle } from './Vehicle';
import Car from './Car';
import Motorcycle from './Motorcycle';
function VehicleComponent(props: { vehicle: Vehicle }) {
props.vehicle.drive();
return <div>Driving a {props.vehicle.name}</div>;
}
const App = () => {
const car = new Car();
const motorcycle = new Motorcycle();
return (
<div>
<VehicleComponent vehicle={car} />
<VehicleComponent vehicle={motorcycle} />
</div>
);
};
export default App;
在此示例中,我们有一个定义 name
属性和 drive
方法的 Vehicle
接口。然后我们有两个具体的实现:Car
和 Motorcycle
,它们都实现 Vehicle
接口。
在 App 组件中,我们创建 Car
和 Motorcycle
的实例并将它们传递给 VehicleComponent。VehicleComponent
在传入的车辆对象上调用驱动方法。
LSP 确保我们可以用 Car
或 Motorcycle
替换 Vehicle
接口,而不会改变程序的正确性。VehicleComponent
与 Car
和 Motorcycle
实例无缝协作,展示了子类型对其基本类型的可替换性。
3. KISS(保持简单,笨)
KISS 原则提倡设计和实现的简单性。编写易于理解、简单且能做好一件事的代码。避免不必要的复杂性和过度设计,因为从长远来看,这可能会导致混乱和维护挑战。
让我们看一下 Counter
组件的 2 个实现。
// Complex Counter
import React, { useState, useEffect } from 'react';
import { debounce } from 'lodash';
const ComplexCounter = () => {
const [count, setCount] = useState(0);
const [clicked, setClicked] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (clicked) {
setCount(prev => prev + 1)
setClicked(false)
}
}, [clicked, setClicked]);
const handleClick = (clicked: boolean) => {
setClicked(!clicked);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={() => handleClick(clicked)}>Increment</button>
</div>
);
};
export default ComplexCounter;
// Simple Counter
import React, { useState } from 'react';
const SimpleCounter = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
export default SimpleCounter;
我们看到,ComplexCounter
的实现更难理解和维护,也更容易出错。
它为 clicked
和 useEffect
钩子引入了不必要的状态变量。
这是一个如何不实现 React 组件的示例。
4. YAGNI(你不需要它)
YAGNI 提醒我们避免基于推测的未来需求过早地添加功能。相反,应专注于正确实现当前所需的功能。当您构建一个非常以用户为中心的产品时,这一点变得非常重要。最好不要根据您认为用户可能想要的假设来引入新功能。使用适当的用户研究框架和原型设计方法。
通过遵循 YAGNI,您可以防止不必要的复杂性、减少开发时间并维护精简的代码库。
5. 干净的代码
干净的代码是可读的、可理解的、可维护的。遵循编码约定和最佳实践,使用有意义的变量名称,并编写不言自明的代码。保持函数和类小而集中,坚持一致的格式,并努力使代码库清晰。
让我们看一个简单的实用函数,用于出于数据安全目的隐藏部分用户的私人信息。
const hashUsersPrivateInformation = (privateInformation: string): string => {
// 计算私人信息的长度,以确定需要屏蔽多少个字符
const maxLength = privateInformation.length > 4 ? privateInformation.length - 4 : privateInformation.length;
// 创建正则表达式模式,以匹配所需的字符数
const regexPattern = `.{1,${maxLength}}`;
const regex = new RegExp(regexPattern);
return privateInformation.replace(regex, (match) => '*'.repeat(match.length));
};
我们看到:
- 函数的名称是自我描述的
- 它包含可以帮助其他开发人员的有用注释。
- 它有一个可以理解的主要目的。
我们应该以类似的方式构建我们的代码。
结论
将这些软件工程原理融入您的前端开发工作流程中,您可以编写质量更高的代码,改善与团队成员的协作,并构建强大且可扩展的应用程序。软件工程不仅仅是编写代码;还涉及编写代码。它是为复杂问题创建可靠、可维护且优雅的解决方案。
原文:https://dev.to/gboladetrue/software-engineering-principles-every-frontend-developer-should-know-1ej7?ref=dailydev