iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >在React中应用SOLID原则的方法
  • 647
分享到

在React中应用SOLID原则的方法

2024-04-02 19:04:59 647人浏览 薄情痞子
摘要

目录1、单一职责原则(SRP)2、开放封闭原则(OCP)3、里氏替换原则(LSP)4、接口隔离原则(ISP)5、依赖倒置原则(DIP)6、小结在面向对象编程(OOP)中,SOLID

面向对象编程(OOP)中,SOLID 原则是设计模式的基础,它的每个字母代表一种设计原则:

  • 单一职责原则(SRP)
  • 开放封闭原则(OCP)
  • 里氏替换原则(LSP)
  • 接口隔离原则(ISP)
  • 依赖倒置原则(DIP)

下面就来看看每个原则的含义以及如何在 React 中应用 SOLID 原则!

1、单一职责原则(SRP)

单一职责原则的定义是每个类应该只有一个职责, 也就是只做一件事。这个原则是最容易解释的,因为我们可以简单地将其理解为“每个功能/模块/组件都应该只做一件事”。

在所有这些原则中,单一职责原则是最容易遵循的,也是最有影响力的一项,因为它极大提高了代码的质量。为了确保组件只做一件事,可以这样:

  • 将功能较多的大型组件拆分为较小的组件。
  • 将与组件功能无关的代码提取到单独的函数中。
  • 将有联系的功能提取到自定义 Hooks 中。

下面来看一个显示活跃用户列表的组件:

const ActiveUsersList = () => {
  const [users, setUsers] = useState([]) 
  useEffect(() => {
    const loadUsers = async () => {  
      const response = await fetch('/some-api')
      const data = await response.JSON()
      setUsers(data)
    }  
    loadUsers()
  }, [])
  const weekAGo = new Date();
  weekAgo.setDate(weekAgo.getDate() - 7);
  return (
    <ul>
      {users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo).map(user => 
         <li key={user.id}>
           <img src={user.avatarUrl} />
           <p>{user.fullName}</p>
           <small>{user.role}</small>
         </li>
      )}
    </ul>    
  )
}

这个组件虽然代码不多,但是做了很多事情:获取数据、过滤数据、渲染数据。来看看如何分解它。

首先,只要同时使用了 useState​ 和 useEffect,就可以将它们提取到自定义 Hook 中:

const useUsers = () => {
  const [users, setUsers] = useState([])
  useEffect(() => {
    const loadUsers = async () => {  
      const response = await fetch('/some-api')
      const data = await response.json()
      setUsers(data)
    }
    loadUsers()
  }, []) 
  return { users }
}
const ActiveUsersList = () => {
  const { users } = useUsers()  
  const weekAgo = new Date()
  weekAgo.setDate(weekAgo.getDate() - 7)
  return (
    <ul>
      {users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo).map(user => 
        <li key={user.id}>
          <img src={user.avatarUrl} />
          <p>{user.fullName}</p>
          <small>{user.role}</small>
        </li>
      )}
    </ul>    
  )
}

现在,useUsers Hook只关心一件事——从API获取用户。它使我们的组件代码更具可读性。

接下来看一下组件渲染的 JSX。每当我们对对象数组进行遍历时,都应该注意它为每个数组项生成的 JSX 的复杂性。如果它是一个没有附加任何事件处理函数的单行代码,将其保持内联是完全没有问题的。但对于更复杂的JSX,将其提取到单独的组件中可能是一个更好的主意:

const UserItem = ({ user }) => {
  return (
    <li>
      <img src={user.avatarUrl} />
      <p>{user.fullName}</p>
      <small>{user.role}</small>
    </li>
  )
}
const ActiveUsersList = () => {
  const { users } = useUsers() 
  const weekAgo = new Date()
  weekAgo.setDate(weekAgo.getDate() - 7)
  return (
    <ul>
      {users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo).map(user => 
        <UserItem key={user.id} user={user} />
      )}
    </ul>    
  )
}

这里将用于呈现用户信息的逻辑提取到了一个单独的组件中,从而使我们的组件更小、更可读。

最后,从 API 获取到的用户列表中过滤出所有非活跃用户的逻辑是相对独立的,可以在其他部分重用,所以可以将其提取到一个公共函数中:

