最终的React组件

我喜欢React生态系统的原因在于,IDEA支持许多决策。 许多作者为支持现有秩序写了各种文章,并解释了为什么一切都是“正确的”,所以每个人都知道该党的前进方向正确。


一段时间后,IDEA会发生一些变化,并且一切从头开始。


这个故事的开始是将组件分为容器和非容器(通常称为Dumb Components,对不起我的法语)。



问题


问题很简单-单元测试。 近来,已经有一些集成测试的趋势-众所周知, “编写测试。不是很多。主要是集成”。 。 这不是一个坏主意,如果时间很短(并且不需要特别测试),这就是您需要做的。 我们称其为烟雾测试-验证似乎没有爆炸。


如果有很多时间并且需要测试,则最好不要采用这种方式,因为编写好的集成测试非常非常长。 只是因为它们会越来越大,并且要测试右侧的第三个按钮,您首先需要单击菜单中的3个按钮,并且不要忘记登录。 总的来说-这是银盘上的组合爆炸


这里的解决方案是一个简单的(按定义)-单元测试。 以应用程序某些部分的现成状态开始测试的能力。 更准确地说,是将测试区域从应用程序或大块减小(缩小)到较小的单元,无论它是什么。 不必使用酶-如果灵魂要求,您可以运行浏览器测试。 这里最重要的是能够孤立地测试某些东西。 而且没有太多麻烦。


隔离是单元测试中的关键点之一,这就是为什么单元测试不喜欢它的原因。 他们出于各种原因不喜欢它:


  • 例如,您的“单元”已从应用程序中撕下,即使其自身的测试是绿色的,它也无法正常工作。
  • 或例如因为隔离是在真空中没有人见过的球形马。 如何实现,以及如何衡量?

我个人认为这里没有问题。 当然,在第一段中您可以推荐集成测试,它是为此而发明的-检查预测试的组件如何正确组装。 您信任npm软件包,这些软件包当然只能对其自身进行测试,而不能对它们本身作为应用程序的一部分进行测试。 您的“组件”与“不是您的”软件包有何不同?


在第二段中,一切都变得有些复杂。 本文正是关于这一点(以及之前的所有内容,即简介),以及如何使“单元” 单元可测试


分而治之


将React组件分为“容器”和“演示文稿”的想法并不是新鲜的,描述得很好的,并且已经变得有些过时了。 如果我们以Dan Abramov的文章作为基础(99%的开发人员这样做),那么Presentation Component:


  • 关注事物的外观
  • 可能同时包含表示性组件和容器组件** ,并且通常具有一些DOM标记和自己的样式)
  • 支持插槽(通常允许通过this.props.children进行封闭)
  • 与应用程序无关(不依赖于应用程序的其余部分,例如Flux操作或存储)
  • 不要依赖数据(不要指定如何加载或更改数据)
  • 该接口基于道具(仅通过道具接收数据和回调)
  • 通常是无状态的(很少有自己的状态(当有状态时,它是UI状态而不是数据状态))
  • 通常是SFC(除非需要状态,生命周期挂钩或性能优化,否则都写为功能组件)

好的,原则上,容器是所有逻辑,所有对数据的访问以及整个应用程序。


在理想的世界中,容器是树干,展示组件是叶子。

Dan的定义有两个关键点: “独立应用程序” ,这几乎是学术上对“单元”的定义,以及*“可能同时包含其他表示组件和容器** ” *,这些星星特别有趣。


(免费翻译)**在我文章的早期版本中,我(丹)说,表示组件应仅包含其他表示组件。 我不这么认为了。 组件的类型是详细信息,并且可能会随时间变化。 通常,不要共享它,一切都会好的。

让我们记住之后发生的情况:


  • 在故事书中,所有内容都下降了,因为某种容器在左侧的第三个按钮中爬入了没有该容器的一侧。 对graphql,react-router和其他react-intl的特殊问候。
  • 失去了在测试中使用mount的能力,因为它渲染了从A到Z的所有内容,并且又在渲染树的某个深处某处某人执行了某项操作,并且测试失败了。
  • 失去了控制应用程序状态的能力,因为失去了使选择器/分解器(尤其是proxyquire)变湿的能力,并且整个门需要变湿。 这对于单元测试很酷。

如果您认为问题有点牵强,请尝试将这些容器(将用于非容器中,在其他部门中使用)进行小组合作,结果,您和他们查看了测试,而您不明白为什么昨天所有它起作用了,现在又起作用了。

结果,您必须使用浅表层, 通过设计消除了所有有害(和意想不到的)副作用。 这是“为什么我总是使用浅表”一文中的一个简单示例


想象一下,工具提示呈现“?”,单击时将显示类型本身。


 import Tooltip from 'react-cool-tooltip'; const MyComponent = () => { <Tooltip> hint: {veryImportantTextYouHaveToTest} </Tooltip> } 

