在React中处理拦截器

哈Ha!

带着令人难以置信的自豪和满足,我们今晚将一本有关React的新书交给了印刷厂。



在这种情况下,我们为您提供Dan Dan Abramov文章的简短翻译,其中描述了第16版React中拦截器的使用。 我们自己已经很期待的这本书在第5章中进行了介绍。

上周, 我和索菲·阿尔珀特Sophie Alpert)在React Conf会议上介绍了“拦截器”的概念,随后瑞安·弗洛伦斯Ryan Florence)对该主题进行了详细讨论

我强烈建议您观看本次全体演讲,以熟悉我们正在尝试借助拦截器解决的一系列问题。 但是,我非常感谢您花费大量时间,因此我决定在本文中简要概述一下拦截器的主要注意事项。
注意:React拦截器仍处于试验阶段。 现在无需深入研究它们。 还要注意,该出版物阐述了我的个人观点,这可能与React开发人员的立场不一致。
为什么需要拦截器?

众所周知,组件组织和向下的数据流有助于以小的,独立的和可重用的片段形式组织大型UI。 但是,由于逻辑保留状态并且不能提取到功能或其他组件中,因此通常不可能将复杂的组件分解到某个限制之外 。 有时那些说React无法实现“职责分离”的人对此表示抱怨。
这种情况非常普遍,例如与动画,表单处理,连接到外部数据源以及我们可能需要对组件执行的许多其他操作相关联。 尝试仅使用组件来解决此类问题,通常可以得到:

  • 难以重构和测试的巨型组件
  • 各个组件和生命周期方法之间的逻辑重复
  • 复杂的图案 ,尤其是渲染道具和高阶组件。

我们认为拦截器是解决所有这些问题的最有前途的工具。 拦截器以可重复使用的隔离单元的形式帮助组织组件内部的逻辑





拦截器符合React原理(明确的数据流和组成),并且在组件内部,而不仅仅是组件之间 。 这就是为什么在我看来拦截器自然适合于React组件模型。

与诸如渲染属性或高阶组件之类的模式不同,嗅探器不会给您的组件树增加不必要的深度附件。 而且,它们不具有杂质固有的缺点

即使乍一看,拦截器也会扭曲您(就像我一样!),我建议您给这个选项一个机会并尝试一下。 我想你会喜欢的。

React是由于拦截器而膨胀吗?

在我们详细介绍了拦截器之前,您可能会担心在React中添加拦截器只是实体的乘积。 这是公平的批评。 我认为:尽管在短期内,您确实会感到额外的认知负担(研究它们),但最终您只会感觉更好。

如果拦截器扎根于React社区,那么实际上将减少编写React应用程序时必须管理的实体数量 。 使用拦截器,您可以不断使用函数,而不必在函数,类,高阶组件和组件渲染之间切换。

至于实现大小的增加,在拦截器支持下的React应用程序仅增加了约1.5kB(最小+ gzip)。 尽管这本身并不过分,但是使用拦截器时,您的程序集大小甚至有可能减小 ,因为与使用类的等效代码相比,拦截器代码通常会被缩小得更好。 以下示例有些极端,但它清楚地说明了为什么一切如此( 单击以展开整个线程):



拦截弹的提议没有革命性的变化 。 即使您开始在新组件中使用拦截器,您的代码也可以正常工作。 实际上,这正是我们的建议:不要全局重写任何内容! 等到所有关键代码中都建立了拦截器的使用才是明智的。 不过,如果您可以试用alpha版本16.7,并向我们提供有关拦截器建议的反馈以及报告任何错误 ,我们将不胜感激。

什么是拦截器?

要了解什么是拦截器,您需要返回上一步并考虑什么是代码重用。

今天,有许多方法可以在React应用程序中重用逻辑。 因此,要计算某些内容,您可以编写简单的函数,然后调用它们。 您还可以编写组件(它们本身可以是函数或类)。 这些组件功能更强大,但是在使用它们时,您需要显示一些UI。 因此,使用组件不方便传输非可视逻辑。 因此,我们介绍了诸如渲染属性和高阶组件之类的复杂模式。 如果只有一种通用的方法可以复用代码,React不会使它变得更容易吗?

函数对于可重用的代码似乎是完美的。 在功能之间传递逻辑最便宜。 但是,React的本地状态不能存储在函数内部。 您不能从类组件中提取诸如“跟踪窗口大小和更新状态”或“为某个时间设置值动画”之类的行为,而无需重组代码或引入诸如Observables之类的抽象方法。 两种方法都只会使代码复杂化,而React以其简单性对我们很好。

拦截器可以解决这个问题。 多亏了拦截器,您可以从一个函数中使用React功能(例如状态)-只需调用一次即可。 React提供了几种与React Brick对应的内置拦截器:状态,生命周期和上下文。

因为拦截器是常规的JavaScript函数,所以您可以组合React中提供的内置拦截器来创建“本机拦截器” 。 因此,只需一行代码即可解决复杂的问题,然后将其在您的应用程序中相乘,或在React社区中共享

注意:严格来说,您自己的拦截器不属于React的功能。 编写自己的拦截器的能力自然源于其内部组织。

给我看看代码!

假设我们要为组件订阅当前的窗口宽度(例如,以显示其他内容或较窄的查看区域)。
今天,可以通过几种方式编写类似的代码。 例如,要创建一个类,创建多个生命周期方法,或者甚至在寻求重用的情况下甚至诉诸渲染属性或应用更高阶的组件。 但是,我认为没有什么比这更好的了:



gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eae

