用上帝视角来看待组件的设计模式

此文已由作者黄锴授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


组件设计,从简单来看,就是如何提高编码效率,提高代码的复用率的方法,从高级来看,这是一门程序设计的艺术

最近看了redux作者——Dan Abramov写的《Presentational and Container Components》 感觉受益匪浅,发现有很多人都在讨论组件模式,作为一个每次写需求就抓耳挠腮的思考如何组织代码结构的人来说,如何更好的在工作中使用设计和使用组件,这确实是一个值得讨论的问题。

注:本文图片部分参考于Michael Chan 做的有关React component patterns的演讲: React component patterns, 有兴趣的童鞋可以去看看。

前言

组合模式

首先,我们先从宏观的角度来聊聊组件。

现在很多人现在都在谈设计模式,设计模式。什么是设计模式?设计模式主要是指GoF(四人组)《设计模式》一书中提出了23种设计模式,这23种设计模式被广泛应用在软件工程中,代表着最佳的实践方案。而在这些模式中,专门有一个设计模式叫:组合模式(Composite pattern),根据wikipedia的解释:

组合模式的目的是将对象组合成树型结构来表示部分-整体(part-whole)的层次结构。

组合模式使得用户对单个对象和组合对象的使用具有一致性。

img

它有以下优点

  • 可扩展性强:由于组件间是充分解耦的,你可以轻松的更新,替换组件

  • 编码高效:由于把功能拆解开,你可以专注于每个子模块功能的实现。

javascript中的组合模式——组件

如果你在现在的工作中使用了框架,你会发现,这些框架基本都使用了组合模式这种设计模式。例如Vue和React的Components,它允许你编写小的组件,然后通过他们的组合构建出你的实际应用。

æ ‘

在学校,总被老师说,要有大局观,大局观。使用组合模式,就可以很好的锻炼你的大局观。 这里我觉得云谦前辈 说的很贴切:

要记得,接到需求的第一步永远不是写代码,而是想清楚你要做什么,以上帝模式做整体设计。

开启上帝视角

接下来,就让我们在我们的编程世界扮演一个上帝(准确来说是女挖),看看我们这个充满组件世界到底发生了什么。

组件的诞生

在框架类的语言中,组件就是我们操作的基本单位,React对组件的操作提供了很多实用的API,一个组件就是通过使用这些API,产生了强大的生命力,你可以把它看作一个功能完善的细胞或者个体,我们所有的APP都是由这一个个细胞(个体)构成的。

img

然后,你在日常使用中会发现,有些组件,它总是使用其中的一部分API,有些组件经常使用另一部分API。说明这些组件开始有了自己的思考,产生了差异化(你可以把它看作基因突变)。当这些差异化越来越多,我们发现,这些组件因为各自的能力偏好,逐减组成了不同的阵营:

image-20181019135735406

对于组件的阵营分类,有很多的说法:

  • 胖和瘦( Fat and Skinny,)组件

  • 状态和无状态/纯(Stateful and Stateless/pure)组件

  • 聪明和愚笨(Smart and Dumb)组件

  • 容器和展示( Container and Presentational )组件……

他们本质上都差不多,这里我们选用Redux作者Dan Abramov对组件的称法,叫他们容器展示组件,这里使用Container和Components

组件分好了阵营,就要开始制定标准,正所谓无规矩不成方圆,一个良好的组织对确定他们统一的旗号,核心价值观,才能方便后续找到更多的同类人,发展壮大。

组件的阵营

容器组件(Container)阵营

他们称自己是统治者,这里汇聚的都是精英份子,它们习惯管理和组织。作为高高在上的管理者,能让下属去做的事情自己一定不会动手。

因此他们制定了一套符合他们价值观的标准:

  • 代表结构:我只关心事情如何运作的,而不关心它是 如何表现的

  • 无样式:我这里除了一些包装div,基本没有其他标签,并且从不具有任何样式

  • 有状态:我掌握着核心数据,剩下的事情你们去做

  • 通常由其他组件生成:我们通常代表更高等的智慧(一般使用更高阶的组件生成)

  • 代表人物:各种Page页面,路由页面

展示组件(Components)阵营

他们通常由蓝白领组成,代表这着广大的工人阶层,什么脏活累活都是他们做,他们通常没有太多想法,只是想简单的做好自己的工作,少背锅就好。

在这里,每个组件关心的事情很少,他们的志向只是做好本质工作,因此他们制定了一套符合他们工作习惯的标准:

  • 代表渲染:关注事物的外观

  • 有样式:基本上dom的渲染和样式这些脏活累活都在这里干。

  • 强调独立(很重要): 我们彼此分工明确,不依赖于应用程序的其余部分(例如Flux操作或Store)。

  • 不关心数据:我们不关心数据怎么产生的,不要在我们这里指定数据的加载方式或变更方式。

  • 接受传回指令:仅通过props接收数据和回调。

  • 弱状态:我们基本不需要有自己的状态(当我们这样做时,它是UI状态而不是数据)。

  • 代表人物:Search,SiderBar,UserList,Pagintation

