React hooks分离关注点
如果您使用 React 有一段时间了,您可能会遇到容器和表示组件, 或者智能和愚蠢组件。 这些术语描述了将 React 组件的 UI 层与逻辑层分开的模式。
将 UI 与业务逻辑分离并不是 React 独有的: 分离关注点是 70 年代就已经存在的设计原则。 例如,通常的做法是将访问数据库的代码与后端的业务逻辑分离。
所以在 React 中,我们通过创建包含所有逻辑的容器组件来解决这个问题,然后容器组件通过 props 将数据传递给表示组件。
随着 React hook 的引入,这方面又有了新的方法:使用定制的 hook 。
为什么我们要将逻辑与组件分开?
在我们开始将逻辑与我们的反应组件分离之前,我们应该知道原因。
组织我们的代码时,每个函数或组件只负责一件事情,它的优点是更容易更改和维护(Dave和Andrew在他们的书“务实的程序员”中称之为“正交性”)。
将此应用于反应意味着我们的组件看起来更干净和更有组织。例如,在编辑UI之前,我们不需要翻过逻辑墙。
像这样组织您的代码不仅使它看起来更好、更容易导航,而且还使它更容易更改,因为更改钩子不会影响UI,反之亦然。
测试也更容易访问:如果愿意,我们可以将逻辑与UI分开测试。然而,对我来说,最重要的优势是这种方法是如何组织我的代码的。
如何用 React hooks 解耦逻辑
为了将逻辑从组件中解耦,我们将首先创建一个自定义hook。
我们以这个组件为例。它计算基数和指数的指数值:
256.00
你可以在这里找到完整的源代码。
代码如下所示:
export const ExponentCalculator = () => {
const [base, setBase] = useState(4);
const [exponent, setExponent] = useState(4);
const result = (base ** exponent).toFixed(2);
const handleBaseChange = (e) => {
e.preventDefault();
setBase(e.target.value);
};
const handleExponentChange = (e) => {
e.preventDefault();
setExponent(e.target.value);
};
return (
<div className="blue-wrapper">
<input
type="number"
className="base"
onChange={handleBaseChange}
placeholder="Base"
value={base}
/>
<input
type="number"
className="exponent"
onChange={handleExponentChange}
placeholder="Exp."
value={exponent}
/>
<h1 className="result">{result}</h1>
</div>
);
};
复制代码
这可能看起来已经很好了,但是为了本教程的缘故,请想象这里有更多的逻辑。
作为第一步,我们将把逻辑移到一个自定义hook上,并在组件内部调用它。
const useExponentCalculator = () => {
const [base, setBase] = useState(4);
const [exponent, setExponent] = useState(4);
const result = (base ** exponent).toFixed(2);
const handleBaseChange = (e) => {
e.preventDefault();
setBase(e.target.value);
};
const handleExponentChange = (e) => {
e.preventDefault();
setExponent(e.target.value);
};
return {
base,
exponent,
result,
handleBaseChange,
handleExponentChange,
};
};
export const ExponentCalculator = () => {
const {
base,
exponent,
result,
handleExponentChange,
handleBaseChange,
} = useExponentCalculator();
// ...
};
复制代码
我们可以将这个hook移动到一个单独的文件中,以便更显著地分离关注点。
此外,我们还可以将hook进一步分离成更小的、可重用的函数。在这种情况下,我们只能提取计算指数
。
useExponentCalculator.js
const calculateExponent = (base, exponent) => base ** exponent;
const useExponentCalculator = () => {
const [base, setBase] = useState(4);
const [exponent, setExponent] = useState(4);
const result = calculateExponent(base, exponent).toFixed(2);
// ...
};
复制代码
测试这些函数比测试第一个示例中整个组件的代码要容易得多。我们可以用任何 Node . js 测试库来测试它们,它甚至不需要支持 React 组件。
现在,我们在组件和钩子的代码中有特定于框架的代码( React ),而我们的业务逻辑存在于我们后面定义的不同函数中(这些函数与框架无关)。
最佳做法
命名
我喜欢以组件的名称命名我的定制hook,作为使用
和组件名称的串联 (e.g. useExponentCalculator
). 然后我调用文件相同的hook。
您可能希望遵循不同的命名约定,但我建议在项目中保持一致。
如果我可以重用定制hook的部分内容,我通常会将其移动到src/hook
下的另一个文件中。
不要做的太多余
尽量讲求实效。如果组件只有几行JS,则没有必要分离逻辑。
CSS-in-JS
如果您正在使用CSS-in-JS库(UsStyle
),您可能也希望将此代码移动到另一个文件中。
你可以把它和钩子放在同一个文件里。但是,我更喜欢将其保留在同一个文件中的组件之上,或者在它增长过大时将其移动到自己的文件中。
结论
不管你是否认为使用自定义 hooks 提高了你的代码,最终还是取决于个人喜好。如果你的代码库没有包含很多逻辑,那么这种模式的优势对你来说就没有太大的相关性。
定制hooks只是增加模块化的一种方法; 我还强烈建议尽可能将组件和函数拆分为更小的、可重用的块。
这个话题在**《实用程序员》**一书中也有更一般的讨论。我写了一篇文章,涵盖了我最喜欢的主题的书,所以如果你有兴趣,一定要检查出来。
链接
我发现这里有一些文章对研究这篇文章有帮助:
-
Separation of concerns with custom React hooks – Arek Nawo
-
Presentational and Container Components – Dan Abramov
这是一篇关于容器模式的优秀文章,对于 React 中的类组件仍然非常有用。
我写的其他一些文章你可能会喜欢读:
-
Form validation with React Hooks WITHOUT a library: The Complete Guide
我写这篇文章是为了在不使用任何库的情况下创建自己的表单验证。我发现对于我的用例来说,一个库通常太多了,创建我自己的库只需要几行代码。
-
How to Make your React App a Progressive Web App (PWA)
一个详细的指南,你需要采取的所有步骤,把你的 React 应用程序成为一个 PWA ;它比大多数人想象的更容易。