怎么测试呢? 挂载+单击+检查可见的内容。 这是一个集成测试,而不是一个单元,问题是如何为您单击“外部”组件。 浅层没有问题,因为没有大脑 ,也没有“外来成分”本身。 但是这里有头脑,因为Tooltip是容器,而MyComponent 实际上是表示形式。


 jest.mock('react-cool-tooltip', {default: ({children}) => childlren}); 

但是,如果您对cool-tooltip做出反应,那么测试就不会有问题。 “组成部分”急剧变笨,变短,变得更加有限


最终组成


  • 具有众所周知大小的组件,可以包括其他先前已知的最终组件,或者根本不包含它们。
  • 不包含其他容器,因为它们包含不受控制的状态和“增加”的大小,即 使当前分量无限
  • 否则,它是常规的演示组件。 实际上,与Dan的文章的第一个版本完全相同。

最后的组件只是从大型机构取出的齿轮。


整个问题是如何将其取出。


解决方案1-DI


我最喜欢的是依赖注入。 丹也爱他 。 通常,这不是DI,而是“插槽”。 简而言之-无需在Presentation内部使用容器-需要将它们注入那里。 在测试中,有可能注入其他东西。


 //    mount     const PageChrome = ({children, aside}) => ( <section> <aside>{aside}</aside> {children} </section> ); //     shallow,       //     mount ? , ,   wiring? const PageChromeContainer = () => ( <PageChrome aside={<ASideContainer />}> <Page /> </PageChrome> ); 

“容器是主干,表示组件是叶子”时,情况就是这样。


解决方案2-边界


DI通常可以很酷。 大概现在%username%认为应该如何在当前代码库中应用它,而该解决方案还没有发明出来……


在这种情况下, Borders会救您。


 const Boundary = ({children}) => ( process.env.NODE_ENV === 'test' ? null : children // //  jest.mock ); const PageChrome = () => ( <section> <aside><Boundary><ASideContainer /></Boundary></aside> <Boundary><Page /></Boundary> </section> ); 

在这里,所有的“转换点”都代替了“槽”,而只是变成了“边界”,它将在测试期间渲染任何东西 。 足够声明 ,并且确切地说是您需要“卸下齿轮”。


解决方案3-层


边框可能会有些粗糙,通过添加一些有关Layer的知识,使边框变得更聪明可能会更容易。


 const checkTier = tier => tier === currentTier; const withTier = tier => WrapperComponent => (props) => ( (process.env.NODE_ENV !== 'test' || checkTier(tier)) && <WrapperComponent{...props} /> ); const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); const ASideContainer = withTier('UI')(...) const Page = withTier('Page')(...) const PageChromeContainer = withTier('UI')(PageChrome); 

在“层/层”的名称下,可以有不同的内容-功能,存储区,模块或只是该层/层。 关键并不重要,主要的是您可以拉动齿轮,也许不是一个齿轮,而是最终数字, 以某种方式在所需和不需要之间划清界限(对于不同的测试,这是不同的边界)。


没有什么能以不同的方式标记这些边界。


解决方案4-单独的问题


如果解决方案(按照定义)在于实体的分离-如果我们采用并分离它们,将会发生什么?


我们非常讨厌的“容器”通常称为容器 。 如果不是这样,那么什么也无法阻止现在开始以某种方式更响亮地命名组件。 或者它们的名称中具有某种模式-Connect(WrappedComonent)或GraphQL / Query。


如果在运行时正确地基于名称在实体之间划界怎么办?


 const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); // remove all components matching react-redux pattern reactRemock.mock(/Connect\(\w\)/) // all any other container reactRemock.mock(/Container/) 

在测试中加上一行,然后react-remock将删除可能干扰测试的所有容器。


原则上,该方法可用于测试容器本身-您只需要删除除第一个容器以外的所有内容。


 import {createElement, remock} from 'react-remock'; //  "" const ContainerCondition = React.createContext(true); reactRemock.mock(/Connect\(\w\)/, (type, props, children) => ( <ContainerCondition.Consumer> { opened => ( opened ? ( // ""     <ContainerCondition.Provider value={false}> {createElement(type, props, ...children)} <ContainerCondition.Provider> ) // "" : null )} </ContainerCondition.Consumer> ) 

再次-删除了几条线和齿轮。


合计


在过去的一年中,测试React组件变得更加复杂,尤其是对于挂载而言-您需要覆盖所有10个提供者,上下文,并且越来越难以以正确的样式测试正确的组件-要拉的绳子太多了。
有人吐口水,进入了浅薄的世界。 有人在单元测试中挥舞着手,然后将所有东西转移给了赛普拉斯(像走路一样走路!)。


有人轻弹了一下反应,说这是代数效应 ,您可以做任何您想做的事情。 以上所有示例实质上都是对这些代数效果和模拟的使用。 对我和DI而言,这些都是魔基。


PS:这篇文章是对React / RFC中关于React团队破坏了一切以及那里所有聚合物的事实的回应。
PPS:这篇文章实际上是另一篇文章的非常免费的翻译
PPPS:通常,要真正隔离,请查看rewiremock

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


All Articles