接口隔离原则在React中的应用

原文信息: 查看原文查看原文

Interface Segregation Principle in React

- Alex Kondov

SOLID原则是我学习的第一个软件设计概念,直到今天它们仍然是我职业生涯中最有影响力的知识。如果不是它们,也许我永远不会开始关注我的代码质量和项目结构。

尽管它们最适合面向对象开发,但无论我所处的环境和模式如何,我总是将它们牢记在心。

如果我在任何地方都成功应用了一个SOLID原则,那就是关于接口隔离的原则。

接口隔离原则

这一原则是关于我们应该避免创建包含许多方法或值的大型接口的想法。相反,我们应该创建更小的、更具体的接口,以满足使用它们的函数或类的需要。

如果你想上一堂历史课,据我所知,这个原则是在施乐公司构思出来的,他们的软件有一个单一的Job类,负责你所能执行的所有任务。随着时间的推移,这个类增长到了一个点,它的可维护性成了问题。

interface Job {
    fax(): void;
    scan(): void;
    print(): void;
}

function print(job: Job) {
    // 我们只使用接口中的一个方法
    // 但我们期望整个接口被实现
    job.print();
}

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

interface PrintJob {
    print(): void;
}

function print(job: PrintJob) {
    // 我们只期望接口实现打印方法
    job.print();
}

这只是一个简单的例子,用来说明要点。使用较小的接口不仅更容易实现和维护,而且更容易测试。

当我学习这些原则时,我发现很难将它们转化为前端开发。我与之交互的唯一接口是属性定义。它们总是特定于组件的,重用大型接口并不是真正的问题。

但几年后,我发现不依赖于你不需要的值的抽象原则实际上在React中有一席之地。

依赖于太多事物的组件

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

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

不要关注引用相等性或重新渲染的问题,现在这不是重点。

我们的组件需要一个用户对象,这是一个强制性的属性,所以我们提供了它。但在仔细查看组件的实现后,我们注意到它实际上只使用了一个值——名字。

这是违反接口隔离原则的。

这句话听起来相当神秘,但不要太担心。这个组件只是有点欺骗性。它向使用它的开发人员传达了它需要一个对象,尽管它只使用了它的一小部分。但在编程中,就像在生活中一样,诚实是有帮助的。

如果我们依赖于整个对象,可能会使组件的使用变得更加困难。如果我们清楚我们需要的值会更好。

interface Props {
    name: string;
}

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

我们可以用来评估我们是否改进了设计的代理指标是评估是否更容易使用这个API进行测试。

使用它之前的属性,当我们想要测试组件时,我们必须模拟整个用户对象。用户对象的任何变化,比如添加一个额外的字段,都必须在组件的测试中反映出来——即使它们并不需要。

但有了这种实现,我们只需要改变组件期望的原始值,它就不会受到对象未来变化的影响。

这绝对更简单。

属性钻取

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

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>
  )
}

这样我们就不会向不需要它们的组件传递值。

接口隔离简化

这个想法的美妙之处在于它可以被简单地描述——代码不应该依赖于它不使用的值和方法。如果你不需要某样东西,就不要要求它。就这么简单。

上述例子中唯一的接口是属性定义。我们没有做比我们通常做的更复杂的事情。重要的是我们在遵循主要原则,而不是关于任意的实现。

我们不希望我们的代码依赖于它不需要的东西,这就是接口隔离的意义所在。

分享于 2024-08-31

访问量 23

预览图片