如果您阅读此代码,则表示它完全按照其说明进行操作 。 我们使用组件内部窗口的宽度,如果组件发生更改,React将重绘您的组件。 这正是拦截器所需要的-使组件真正具有声明性,即使它们包含状态和副作用。

考虑如何实现自己的拦截器。 我们可以使用本地React状态将当前窗口宽度保留在其中,并在使用副作用调整窗口大小时设置状态:



gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eae

如上所示,React的内置拦截器(如useStateuseEffect用作积木。 我们可以直接从组件中使用它们,也可以从它们组装我们自己的拦截器,例如useWindowWidth 。 使用自己的拦截器似乎比使用内置的React API更为习惯。

此评论中了解有关内置拦截器的更多信息。

拦截器是封装的-每次调用拦截器时,它都会在当前正在执行的组件内部接收隔离的本地状态 。 在这个特定示例中,这并不重要(所有组件的窗口宽度都相同!),但这恰恰是拦截器的强大功能所在! 它们的目的不是分离状态,而是分离状态保持逻辑。 我们不想中断下游数据流!

每个拦截器可能包含一些局部状态和副作用。 您可以在多个拦截器之间传输数据,就像通常在函数之间进行传输一样。 它们可以接受参数并返回值,因为它们是JavaScript函数。

这是一个 React动画库的示例 ,我们在其中试验拦截器:
请注意,在所示的源代码中如何实现了惊人的动画:我们在同一渲染函数中的多个本机拦截器之间传递值。



codesandbox.io/s/ppxnl191zx

(此示例在本指南中进行了更详细的讨论。)

由于能够在拦截器之间传输数据,因此它们非常便于实现动画,订阅数据,管理表单以及与其他有状态抽象一起使用。 与渲染属性或高阶组件不同,拦截器不会在渲染树中创建“假层次” 。 它们更像是附加到组件的“内存单元”的二维列表。 没有额外的水平。

那班呢?

我们认为,我们自己的拦截器是整个报价中最有趣的细节。 但是为了使自己的拦截器起作用,React必须在功能级别提供声明状态和副作用的能力。 这正是允许我们执行诸如useStateuseEffect类的内置拦截器的useEffect 。 在文档中阅读有关此内容的更多信息。

事实证明,这样的内置拦截器不仅在创建自己的拦截器时很方便。 它们还足以确定整个组件,因为它们为我们提供了必要的功能-例如状态。 这就是为什么我们希望拦截器成为将来定义React组件的主要手段。
不,我们不打算逐步取消课程。 我们在Facebook上使用了成千上万个类组件,我们(就像您一样)绝对不希望重写它们。 但是,如果React社区开始使用拦截器,则保留建议的两种编写组件的方式将变得不合适。 拦截器涵盖了所有使用类的实际情况,但在提取,测试和重用代码时具有更大的灵活性。 这就是为什么我们将拦截器与我们对React未来的想法联系起来的原因。

如果拦截器是魔法怎么办?

也许拦截器规则会让您感到困惑。

尽管通常不习惯在较高级别调用拦截器,但是即使您可以,也可能不想自己确定条件中的条件 。 例如,无法在教室中确定条件限制状态,并且与React用户沟通四年来,我没有听到对此有任何抱怨。

这样的设计对于引入自己的拦截器而不引入过多的语法噪音或造成陷阱至关重要。 我们知道,出于习惯,这很困难,但我们认为,鉴于这种妥协所提供的机会,这种妥协是可以接受的。 如果您不同意,我建议您尝试一下并尝试如何使用这种方法。

我们已经使用生产挂钩一个月了,以查看新规则是否会使程序员感到困惑。 实践表明,一个人可以在几个小时内掌握拦截器。 我承认这些规则乍一看对我来说似乎是异端,但是这种感觉很快就过去了。 那是我第一次遇到React时的印象。 (您不喜欢React?我只第二次喜欢它。)

请注意:在拦截器的实现级别上也没有魔术。 根据杰米(Jamie)的说法,她得到如下信息:



gist.github.com/gaearon/62866046e396f4de9b4827eae861ff19

我们将维护一份爆炸物清单,并在每次使用拦截物时移至清单中的下一个组件。 由于拦截器的规则,它们在任何渲染引擎中的顺序都是相同的,因此,每次调用我们都可以为组件提供正确的状态。

在Rudy Yardley的这篇文章中,所有内容都在图片中得到了精美的解释!)

也许您想知道React在哪里存储拦截器的状态。 与类的状态相同。 无论您如何定义组件,React都有一个内部更新队列,其中包含每个状态的最终事实。

拦截器独立于代理和获取器,在现代JavaScript库中很常见。 因此,可以认为拦截器的魔力要比解决此类问题的其他流行方法少。 只不过在array.pusharray.pop (在这种情况下,调用顺序也很重要!)

拦截器设计不依赖于React。 实际上,在该提案发布几天后,许多人向我们展示了针对Vue,Web组件甚至是普通JavaScript函数的相同拦截器API的实验性实现。
最后,如果您热衷于函数式编程,并且当React开始依赖可变状态作为实现的一部分时,您会感到不自在。 但是,让您感到欣慰的是,拦截器处理可以以纯形式实现,从而将自身限制为代数效果(如果JavaScript支持)。 自然,在系统内部级别,React始终依赖于可变状态-这正是您要避免的状态。

无论哪种观点更接近您-务实还是教条-我希望这些选择中至少有一个对您来说合乎逻辑。 在我看来,最重要的是,拦截器简化了我们的工作,使用户的工作变得更加方便。 那就是拦截器那样贿赂我的东西。

Source: https://habr.com/ru/post/zh-CN428632/


All Articles