const getOnlyActive = (users) => {
  const weekAgo = new Date()
  weekAgo.setDate(weekAgo.getDate() - 7)
  return users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo)
}
const ActiveUsersList = () => {
  const { users } = useUsers()
  return (
    <ul>
      {getOnlyActive(users).map(user => 
        <UserItem key={user.id} user={user} />
      )}
    </ul>    
  )
}

到现在为止,通过上面三步拆解,组件已经变得比较简单。但是,仔细观察会发现,这个组件还有优化的空间。目前,组件首先获取数据,然后需要对数据进行过滤。理想情况下,我们只想获取数据并渲染它,而不需要任何额外的操作。所以,可以将这个逻辑封装到一个新的自定义 Hook 中,最终的代码如下:

// 获取数据
const useUsers = () => {
  const [users, setUsers] = useState([])
  
  useEffect(() => {
    const loadUsers = async () => {  
      const response = await fetch('/some-api')
      const data = await response.json()
      setUsers(data)
    }
    loadUsers()
  }, [])
  
  return { users }
}
// 列表渲染
const UserItem = ({ user }) => {
  return (
    <li>
      <img src={user.avatarUrl} />
      <p>{user.fullName}</p>
      <small>{user.role}</small>
    </li>
  )
}
// 列表过滤
const getOnlyActive = (users) => {
  const weekAgo = new Date()
  weekAgo.setDate(weekAgo.getDate() - 7) 
  return users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo)
}
const useActiveUsers = () => {
  const { users } = useUsers()
  const activeUsers = useMemo(() => {
    return getOnlyActive(users)
  }, [users])
  return { activeUsers }
}
const ActiveUsersList = () => {
  const { activeUsers } = useActiveUsers()
  return (
    <ul>
      {activeUsers.map(user => 
        <UserItem key={user.id} user={user} />
      )}
    </ul>    
  )
}

在这里,我们创建了useActiveUsers Hook 来处理获取和过滤数据的逻辑,而组件只做了最少的事情——渲染它从 Hook 中获取的数据。

现在,这个组件只剩下两个职责:获取数据和渲染数据,当然我们也可以在组件的父级获取数据,并通过 props 传入该组件,这样只需要渲染组件就可以了。当然,还是要视情况而定。我们可以简单地将获取并渲染数据看作是“一件事”。

总而言之,遵循单一职责原则,我们有效地采用了大量独立的代码并使其更加模块化,模块化的代码更容易测试和维护。

2、开放封闭原则(OCP)

开放封闭原则指出“ 一个软件实体(类、模块、函数)应该对扩展开放,对修改关闭 ”。开放封闭原则主张以一种允许在不更改源代码的情况下扩展组件的方式来构造组件。

下面来看一个场景,有一个可以在不同页面上使用的 Header​ 组件,根据所在页面的不同,Header 组件的 UI 应该有略微的不同:

const Header = () => {
  const { pathname } = useRouter()
  return (
    <header>
      <Logo />
      <Actions>
        {pathname === '/dashboard' && <Link to="/events/new">Create event</Link>}
        {pathname === '/' && <Link to="/dashboard">Go to dashboard</Link>}
      </Actions>
    </header>
  )
}
const HomePage = () => (
  <>
    <Header />
    <OtherHomeStuff />
  </>
)
const DashboardPage = () => (
  <>
    <Header />
    <OtherDashboardStuff />
  </>
)

这里,根据所在页面的不同,呈现指向不同页面组件的链接。那现在考虑一下,如果需要将这个Header​组件添加到更多的页面中会发生什么呢?每次创建新页面时,都需要引用 Header​ 组件,并修改其内部实现。这种方式使得 Header 组件与使用它的上下文紧密耦合,并且违背了开放封闭原则。

为了解决这个问题,我们可以使用组件组合。Header​ 组件不需要关心它将在内部渲染什么,相反,它可以将此责任委托给将使用 children 属性的组件:

const Header = ({ children }) => (
  <header>
    <Logo />
    <Actions>
      {children}
    </Actions>
  </header>
)
const HomePage = () => (
  <>
    <Header>
      <Link to="/dashboard">Go to dashboard</Link>
    </Header>
    <OtherHomeStuff />
  </>
)
const DashboardPage = () => (
  <>
    <Header>
      <Link to="/events/new">Create event</Link>
    </Header>
    <OtherDashboardStuff />
  </>
)

