高阶组件。一种React应用的设计模式

65 阅读4分钟

Higher-order Components: A React Application Design Pattern

在这篇文章中,我们将讨论如何使用高阶组件来保持你的React应用程序整洁、结构良好和易于维护。我们将讨论纯函数如何保持代码整洁,以及这些相同的原则如何应用于React组件。

纯函数

如果一个函数遵守以下属性,它就被认为是纯函数。

  • 它所处理的所有数据都被声明为参数
  • 它不会改变它所给的数据或任何其他数据(这些通常被称为副作用)
  • 给予相同的输入,它将总是返回相同的输出。

例如,下面的函数add 是纯粹的。

function add(x, y) {
  return x + y;
}

然而,下面的函数badAdd 是不纯的。

let y = 2;
function badAdd(x) {
  return x + y;
}

这个函数是不纯的,因为它引用了它没有直接被赋予的数据。因此,有可能用相同的输入调用这个函数,而得到不同的输出。

let y = 2;
badAdd(3) // 5
y = 3;
badAdd(3) // 6

要了解更多关于纯函数的信息,你可以阅读马克-布朗的《合理的纯编程介绍》。

虽然纯函数非常有用,并使调试和测试应用程序变得更加容易,但偶尔你需要创建有副作用的不纯函数,或修改你无法直接访问的现有函数的行为(例如,来自库的函数)。为了实现这一点,我们需要看一下高阶函数。

高阶函数

高阶函数是一个当它被调用时返回另一个函数的函数。通常它们也接受一个函数作为参数,但这并不是一个函数被认为是高阶函数的必要条件。

假设我们有上面的add ,我们想写一些代码,当我们调用它时,在返回结果之前将结果记录到控制台。我们无法编辑add 函数,所以我们可以创建一个新的函数。

function addAndLog(x, y) {
  const result = add(x, y);
  console.log(`Result: ${result}`);
  return result;
}

我们决定记录函数的结果是有用的,现在我们想用一个subtract 函数做同样的事情。与其重复上述做法,我们可以写一个高阶函数,它可以接受一个函数并返回一个新的函数,该函数调用给定的函数并在然后返回之前记录结果。

function logAndReturn(func) {
  return function(...args) {
    const result = func(...args)
    console.log('Result', result);
    return result;
  }
}

现在我们可以利用这个函数,将日志记录添加到addsubtract

const addAndLog = logAndReturn(add);
addAndLog(4, 4) // 8 is returned, ‘Result 8’ is logged

const subtractAndLog = logAndReturn(subtract);
subtractAndLog(4, 3) // 1 is returned, ‘Result 1’ is logged;

logAndReturn 是一个高阶函数,因为它接收一个函数作为参数,并返回一个我们可以调用的新函数。这些对于包装你不能改变行为的现有函数来说真的很有用。关于这方面的更多信息,请查看M.David Green的文章《JavaScript中的高阶函数》,其中对这个问题有更详细的阐述。

参见CodePen上SitePoint(@SitePoint)的Pen
高阶函数

高阶组件

进入React领域,我们可以使用与上面相同的逻辑来处理现有的React组件,并赋予它们一些额外的行为。

注意:随着React 16.8发布的React Hooks的引入,高阶函数的用处变得很小,因为钩子可以实现行为共享而不需要额外的组件。也就是说,它们仍然是你腰带上的一个有用工具。

在本节中,我们将使用React Router,这是React事实上的路由解决方案。如果你想开始使用这个库,我强烈推荐React Router文档作为入门的最好地方。

React Router的Link组件

React Router提供了一个<NavLink> 组件,用于在React应用程序中的页面之间进行链接。这个<NavLink> 组件的一个属性是activeClassName 。当一个<NavLink> 有这个属性并且当前是活动的(用户在链接指向的URL上),该组件将被赋予这个类,使开发者能够对其进行样式设计。

这是一个非常有用的功能,在我们假设的应用程序中,我们决定总是要使用这个属性。然而,这样做之后,我们很快发现,这使得我们所有的<NavLink> 组件变得非常冗长。

<NavLink to="/" activeClassName="active-link">Home</NavLink>
<NavLink to="/about" activeClassName="active-link">About</NavLink>
<NavLink to="/contact" activeClassName="active-link">Contact</NavLink>

请注意,我们不得不每次都重复类名属性。这不仅使我们的组件变得冗长,而且还意味着如果我们决定改变类名,我们必须在很多地方这样做。

相反,我们可以写一个组件来包装<NavLink> 组件。

const AppLink = (props) => {
  return (
    <NavLink to={props.to} activeClassName="active-link">
      {props.children}
    </NavLink>
  );
};

而现在我们可以使用这个组件,它可以整理我们的链接。

<AppLink to="/home" exact>Home</AppLink>
<AppLink to="/about">About</AppLink>
<AppLink to="/contact">Contact</AppLink>

在React生态系统中,这些组件被称为高阶组件,因为它们采用一个现有的组件,并在不改变现有组件的情况下对其进行轻微的操作。你也可以把这些看作是包装组件,但你会发现它们在基于React的内容中通常被称为高阶组件。

继续阅读Higher-order Components:一个React应用的设计模式SitePoint上。