原文摘自:https://dmitripavlutin.com/7-architectural-attributes-of-a-reliable-react-component/
I. 组合
一个组合式组件是由更小的特定组件组合而成的
组合(composition)是一种通过将各组件联合在一起以创建更大组件的方式。组合是 React 的核心。
幸好,组合易于理解。把一组小的片段,联合起来,创建一个更大个儿的。
让我们来看看一个更通用的前端应用组合模式。应用由头部的 header、底部的 footer、左侧的 sidebar,还有中部的有效内容联合而成:
代码语言:javascript复制<div id="root"></div>
代码语言:javascript复制function Application({ children }) {
return (
<div className="application box">
<Label>Application</Label>
{children}
</div>
);
}
function Header() {
return (
<div className="header box">
<Label>Header</Label>
</div>
)
}
function Footer() {
return (
<div className="header box">
<Label>Footer</Label>
</div>
)
}
function Sidebar({ children }) {
return (
<div className="sidebar box">
<Label>Sidebar</Label>
{children}
</div>
);
}
function Content({ children }) {
return (
<div className="content box">
<Label>Content</Label>
{children}
</div>
)
}
function Menu() {
return (
<div className="menu box">
<Label>Menu</Label>
<div className="description">
<div className="text shorter" />
<div className="text" />
<div className="text shorter" />
<div className="text shorter" />
</div>
</div>
);
}
function Article() {
return (
<div className="article box">
<Label>Article</Label>
<div className="description">
<div className="text shorter" /> <div className="text longer" />
<div className="text shorter" /> <div className="text" />
<div className="text shorter" /> <div className="text" />
<div className="text" /> <div className="text shorter" />
<div className="text shorter" /> <div className="text" />
<div className="text" /> <div className="text shorter" />
<div className="text shorter" /> <div className="text longer" />
<div className="text shorter" /> <div className="longer" />
<div className="text shorter" /> <div className="text" />
<div className="text" /> <div className="text shorter" />
<div className="text shorter" /> <div className="text" />
</div>
</div>
);
}
function Label({ children }) {
return <div className="label"><{children}></div>
}
const app = (
<Application>
<Header />
<Sidebar>
<Menu />
</Sidebar>
<Content>
<Article />
</Content>
<Footer />
</Application>
);
ReactDOM.render(app, document.getElementById('root'));
骨架部分展示了好的组合如何构建应用。这样组织代码即富于表现力又便于理解。
React 组件的组合是自然而然的。这个库使用了一个描述式的范式,从而不会抑制组合式的表现力。
<Application>
由 <Header>
、<Sidebar>
、<Content>
和 <Footer>
组合而成。 <Sidebar>
只有一个子组件 <Menu>
,而 <Content>
同样也只有 <Article>
。
那么组合式和单一职责以及封装有何联系呢?让我们来看看:
单一职责原则(SRP) 描述了如何根据需求划分组件,封装(encapsulation) 则描述了如何组织这些组件,而 组合(composition) 描述的是如何把整个系统粘合在一起。
单一职责
组合的一个重要方面在于从特定的小组件组成复杂组件的能力。这种逐个击破(divide and conquer)的方式帮助了被组合而成的复杂组件也能符合 SRP 原则。
回顾之前的代码片段,<Application>
的职责就是渲染 header、footer、sidebar 和主体区域。要明白的是这个职责被分割为了四个子职责,每个子职责由对应的子组件实现。
也就是说,组合的好处在于,通过允许子组件分别实现单一职责的方式,让 <Application>
这样的组件也符合了单一职责原则。
可重用性
使用组合的组件也有可重用性的优点,可以重用通用的逻辑。
举例来说,<Composed1>
和 <Composed2>
共享了通用的逻辑:
const instance1 = (
<Composed1>
/* Composed1 组件的特定代码... */
/* 通用代码... */
</Composed1>
);
const instance2 = (
<Composed2>
/* 通用代码... */
/* Composed2 组件的特定代码... */
</Composed2>
);
既然 ctrl C/ctrl V 人人喊打,那如何让组件重用通用逻辑呢?
首先,将通用代码封装到新组件 <Common>
中。其次,<Composed1>
和 <Composed2>
应使用组合方式来包含 <Common>
;代码实现如下:
const instance1 = (
<Composed1>
<Piece1 />
<Common />
</Composed1>
);
const instance2 = (
<Composed2>
<Common />
<Piece2 />
</Composed2>
);
可重用的组件符合 Don't repeat yourself (DRY) 原则。这种有益的实践节省了时间和精力。
灵活性
在 React 中一个组合式的组件可以控制其子组件,这通常是通过 children 属性实现的。这带来了另一个好处 -- 灵活性。
比如,有一个组件根据用户设备显示提示信息。使用组合可以灵活地实现这个需求:
代码语言:javascript复制function ByDevice({ children: { mobile, other } }) {
return Utils.isMobile() ? mobile : other;
}
<ByDevice>{{
mobile: <div>检测到移动设备!</div>,
other: <div>非移动设备</div>
}}</ByDevice>
有效性
用户界面就是组合式的分层结构。这种组件的组合是一种构建 UI 的有效的方式。
II. 可重用性
一个可重用的组件,可以做到一次编写多次使用
想象一下,如果有那么一个总是重复发明轮子的软件开发世界。
人们编写代码时,不能使用任何已有库或工具。甚至在同一个应用中你都不能使用写过的任何代码。
在这种环境中,是否有可能在合理的时间内编写出一个应用呢?绝无可能。
有请可重用性出场 -- 让事情运转起来,而非重新发明如何让其运作。
应用内的重用
根据 Don't repeat yourself (DRY) 原则,知识的每一个片段在系统中都应该是单独、不含糊而权威的。DRY 原则就是为了避免重复。
代码重复提高了复杂性和维护成本,却没有带来显著的价值。一处逻辑的更新会迫使你修改应用中其所有重复的副本。
重复问题要靠可重用的组件来解决。编写一次使用多次,是一种有效而省时的策略。
但享受可重用性也非毫无成本的。必须符合单一职责原则和合理的封装,才能说组件是可重用的。
符合单一职责原则是必须的:
重用一个组件实际上就意味着重用其职责
所以,只有唯一职责的组件最容易被重用。
当组件不恰当的具有了多个职责时,其可重用性就收到了很大的限制。只想重用某一个职责时,又会面对由其余不需要的实现造成的职责。
想要一个香蕉,香蕉倒是拿到了 -- 整个丛林都跟了过来。
合理的封装的组件不会被依赖卡住。隐藏的内部结构以及目的明确的 props ,使得组件可以良好的适用于重用其的多个场合。
重用第三方库
今天天气不错还风和日丽哒,你打开电脑,并在启动编辑器的几分钟里,看了看今天的需求 -- 往应用里添加一个新特性,准备开干了...
然而此时你要做的事情在很大概率上实际上是已经被解决过的了。由于 React 的流行度以及其非常棒的开源社区,所以先搜索一下是否有已存在的解决方案是明智的。
检出 brillout/awesome-react-components
仓库,会发现一个编辑过的可重用组件菜单。
良好的库无疑会产生结构性的影响并推广最佳实践。以我的经验而言,最有影响的当属 react-router 和 redux。
react-router
使用了声明式的路由来规划一个单页应用的结构。使用 <Route>
可以将一个 URL 和一个组件联系起来。而后当用户访问匹配的 URL 时,路由将渲染相应的组件。
redux
和 react-redux
引入了单向且可预期的应用状态管理。借助它们,可以将异步的和非纯的代码(例如 HTTP 请求)从组件中提取出来,从而符合单一职责原则并创建出 纯(pure)组件 或 几乎纯(almost-pure)的组件。
要确定第三方库是否堪用,这里是一份检查清单:
- 文档:检查库是否具备有意义的 README.md 文件以及详细的文档
- 测试过的:可信赖库的一个显著特征就是有高的代码覆盖率
- 维护:看看库作者创建新特性、修改bug及日常维护的频率如何