使用这种方法,我们完全删除了 Header​ 组件内部的变量逻辑。现在可以使用组合将想要的任何内容放在Header中,而无需修改组件本身。

遵循开放封闭原则,可以减少组件之间的耦合,使它们更具可扩展性和可重用性。

3、里氏替换原则(LSP)

里氏替换原则可以理解为 对象之间的一种关系,子类型对象应该可以替换为超类型对象 。这个原则严重依赖类继承来定义超类型和子类型关系,但它在 React 中可能不太适用,因为我们几乎不会处理类,更不用说类继承了。虽然远离类继承会不可避免地将这一原则转变为完全不同的东西,但使用继承编写 React 代码会使代码变得糟糕(React 团队不推荐使用继承)。因此,对于这一原则不再过多解释。

4、接口隔离原则(ISP)

根据接口隔离原则的说法,客户端不应该依赖它不需要的接口。为了更好的说明 ISP 所针对的问题,来看一个呈现视频列表的组件:

type Video = {
  title: string
  duration: number
  coverUrl: string
}
type Props = {
  items: Array<Video>
}
const VideoList = ({ items }) => {
  return (
    <ul>
      {items.map(item => 
        <Thumbnail 
          key={item.title} 
          video={item} 
        />
      )}
    </ul>
  )
}

Thumbnail 组件的实现如下:

type Props = {
  video: Video
}
const Thumbnail = ({ video }: Props) => {
  return <img src={video.coverUrl} />
}

Thumbnail​ 组件非常小并且很简单,但它有一个问题:它希望将完整的视频对象作为 props​ 传入,但是仅有效地使用其属性之一(coverUrl)。

除了视频,我们还需要渲染直播的缩略图,这两种媒体资源会混合在同一个列表中。

下面来定义直播的类型 LiveStream :

type LiveStream = {
  name: string
  previewUrl: string
}

这是更新后的 VideoList 组件:

type Props = {
  items: Array<Video | LiveStream>
}
const VideoList = ({ items }) => {
  return (
    <ul>
      {items.map(item => {
        if ('coverUrl' in item) {
          return <Thumbnail video={item} />
        } else {
          // 直播组件,该怎么写?
        }
      })}
    </ul>
  )
}

这时就发现一个问题,我们可以轻松的区分视频和直播对象,但是不能将后者传递给Thumbnail​组件,因为Video和LiveStream​类型不兼容。它们包含了不同的属性来保存缩略图:视频对象调用coverUrl​,直播对象调用previewUrl​。这就是使组件依赖了比实际更多的props的原因所在。

下面来重构 Thumbnail​ 组件以确保它仅依赖于它需要的props:

type Props = {
  coverUrl: string
}
const Thumbnail = ({ coverUrl }: Props) => {
  return <img src={coverUrl} />
}

通过这样修改,现在我们可以使用它来渲染视频和直播的对略图:

type Props = {
  items: Array<Video | LiveStream>
}
const VideoList = ({ items }) => {
  return (
    <ul>
      {items.map(item => {
        if ('coverUrl' in item) {
          return <Thumbnail coverUrl={item.coverUrl} />
        } else {
          return <Thumbnail coverUrl={item.previewUrl} />
        }
      })}
    </ul>
  )
}

当然,这段代码还可以简化一下:

type Props = {
  items: Array<Video | LiveStream>
}
const VideoList = ({ items }) => {
  return (
    <ul>
      {items.map(item => (
         <Thumbnail coverUrl={'coverUrl' in item ? item.coverUrl : item.previewUrl} />
      ))}
    </ul>
  )
}

接口隔离原则主张最小化系统组件之间的依赖关系,使它们的耦合度降低,从而提高可重用性。

5、依赖倒置原则(DIP)

依赖倒置原则指出“要依赖于抽象,不要依赖于具体”。换句话说,一个组件不应该直接依赖于另一个组件,而是它们都应该依赖于一些共同的抽象。这里,“组件”是指应用程序的任何部分,可以是 React 组件、函数、模块或第三方库。这个原则可能很难理解,下面来看一个具体的例子。