这两种阵营基本是完全独立的,由于这种良好的划分,他们在社会中能很好的相处(蓝色是展示组件,灰色是容器组件。

img

实际运行

好了,看他们风风火火的把阵营分了,得给他们找点事干,看这个社会分工的实际运行效果怎么样。

这里顺便提一句React的组件定义,通常有两种方式类定义无状态函数定义

无状态函数定义:

这样的好处是它本身是独立的,没有状态的,它只会根据传入的props(可以考虑成指令)来修改自己的显示,这样的组件就具备很好的通用性,而且修改起来也方便,不用担心会影响到别的组件。

var User = ({name}) => (  <span>
    {name}  </span>);

类定义:

如果有些内置的状态需要维护,或者需要使用生命周期,可以使用类定义的方式。

class User extends Component {
  constructor(props) {
    super(props);    this.state = {
      name: props.name,
      editable: false,
    };
  } 
  render(){     return ...
  }
}

容器组件——定义结构,下发指令

容器组件可以通过类定义,因为可能会使用到生命周期钩子(使用dva也可能无需生命周期钩子)。

如果使用了redux类的状态管理机制,一般就会由connect生成(Relay的createContainer(),或Flux Utils的Container.create())。

我们现在要做一个用户页面,我们把这个任务布置下去,有一个容器阵营拿到了这个页面制作指标,然后这些精英开始做组织架构工作,他们觉得完成这个工作需要造三个组件:UserSearchUserListUserModal。然后每个组件需要的处理的指令(数据)也写好了,放在userXXXProps里,好了,他们的任务完成,开始去找展示容器阵营的人去实现这些功能。

function Users({ users }) {  // 从Redux里获取数据
  const {
    loading,
    list,
    total,
    current,
  } = users;  // 下发任务
  const userSearchProps = {};  const userListProps = {};  const userModalProps = {};  // 整体结构设计
  return (    <div>
      <UserSearch {...userSearchProps} />
      <UserList {...userListProps} />
      <UserModal {...userModalProps} />
    </div>
  );
}

// 指定订阅数据,这里关联了 users
function mapStateToProps({ users }) {
  return { users };
}

// 建立数据关联关系
export default connect(mapStateToProps)(Users);

展示容器——接收指令,开工

一般来说,展示容器推荐使用 无状态函数 的方式生成

一个良好的展示容器应该是彼此独立的,这也是一个良好的分工社会应该具备的,大家各司其职,因此这里通常使用无状态函数生成一个组件。在这里,通常是根据props传入的值去处理相应的事情(老板要你干嘛就干嘛)。

// 从props里获取指令(数据)const UserList = ({
  total,
  current,
  loading,
  dataSource,
}) => {const columns = [……];// 开始工作,构建组件的渲染return (  <div>
    <Table
      columns={columns}
      dataSource={dataSource}
      loading={loading}
    />
  </div>);
}

这样看下来,这种社会分工好像运行的挺顺畅的。那这就是这个社会的全貌吗?当然不是,实际的生活中,组件的区分可以更加精确更加精细。

例如有一些组件设计模式里把组件模式细分为:

  • Proxy component

  • Presentational component

  • Layout component

  • Container component

  • Higher-order component (HOC's)

  • Render callback

但是,我个人认为以上两种划分已经足够应付生活中的绝大部分情形,剩下的,可以通过两者的组合实现mixed Component

总结

这样看下来,这种社会分工好像运行的挺顺畅的,实际上,这种分类方式也是 很多开发者经验的总结。

根据“约定优于配置(convention over configuration)”的思想,提前做一定的规范肯定是没错的,但很多人将这种分工看做了他们设计组件的教条,这样就不好了,因为凡事总有例外,也许在某些复杂的业务场景中,Container 和 Components需要混用。你需要灵活的去使用,这也是为什么Dan修改了自己文章:

I amended the article because people were taking the separation as a dogma. 90% of React users don’t ever plan to have something like a living styleguide. There is no need to make life harder for them. Even those that do, don’t need to make every component available in such tool.

适合自己的才是最好的。

何苦让生活更艰难?

最后,再说说比较好的应用开发步骤是什么?我觉得可能是:

  1. 彻底弄清楚你要做什么

  2. 功能的划分

  3. 根据功能组织目录结构

  4. 设计你的数据模型(modal)

  5. 设计你的容器,确定整体框架结构

  6. 开始依次填充展示容器

  7. 连接组件和数据模型

  8. 测试

参考

Composite pattern

React component patterns

React Patterns

Presentational and Container Components

Container Components

React.js Conf 2015 - Making your app fast with high-performance components

Components and Props

Guide to Using the Composite Pattern with JavaScript

dva快速上手


免费体验云安全(易盾)内容安全、验证码等服务

更多网易技术、产品、运营经验分享请点击



相关文章:
【推荐】 客户端SDK测试思路
【推荐】 Docker 的优势
【推荐】 最小化局部边际的合并聚类算法(中篇)

posted @ 2018-10-31 11:21  网易数帆  阅读(726)  评论(0编辑  收藏  举报