React中的接口隔离原则

以云看科技 2024-08-31 02:01:37

SOLID 原则是我很早就学习到的软件设计概念,但是直到今天,它们仍然在我的职业生涯中发挥作用。如果不是因为它们,也许我永远不会关注我的代码质量和项目结构。尽管它们适用于面向对象开发,但无论我使用何种环境和模式,我总是将它们牢记在心。其中有一个 SOLID 原则是我在任何地方都能成功应用的,那就是接口隔离原则(ISP)。

接口隔离原则

这个原则的核心思想是:我们应该避免创建包含许多方法或值的大型接口。相反,我们应该创建更小的、满足特定需求的函数或类接口。据我所知,这个原则是在施乐公司提出来的,在那里他们的软件有一个单一的作业类,负责所有可以执行的任务。随着时间的推移,这个类不断地增长,使其可维护性逐渐变差。

interface Job { fax(): void; scan(): void; print(): void;}function print(job: Job) { // We're using only a single method from the interface // But we expect the entire interface to be implemented job.print();}

当他们将代码拆分为只涵盖特定任务的更小的类和接口时,代码变得更简单了。

interface PrintJob { print(): void;}function print(job: PrintJob) { // We only expect the interface to implement the print method job.print();}

上面只是用一个简单的例子来介绍接口隔离原则。使用较小的接口不仅更容易实现和维护,而且也更容易测试。

然而,在学习这些原则时,我发现很难将它们应用于前端开发中。前端开发中唯一能接触到的接口就是属性定义。并且它们总是服务于特定的组件,重用大型接口也不是真正的问题。

但是几年后,我发现不依赖于不需要的值这一抽象原则,在 React 的实际开发中是有用武之地的。

依赖太多

想象一下,我们有以下组件,它期望将一个用户对象作为属性传递给它:

interface Props { user: User;}function UserGreeting({ user }: Props) { return <h1>Hey, {user.name}!</h1>;}

不要关注对象引用、重新渲染等问题,这些并不是本文讨论的重点。

在这里,我们的组件需要一个用户对象,它是一个必填属性,所以我们提供了它。但是在仔细查看组件的实现后,不难发现它实际上只使用了一个值 —— 名字(name)。

这违反了接口隔离原则。

虽然这个组件只是使用了 user 对象的一小部分,但是它会让使用它的开发人员误以为它需要的是一个对象。这个组件带有一定的欺骗性,但是在编程中,诚实对大家更有帮助。

如果我们依赖于整个对象,可能会使组件更难使用。如果我们明确我们需要的值,那会更好。

interface Props { name: string;}function UserGreeting({ name }: Props) { return <h1>Hey, {name}!</h1>;}

在这个组件的原始实现中,当我们想要测试这个组件时,我们必须模拟整个用户对象。对用户对象的任何更改,比如添加一个额外的字段,都必须在组件的测试中反映出来——即使它们不是必需的。对组件的实现进行修改后,我们只设置了组件所需的基本值,并且它不会受到对象未来变化的影响,这样会更简单。

属性透传

另一种我们违反这个原则的常见方式是通过属性透传。这是在所有前端框架中都能找到的一种常见反模式。当我们将一个值通过多个不需要它的组件进行传递,以便它能到达一个特定的组件时,就会发生这种情况。

function Dashboard({ user }) { return ( <section> <Header /> ... </section> )}function Header({ user }) { return ( <header> <Navigation user={user} /> </header> )}function Navigation({ user }) { return ( <nav> <UserGreeting name={user.name} /> ... </nav> )}function UserGreeting({ name }) { return <h1>Hey, {name}!</h1>;}

这同样也是一个问题,因为我们再次对我们代码的读者进行了欺骗。查看我们组件的属性时,他们会认为他们需要用户对象以便从中渲染某些内容,但实际上,他们只是将其传递给他们的子组件。他们实际上并没有使用它。

在这种情况下,我们需要寻找一个不同的解决方案。

在 React 中,这通常是通过使用上下文或使用状态管理库来实现的,这将允许我们的组件直接读取该值。

function UserGreeting() { const user = useUser(); return <h1>Hey, {user.name}!</h1>;}

另一种解决方案是组件组合。

function Dashboard({ user }) { return ( <section> <Header> <Navigation> <UserGreeting name={user.name} /> </Navigation> </Header> </section> )}

通过这种方式,我们就不会将值传递给不需要它们的组件。

大道至简

接口隔离原则可简单概括为 —— 代码不应该依赖于它不使用的值和方法。如果你不需要某样东西,就要避免它。就这么简单。在上面的例子中,唯一的接口就是属性定义。我们没有做比平时更复杂的事情。重要的是我们遵循了这个原则。我们不希望我们的代码依赖于它不需要的东西,而这就是接口隔离原则的意义所在。

作者:ikoofe

来源-微信公众号:KooFE前端团队

本文翻译自文章「Interface Segregation Principle in React」:https://alexkondov.com/interface-segregation-principle-in-react/

出处:https://mp.weixin.qq.com/s/VN0bcxStq7EORMnmiLGelQ

0 阅读:3

以云看科技

简介:感谢大家的关注