亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定

在 JavaScript 和 TypeScript 中應用 SOLID 原則

標簽:
JavaScript
开场白

SOLID 原则是编写干净、可扩展和可维护的软件的基础。虽然这些原则起源于面向对象编程(OOP),但它们也适用于 JavaScript(JS)和 TypeScript(TS)中的前端框架,例如 React 和 Angular。本文将通过 JS 和 TS 中的实际示例来解释每个原则。

zh: (此处省略)

1. 单一职责原则(SRP)

原则: 一个类或模块应该只有一个变更的理由。这意味着它应该只为一个功能负责。

  • JavaScript (React) 例子:

在 React 中,我们常常会遇到承担太多职责的组件——比如这样的情况,这些组件既要管理用户界面,又要处理业务逻辑。

反模式:
这是一种不好的实践方式。

    function UserProfile({ userId }) {
      const [user, setUser] = useState(null);

      useEffect(() => {
        fetchUserData();
      }, [userId]);

      async function fetchUserData() {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        setUser(data);
      }

      return <div>{user?.name}</div>;
    }

点击全屏 播出全屏退出

在这里,UserProfile 组件不仅负责UI渲染,还负责数据加载,因此违反了SRP。

重新整理:

    // 这是一个自定义钩子,用于获取用户数据
    function useUserData(userId) {
      const [user, setUser] = useState(null);

      useEffect(() => {
        async function fetchUserData() {
          const response = await fetch(`/api/users/${userId}`);
          const data = await response.json();
          setUser(data);
        }
        fetchUserData();
      }, [userId]);

      return user;
    }

    // UI 组件
    function UserProfile({ userId }) {
      const user = useUserData(userId); // 将数据获取逻辑移到钩子

      return <div>{user?.name}</div>;
    }

全屏显示 退出全屏

通过使用一个自定义钩子(如useUserData),我们将数据获取逻辑和UI分离,使每一部分只负责一项工作。

  • TypeScript(Angular)示例:

在 Angular 这个框架中,服务和组件有时候可能会因为承担多种职责而变得杂乱。

反模式行为:
反模式,也称为不良模式或常见坏习惯,是指在软件开发或其他领域中反复出现的低效或有害的做法。

    @Injectable()
    export class UserService {
      constructor(private http: HttpClient) {}

      getUser(userId: string) {
        return this.http.get(`/api/users/${userId}`);
      }

      updateUserProfile(userId: string, data: any) {
        // 更新资料并处理通知事宜
        return this.http.put(`/api/users/${userId}`, data).subscribe(() => {
          console.log('已更新用户');
          alert('资料更新成功了');
        });
      }
    }

进入全屏 退出全屏

这个 UserService 负责多种任务:取回、修改和处理通知信息。

重写:

@Injectable()
export class UserService {
  constructor(private http: HttpClient) {}

  getUser(userId: string) {
    return this.http.get(`/api/users/${userId}`);
  }

  updateUserProfile(userId: string, data: 数据) {
    return this.http.put(`/api/users/${userId}`, data);
  }
}

// 分离的通知服务
@Injectable()
export class NotificationService {
  提醒(message: string) {
    alert(message);
  }
}

全屏模式退出全屏

通过将通知处理拆分到一个单独的服务中(通知服务),我们确保每个类只负责一件事。

zh: zh: ……

2. 开闭原则(OCP,开放封闭原则)

原则: 软件模块应该易于扩展但难于修改。这意味着你可以扩展模块的功能而不改动其源代码。

  • JavaScript (React) 例子:

你可能有一个运行良好的表单验证函数,但未来可能需要增加额外的验证逻辑。

常见错误模式,

    function validate(input) {
      if (input.length < 5) {
        return '输入太短了';
      }
      if (!input.includes('@')) {
        return '这不是有效的电子邮件地址';
      }
      return '输入有效';
    }

开启全屏 关闭全屏

每次需要新增一个验证规则时,都得修改这个函数的内容,这样就违反了开闭原则(OCP)。

重构

    function validate(input, rules) {
      return rules.map(rule => rule(input)).find(result => result !== 'Valid') || '有效输入';
    }

    const lengthRule = input => input.length >= 5 ? 'Valid' : '输入太短了';
    const emailRule = input => input.includes('@') ? 'Valid' : '无效的电子邮件';

    validate('[email protected]', [lengthRule, emailRule]);

全屏模式 退出全屏

现在,我们可以扩展验证规则的范围而无需修改原始的 validate 函数,从而遵循 OCP(开放封闭原则)。

  • TypeScript(Angular)示例:

在 Angular 中,服务和组件应当被设计成允许添加新功能而不改动核心逻辑。