有一个 LoginFORM 组件,它在提交表单时将用户凭据发送到某些 API:

import api from '~/common/api'
const LoginForm = () => {
  const [email, setEmail] = useState('')
  const [passWord, setPassword] = useState('')

  const handleSubmit = async (evt) => {
    evt.preventDefault()
    await api.login(email, password)
  }
  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
      <button type="submit">Log in</button>
    </form>
  )
}

在这段代码中,LoginForm​ 组件直接引用了 api 模块,因此它们之间存在紧密耦合。这种依赖关系就会导致一个组件的更改会影响其他组件。依赖倒置原则就提倡打破这种耦合,下面来看看如何实现这一点。

首先,从 LoginForm​ 组件中删除对 api​ 模块的直接引用,而是允许通过 props 传入所需的回调函数:

type Props = {
  onSubmit: (email: string, password: string) => Promise<void>
}
const LoginForm = ({ onSubmit }: Props) => {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const handleSubmit = async (evt) => {
    evt.preventDefault()
    await onSubmit(email, password)
  }
  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
      <button type="submit">Log in</button>
    </form>
  )
}

通过这样修改,LoginForm​ 组件不再依赖于 api​ 模块。向 API 提交凭证的逻辑是通过 onSubmit 回调函数抽象出来的,现在由父组件负责提供该逻辑的具体实现。

为此,创建了一个 ConnectedLoginForm​ 组件来将表单提交逻辑委托给 api 模块:

import api from '~/common/api'
const ConnectedLoginForm = () => {
  const handleSubmit = async (email, password) => {
    await api.login(email, password)
  }
  return (
    <LoginForm onSubmit={handleSubmit} />
  )
}

ConnectedLoginForm​ 组件充当 api​ 和 LoginForm 之间的粘合剂,而它们本身保持完全独立。这样就可以对这两个组件进行单独的修改和维护,而不必担心修改会影响其他组件。

依赖倒置原则旨在最小化应用程序不同组件之间的耦合。你可能已经注意到,最小化是所有 SOLID 原则中反复出现的关键词——从最小化单个组件的职责范围到最小化它们之间的依赖关系等等。

6、小结

通过上面的示例,相信你已经对如何在 React 中使用 SOLID 原则有了一定的了解。应用SOLID 原则使我们的 React 代码更易于维护和健壮。

但是需要注意,虔诚地遵循这些原则可能会造成破坏并导致代码过度设计,因此我们应该学会识别对组件的进一步分解或解耦何时会导致其复杂度增加而几乎没有任何好处。

到此这篇关于如何在React中应用SOLID原则?的文章就介绍到这了,更多相关React SOLID原则内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 在React中应用SOLID原则的方法

