在这篇文章中,我们将讨论如何使用高阶组件来保持你的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;
}
}
现在我们可以利用这个函数,将日志记录添加到add 和subtract 。
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的内容中通常被称为高阶组件。