不良模式:

    export class NotificationService {
      send(type: 'email' | 'sms', message: string) {
        if (type === 'email') {
          // 发送电子邮件
        } else if (type === 'sms') {
          // 发送短信
        }
      }
    }

切换到全屏 / 退出全屏

此服务不符合OCP原则,因为每次添加新的通知类型时(例如,推送通知,),你就需要去修改发送方法。

优化:

    interface 通知接口 {
      发送(message: string): void;
    }

    @Injectable()
    export class 电子邮件通知 implements 通知接口 {
      发送(message: string) {
        // 发送邮件的逻辑
      }
    }

    @Injectable()
    export class 短信通知 implements 通知接口 {
      发送(message: string) {
        // 发送短信的逻辑
      }
    }

    @Injectable()
    export class 通知服务类 {
      constructor(private 通知s: 通知接口[]) {}

      发送通知(message: string) {
        this.通知s.forEach(n => n.发送(message));
      }
    }

点击全屏看 点击退出全屏

现在,添加新的通知类型只需创建新的类即可,而无需修改NotificationService(NotificationService)。

zh: (内容省略)

3. Liskov 替换原则 (LSP)

原则: 子类必须可以替代基类。派生类或其它组件可以用来替换基类而不会影响程序的正确性。

  • JavaScript (React) 例子:

当我们使用高阶组件(HOCs)或根据条件渲染不同的组件时,LSP可以帮助确保所有组件的行为都更加可预测。

反模式:

    function Button({ onClick }) {
      return <button onClick={onClick}>点击我</button>;
    }

    function LinkButton({ href }) {
      return <a href={href}>点击我</a>;
    }

    <Button onClick={() => {}} />;
    <LinkButton href="/home" />;

点击全屏模式,再次点击退出全屏。

在这里,Button 和 LinkButton 不一致。一个使用了 onClick,另一个使用了 href,这使得替换变得困难。

改写:

function Clickable({ children, onClick }) {
  return <div onClick={onClick}>{children}</div>;
}

function Button({ onClick }) {
  return <Clickable onClick={onClick}>
    <button>点我</button>
  </Clickable>;
}

function LinkButton({ href }) {
  return <Clickable onClick={() => window.location.href = href}>
    <a href={href}>点我</a>
  </Clickable>;
}

切换到全屏模式 切换回正常模式

目前,Button 和 LinkButton 遵循 LSP,它们的行为相似。

  • TypeScript(Angular中的示例):

反模式:

    class Rectangle {
      constructor(protected width: number, protected height: number) {}

      area() {
        return this.width * this.height;
      }
    }

    class Square extends Rectangle {
      constructor(size: number) {
        super(size, size);
      }

      setWidth(width: number) {
        this.width = width;
        this.height = width; // 这样做违反了Liskov替换原则
      }
    }

进入全屏,退出全屏

在 Square 中修改 setWidth 方法会违背 LSP 原则,因为 Square 的行为与 Rectangle 不同,这不符合 LSP 原则。

重构代码:
</TRANSLATION>

    class Shape {
      面积(): number {
        抛出新错误('方法未实现');
      }
    }

    class 矩形 extends Shape {
      constructor(private 宽: number, private 高: number) {
        超类();
      }

      面积() {
        返回 this.宽 * this.高;
      }
    }

    class 正方形 extends Shape {
      constructor(private 边长: number) {
        超类();
      }

      面积() {
        返回 this.边长 * this.边长;
      }
    }

全屏模式(按X退出)

这样一来,Square和Rectangle可以互换,这样不会违反LSP。

zh: (省略)

4. 接口隔离原则 (ISP):

准则: 客户不应被迫依赖他们未使用的接口。

  • JavaScript (React) 例子:

React组件可能会收到多余的props,导致代码紧密耦合且臃肿。

反模式:

    function 多功能组件({ user, posts, comments }) {
      return (
        <div>
          <用户资料 user={user} />
          <用户帖子 posts={posts} />
          <用户评论 comments={comments} />
        </div>
      );
    }

进入全屏 退出全屏

在这里,组件依赖于多个属性,哪怕它有时候并不需要用到这些属性。

重构代码:

function 用户资料组件({ 用户 }) {
  return <UserProfile 用户={用户} />;
}

function 用户帖子组件({ 帖子 }) {
  return <UserPosts 帖子={帖子} />;
}

function 用户评论组件({ 评论 }) {
  return <UserComments 评论={评论} />;
}

进入全屏 退出全屏

通过这种方式,将组件拆分成更小的部分,每个拆分后的部分仅依赖实际需要的数据。

在Angular中使用TypeScript的示例

