翻译:刘小夕原文连接:https://dmitripavlutin.com/7-...javascript
本篇文章重点阐述可测试和富有意义。因水平有限,文中部分翻译可能不够准确,若是你有更好的想法,欢迎在评论区指出。java
尽管 组合
、复用
和 纯组件
三个准则阅读量不高,不过本着善始善终的原则,固然我我的始终仍是以为此篇文章很是优质,仍是坚持翻译完了。本篇是最后 可靠React组件设计
的最后一篇,但愿对你的组件设计有所帮助。react
更多文章可戳: https://github.com/YvetteLau/...git
———————————————我是一条分割线————————————————github
若是你尚未阅读过前几个原则:服务器
通过测试的组件验证了其在给定输入的状况下,输出是否符合预期。可测试组件易于测试。网络
如何确保组件按预期工做?你能够不觉得然地说:“我经过手动验证其正确性”。架构
若是你计划手动验证每一个组件的修改,那么早晚,你会跳过这个繁琐的环节,进而致使你的组件早晚会出现缺陷。app
这就是为何自动化组件验证很重要:进行单元测试。单元测试确保每次进行修改时,组件都能正常工做。ide
单元测试不只涉及早期错误检测。 另外一个重要方面是可以验证组件架构是否合理。
我发现如下几点特别重要:
一个不可测试或难以测试的组件极可能设计得很糟糕。
组件很难测试每每是由于它有不少 props
、依赖项、须要原型和访问全局变量,而这些都是设计糟糕的标志。
当组件的架构设计很脆弱时,就会变得难以测试,而当组件难以测试的时候,你大概念会跳过编写单元测试的过程,最终的结果就是:组件未测试。
总之,须要应用程序未经测试的缘由都是由于设计不当,即便你想测试这样的应用,你也作不到。
咱们来测试一下 封装章节的两个版本的 <Controls>
组件。
import assert from 'assert'; import { shallow } from 'enzyme'; class Controls extends Component { render() { return ( <div className="controls"> <button onClick={() => this.updateNumber(+1)}> Increase </button> <button onClick={() => this.updateNumber(-1)}> Decrease </button> </div> ); } updateNumber(toAdd) { this.props.parent.setState(prevState => ({ number: prevState.number + toAdd })); } } class Temp extends Component { constructor(props) { super(props); this.state = { number: 0 }; } render() { return null; } } describe('<Controls />', function () { it('should update parent state', function () { const parent = shallow(<Temp />); const wrapper = shallow(<Controls parent={parent} />); assert(parent.state('number') === 0); wrapper.find('button').at(0).simulate('click'); assert(parent.state('number') === 1); wrapper.find('button').at(1).simulate('click'); assert(parent.state('number') === 0); }); });
咱们能够看到 <Controls>
测试起来很复杂,由于它依赖父组件的实现细节。
测试时,须要一个额外的组件 <Temp>
,它模拟父组件,验证 <Controls>
是否正确修改了父组件的状态。
当 <Controls>
独立于父组件的实现细节时,测试会变得更加容易。如今咱们来看看正确封装的版本是如何测试的:
import assert from 'assert'; import { shallow } from 'enzyme'; import { spy } from 'sinon'; function Controls({ onIncrease, onDecrease }) { return ( <div className="controls"> <button onClick={onIncrease}>Increase</button> <button onClick={onDecrease}>Decrease</button> </div> ); } describe('<Controls />', function () { it('should execute callback on buttons click', function () { const increase = sinon.spy(); const descrease = sinon.spy(); const wrapper = shallow( <Controls onIncrease={increase} onDecrease={descrease} /> ); wrapper.find('button').at(0).simulate('click'); assert(increase.calledOnce); wrapper.find('button').at(1).simulate('click'); assert(descrease.calledOnce); }); });
良好封装的组件,测试起来简单明了,相反,没有正确封装的组件难以测试。
可测试性是肯定组件结构良好程度的实用标准。
很容易一个有意义的组件做用是什么。
代码的可读性是很是重要的,你使用了多少次模糊代码?你看到了字符串,可是不知道意思是什么。
开发人员大部分时间都在阅读和理解代码,而不是实际编写代码。咱们花75%的时间理解代码,花20%的时间修改现有代码,只有5%编写新的代码。
在可读性方面花费的额外时间能够减小将来队友和本身的理解时间。当应用程序增加时,命名实践变得很重要,由于理解工做随着代码量的增长而增长。
阅读有意义的代码很容易。然而,想要编写有意义的代码,须要清晰的代码实践和不断的努力来清楚地表达本身。
组件名是由一个或多个帕斯卡单词(主要是名词)串联起来的,好比:<DatePicker>
、<GridItem>
、Application
、<Header>
。
组件越专业化,其名称可能包含的单词越多。
名为 <HeaderMenu>
的组件表示头部有一个菜单。 名称 <SidebarMenuItem>
表示位于侧栏中的菜单项。
当名称有意义地暗示意图时,组件易于理解。为了实现这一点,一般你必须使用详细的名称。那很好:更详细比不太清楚要好。
假设您导航一些项目文件并识别2个组件: <Authors>
和 <AuthorsList>
。 仅基于名称,您可否得出它们之间的区别? 极可能不能。
为了获取详细信息,你不得不打开 <Authors>
源文件并浏览代码。字后,你知道 <Authors>
从服务器获取做者列表并呈现 <AuthorsList>
组件。
更专业的名称而不是 <Authors>
不会建立这种状况。更好的名称如:<FetchAuthors>
,<AuthorsContainer>
或 <AuthorsPage>
。
一个词表明一个概念。例如,呈现项目概念的集合由列表单词表示。
每一个概念对应一个单词,而后在整个应用程序中保持关系一致。结果是可预测的单词概念映射关系。
当许多单词表示相同的概念时,可读性会受到影响。例如,你定义一个呈现订单列表组件为:<OrdersList>
,定义另外一个呈现费用列表的组件为: <ExpensesTable>
。
渲染项集合的相同概念由2个不一样的单词表示:list
和 table
。 对于相同的概念,没有理由使用不一样的词。 它增长了混乱并打破了命名的一致性。
将组件命名为 <OrdersList>
和 <ExpensesList>
(使用 list
)或 <OrdersTable>
和<ExpensesTable>
(使用 table
)。使用你以为更好的词,只要保持一致。
组件,方法和变量的有意义的名称足以使代码可读。 所以,注释大可能是多余的。
常见的滥用注释是对没法识别和模糊命名的解释。让咱们看看这样的例子:
// <Games> 渲染 games 列表 // "data" prop contains a list of game data function Games({ data }) { // display up to 10 first games const data1 = data.slice(0, 10); // Map data1 to <Game> component // "list" has an array of <Game> components const list = data1.map(function (v) { // "v" has game data return <Game key={v.id} name={v.name} />; }); return <ul>{list}</ul>; } <Games data=[{ id: 1, name: 'Mario' }, { id: 2, name: 'Doom' }] />
上例中的注释澄清了模糊的代码。 <Games>
,data
, data1
, v
,,数字 10
都是无心义的,难以理解。
若是重构组件,使用有意义的 props
名和变量名,那么注释就能够被省略:
const GAMES_LIMIT = 10; function GamesList({ items }) { const itemsSlice = items.slice(0, GAMES_LIMIT); const games = itemsSlice.map(function (gameItem) { return <Game key={gameItem.id} name={gameItem.name} />; }); return <ul>{games}</ul>; } <GamesList items=[{ id: 1, name: 'Mario' }, { id: 2, name: 'Doom' }] />
不要使用注释去解释你的代码,而是代码即注释。(小夕注:代码即注释不少人未必能作到,另外因团队成员水平不一致,你们仍是应该编写适当的注释)
我将一个组件表现力分为4个台阶。 组件在楼梯上的位置越低,意味着须要更多的努力才能理解。
你能够经过如下几种方式来了解组件的做用:
props
若是变量名和 props
提供了足够的信息足以让你理解这个组件的做用和使用方式,那就是一种超强的表达能力。 尽可能保持这种高质量水平。
有些组件具备复杂的逻辑,即便是好的命名也没法提供必要的细节。那么就须要阅读文档。
若是缺乏文档或没有文档中没有回答全部问题,则必须浏览代码。因为花费了额外的时间,这不是最佳选择,但这是能够接受的。
在浏览代码也无助于理解组件时,下一步是向组件的做者询问详细信息。这绝对是错误的命名,并应该避免进入这一步。最好让做者重构代码,或者本身重构代码。
重写是写做的本质。专业做家一遍又一遍地重写他们的句子。
要生成高质量的文本,您必须屡次重写句子。阅读书面文字,简化使人困惑的地方,使用更多的同义词,删除杂乱的单词 - 而后重复,直到你有一段愉快的文字。
有趣的是,相同的重写概念适用于设计组件。有时,在第一次尝试时几乎不可能建立正确的组件结构,由于:
组件越复杂,就越须要验证和重构。
组件是否实现了单一职责,是否封装良好,是否通过充分测试?若是您没法回答某个确定,请肯定脆弱部分(经过与上述7个原则进行比较)并重构该组件。
实际上,开发是一个永不中止的过程,能够审查之前的决策并进行改进。
组件的质量保证须要努力和按期审查。这个投资是值得的,由于正确的组件是精心设计的系统的基础。这种系统易于维护和增加,其复杂性线性增长。
所以,在任何项目阶段,开发都相对方便。
另外一方面,随着系统大小的增长,你可能忘记计划并按期校订结构,减小耦合。仅仅是是实现功能。
可是,在系统变得足够紧密耦合的不可避免的时刻,知足新要求变得呈指数级复杂化。你没法控制代码,系统的脆弱反而控制了你。错误修复会产生新的错误,代码更新须要一系列相关的修改。
悲伤的故事要怎么结束?你可能会抛弃当前系统并从头开始重写代码,或者极可能继续吃仙人掌。我吃了不少仙人掌,你可能也是,这不是最好的感受。
解决方案简单但要求苛刻:编写可靠的组件。
前文所说的7个准则从不用的角度阐述了同一个思想:
可靠的组件实现一个职责,隐藏其内部结构并提供有效的
props
来控制其行为。
单一职责和封装是 solid
设计的基础。(maybe你须要了解一下 solid 原则是什么。)
单一职责建议建立一个只实现一个职责的组件,并有一个改变的理由。
良好封装的组件隐藏其内部结构和实现细节,并定义 props
来控制行为和输出。
组合结构大的组件。只需将它们分红较小的块,而后使用组合进行整合,使复杂组件变得简单。
可复用的组件是精心设计的系统的结果。尽量重复使用代码以免重复。
网络请求或全局变量等反作用使组件依赖于环境。经过为相同的 prop
值返回相同的输出来使它们变得纯净。
有意义的组件命名和表达性代码是可读性的关键。你的代码必须易于理解和阅读。
测试不只是一种自动检测错误的方式。若是你发现某个组件难以测试,则极可能是设计不正确。
成功的应用站在可靠的组件的肩膀上,所以编写可靠、可扩展和可维护的组件很是中重要。
在编写React组件时,你认为哪些原则有用?
最后谢谢各位小伙伴愿意花费宝贵的时间阅读本文,若是本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,您的确定是我前进的最大动力。https://github.com/YvetteLau/...
推荐关注本人公众号