[译] 更可靠的 React 组件:组合及可重用性

2020-06-15 22:21:51 浏览数 (1)

原文摘自: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">&lt;{children}&gt;</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> 共享了通用的逻辑:

代码语言:javascript复制
const instance1 = (  
 <Composed1>
   /* Composed1 组件的特定代码... */
   /* 通用代码... */
 </Composed1>
);
const instance2 = (  
 <Composed2>
   /* 通用代码... */
   /* Composed2 组件的特定代码... */
 </Composed2>
);

既然 ctrl C/ctrl V 人人喊打,那如何让组件重用通用逻辑呢?

首先,将通用代码封装到新组件 <Common> 中。其次,<Composed1><Composed2> 应使用组合方式来包含 <Common>;代码实现如下:

代码语言:javascript复制
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 时,路由将渲染相应的组件。

reduxreact-redux 引入了单向且可预期的应用状态管理。借助它们,可以将异步的和非纯的代码(例如 HTTP 请求)从组件中提取出来,从而符合单一职责原则并创建出 纯(pure)组件几乎纯(almost-pure)的组件

要确定第三方库是否堪用,这里是一份检查清单:

  • 文档:检查库是否具备有意义的 README.md 文件以及详细的文档
  • 测试过的:可信赖库的一个显著特征就是有高的代码覆盖率
  • 维护:看看库作者创建新特性、修改bug及日常维护的频率如何

0 人点赞