常见误区:

    interface 工人 {
      工作(): void;
      用餐(): void;
    }

    class 人类工人 implements 工人 {
      工作() {
        console.log('工作');
      }
      用餐() {
        console.log('用餐');
      }
    }

    class 机器人工人 implements 工人 {
      工作() {
        console.log('工作');
      }
      用餐() {
        throw new Error('机器人不进食'); // 违背了ISP原则
      }
    }

全屏, 退出全屏

在这里,RobotWorker不得不实现一个无关紧要的eat方法。

重构:

    interface Worker {
      work(): void;
    }

    interface Eater {
      eat(): void;
    }

    class HumanWorker implements Worker, Eater {
      work() {
        console.log('在工作');
      }

      eat() {
        console.log('在吃饭');
      }
    }

    class RobotWorker implements Worker {
      work() {
        console.log('在工作');
      }
    }

进入全屏模式,退出全屏模式

通过将Worker和Eater接口分离,我们确保客户端仅依赖于所需的功能。

zh: zh: (此处省略)

第五 依赖倒置原则(DIP):

原则: 高层模块不应该依赖于底层模块,两者都应依赖于抽象概念,如接口。

  • JavaScript (React) 例子:

反模式(Anti-pattern):

    function fetchUser(userId) {
      return fetch(`/api/users/${userId}`).then(res => res.json());
    }

    function UserComponent({ userId }) {
      const [user, setUser] = useState(null);

      useEffect(() => {
        fetchUser(userId).then(setUser);
      }, [userId]);

      return <div>{user?.name}</div>;
    }

切换到全屏 退出全屏

在这里,UserComponent 和 fetchUser 函数紧密结合。

改写:

    function UserComponent({ userId, fetchUserData }) {
      const [user, setUser] = useState(null);

      useEffect(() => {
        fetchUserData(userId).then(setUser);
      }, [userId, fetchUserData]);

      return <div>{user?.name}</div>;
    }

    // 法
    <UserComponent userId={1} fetchUserData={fetchUser} />;

进入全屏 退出全屏

通过在组件中引入 fetchUserData,我们可以在测试或不同场景下轻松替换其实现。

  • TypeScript(Angular)示例:

常见陷阱: 在编程或设计中,反模式指那些看似合理但实则会导致问题的常见做法:

    @Injectable()
    export class UserService {
      constructor(private http: HttpClient) {}

      getUser(userId: string) {
        return this.http.get(`/api/users/${userId}`);
      }
    }

    @Injectable()
    export class UserComponent {
      constructor(private userService: UserService) {}

      loadUser(userId: string) {
        this.userService.getUser(userId).subscribe(user => console.log(user));
      }
    }

点击这里切换到全屏 点击这里退出全屏

UserComponent 与 UserService 紧密耦合,这使得更换 UserService 变得很麻烦。

代码重构:

    interface 用户信息服务 {
      取用户(userId: string): Observable<用户>;
    }

    @Injectable()
    export class Api用户信息服务 implements 用户信息服务 {
      constructor(private http: HttpClient) {}

      取用户(userId: string) {
        return this.http.get<用户>(`/api/users/${userId}`);
      }
    }

    @Injectable()
    export class 用户组件类 {
      constructor(private service: 用户信息服务) {}

      取用户(userId: string) {
        this.service.get(userId).subscribe(用户信息 => console.log(用户信息));
      }
    }

点击全屏显示 点击退出全屏

通过依赖UserService接口,UserComponent 现在从具体的 ApiUserService 实现中解耦。


接下来做什么?

不论你是在前端使用 React 或 Angular 等框架,还是在后端使用 Node.js 技术,SOLID 原则都作为指南,确保你的软件架构保持稳固。

将这些原则融入您的项目中:

  • 定期实践: 重构现有代码库以应用SOLID原则,并确保代码符合这些原则。
  • 与团队合作: 通过代码审查和关于干净架构的讨论来促进最佳实践。
  • 保持好奇: SOLID原则只是开始。 探索其他架构模式,如MVC、MVVM或CQRS,这些模式基于这些基本原则,可以帮助你进一步优化设计。

    • *
最后

SOLID 原则是确保你的代码干净、易于维护和可扩展,非常有效的方法,即使是在 JavaScript 和 TypeScript 框架(如 React 和 Angular)中也同样适用。应用这些原则可以使开发人员编写灵活且可重用的代码,这样的代码易于扩展和重构,以适应不断变化的需求。通过遵循 SOLID,让你的代码库既稳健又为未来的增长做好准备。

點擊查看更多內容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優質文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學習,寫下你的評論
感謝您的支持,我會繼續努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦
今天注冊有機會得

100積分直接送

付費專欄免費學

大額優惠券免費領

立即參與 放棄機會
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號

舉報

0/150
提交
取消