本文链接: https://www.lsjlt.com/news/164300.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 在React中应用SOLID原则的方法
    目录1、单一职责原则(SRP)2、开放封闭原则(OCP)3、里氏替换原则(LSP)4、接口隔离原则(ISP)5、依赖倒置原则(DIP)6、小结在面向对象编程(OOP)中,SOLID ...
    99+
    2024-04-02
  • 在React中怎么应用SOLID原则
    本篇内容介绍了“在React中怎么应用SOLID原则”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、单一职责原则(SRP)单一职责原则的定...
    99+
    2023-07-02
  • react中怎么应用SOLID原则
    这篇文章主要介绍“react中怎么应用SOLID原则”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“react中怎么应用SOLID原则”文章能帮助大家解决问题。什么是开闭原则?Robert c. Ma...
    99+
    2023-07-02
  • 如何应用 SOLID 原则在 React 中整理代码之开闭原则
    目录本系列其他文章什么是开闭原则?让我们从一个例子开始一个糟糕的解决方案解决方案是什么?让我们创建单独的用户组件注意总结 SOLID 是一套原则。它们主要是关心代码质量和可维护性的软...
    99+
    2024-04-02
  • 教你应用 SOLID 原则整理 React 代码之单一原则
    目录什么是单一责任原则?让我们从一个糟糕的例子开始1. 移动数据处理逻辑2. 可重用的数据获取钩子3. 分解 UI 组件让我们回顾一下我们刚刚做了什么总结SOLID 原则的主要是作为...
    99+
    2024-04-02
  • 如何应用SOLID原则
    本文小编为大家详细介绍“如何应用SOLID原则”,内容详细,步骤清晰,细节处理妥当,希望这篇“如何应用SOLID原则”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。什么是单一责任原则?单一责任原则告诉我们的是,每个...
    99+
    2023-07-02
  • Python 中的 SOLID 原则
    💂 个人网站:【海拥】【摸鱼游戏】【神级源码资源网】🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】💅 想寻找共同学...
    99+
    2023-09-11
    python 开发语言
  • Python中的SOLID原则实例详解
    目录前言单一职责原则开闭原则里氏替换原则接口隔离原则依赖倒置原则如何发现它?结论前言 SOLID 是一组面向对象的设计原则,旨在使代码更易于维护和灵活。它们是由 Robert &ld...
    99+
    2023-02-15
    python solid原则 面向对象的solid
  • “PHP 面向对象编程设计模式:理解 SOLID 原则及其应用”
    SOLID 原则是面向对象编程设计模式中的一组指导原则,旨在提高软件设计的质量和可维护性。由罗伯特·马丁(Robert C. Martin)提出,SOLID 原则包括: 单一职责原则(Single Responsibility Pri...
    99+
    2024-02-25
    SOLID 原则 面向对象编程 设计模式 单一职责原则 开放-封闭原则 里氏替换原则 接口隔离原则 依赖倒置原则
  • React ref的原理和应用
    本篇内容介绍了“React ref的原理和应用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!提到 ref或...
    99+
    2024-04-02
  • 在react中如何使用mockjs方法
    本篇内容主要讲解“在react中如何使用mockjs方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“在react中如何使用mockjs方法”吧!介绍mock意为“模仿”或"模拟&qu...
    99+
    2023-06-29
  • Context怎么在React中应用
    本篇文章给大家分享的是有关Context怎么在React中应用,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Context定义和目的Context 提供了一种在组件之间共享数据...
    99+
    2023-06-15
  • 在 React Native 中使用 CSS Modules的配置方法
    目录安装依赖和配置使用示例有些前端工程师希望也能像开发 web 应用那样,使用 CSS Modules 来开发 React Native。本文将介绍如何在 React Native ...
    99+
    2022-11-13
    React Native使用 CSS Modules React  CSS Modules
  • 在react中使用mockjs的方法你知道吗
    目录介绍安装&卸载&引入基础语法&规范 7种生成规则生成规则和属性值value的关系占位符@模拟接口总结介绍 mock意为“模仿&rdq...
    99+
    2024-04-02
  • 在 React 项目中全量使用 Hooks的方法
    目录前言React HooksuseStateuseReducer基础用法进阶用法useContextuseEffectuseLayoutEffectuseRefuseImperat...
    99+
    2022-11-13
    React使用 Hooks  React 项目使用 Hooks
  • React中使用Mobx的方法
    目录一、Mobx前端状态管理框架 基础概念?1. 什么是Mobx2. 什么是状态管理?3. 为什么需要状态管理?状态管理的一般思想(Flux)二、mobx的用法1. 安装 mobx2...
    99+
    2023-02-03
    React使用Mobx React使用Mobx
  • GraphQL在react中的应用示例详解
    目录什么是 GraphQLGraphQL出现的意义传统API存在的主要问题:GraphQL 如何解决问题GraphQL基本语法标量类型对象类型枚举类型GraphQL 内置指令什么是 ...
    99+
    2022-11-13
    GraphQL在react应用 GraphQL react
  • 如何在JavaScript中使用正则exec方法
    这篇文章将为大家详细讲解有关如何在JavaScript中使用正则exec方法,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。JavaScript可以做什么1.可以使网页具有交互性,例如响应用户...
    99+
    2023-06-14
  • 详解react应用中的DOM DIFF算法
    目录前言 什么是Virtual DOM? 使用Virtual DOM的原因 DOM 渲染页面的操作流程 Virtual DOM的优势如何将DOM用virtual DOM 来表...
    99+
    2024-04-02
  • React的核心原理和用法
    本篇内容介绍了“React的核心原理和用法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! 1. ...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作