致敬 React: 为 Vue 引入容器组件和展现组件

若是你使用过 Redux 开发 React,你必定听过 容器组件(Smart/Container Components) 或 展现组件(Dumb/Presentational Components),这样划分有什么样的好处,咱们可否能借鉴这种划分方式来编写 Vue 代码呢?这篇文章会演示为何咱们应该采起这种模式,以及如何在 Vue 中编写这两种组件。前端

为何要使用容器组件?

假如咱们要写一个组件来展现评论,在没听过容器组件以前,咱们的代码通常都是这样写的:vue

components/CommentList.vue

为 Vue 引入容器组件和展现组件

store/index.js

为 Vue 引入容器组件和展现组件

这样写看起来理所固然,有没有什么问题,或者能够优化的地方呢?react

有一个很显而易见的问题,因为 CommentList.vue 与 项目的 Vuex store 产生了耦合,致使脱离当前的项目很难复用。git

有没有更好的组件的组织方式,能够解决这个问题呢?是时候了解下 React 社区的容器组件的概念了。github

什么是容器组件

在 React.js Conf 2015 ,有一个 Making your app fast with high-performance components 的主题介绍了容器组件。vuex

什么是容器组件

容器组件专门负责和 store 通讯,把数据经过 props 传递给普通的展现组件,展现组件若是想发起数据的更新,也是经过容器组件经过 props 传递的回调函数来告诉 store。redux

因为展现组件再也不直接和 store 耦合,而是经过 props 接口来定义本身所需的数据和方法,使得展现组件的可复用性会更高。设计模式

容器组件 和 展现组件 的区别

展现组件 容器组件
做用 描述如何展示(骨架、样式) 描述如何运行(数据获取、状态更新)
直接使用 store
数据来源 props 监听 store state
数据修改 从 props 调用回调函数 向 store 派发 actions

来自 Redux 文档 https://user-gold-cdn.xitu.io/2018/5/2/1631f590aa5512b7数组

用 容器组件/展现组件 模式改造上面的例子

针对最初的例子,如何快速按照这种模式来划分组件呢?咱们主要针对 CommentList.vue 进行拆分,首先是基本的概要设计:promise

概要设计

展现组件

  • components/CommentListNew.vue 这是一个新的评论展现组件,用于展现评论
    • comments: Array prop 接收以 { id, author, body } 形式显示的 comment 项数组。
    • fetch() 接收更新评论数据的方法

展现组件只定义外观并不关心数据来源和如何改变。传入什么就渲染什么。

comments、fetch 等这些 props 并不关心背后是不是由 Vuex 提供的,你可使用 Vuex,或者其余状态管理库,甚至是一个 EventBus,均可以复用这些展现组件。

同时,能够利用 props 的类型和验证来约束传入的内容,好比验证传入的 comments 是不是一个含有指定字段的对象,这在以前混合组件的状况是下是没有的,提升了代码的健壮性。

容器组件

  • containers/CommentListContainer.vue 将 CommentListNew 组件链接到 store

容器组件能够将 store 对应的 state 或者 action 等封装传入展现组件。

编码实现

Talk is cheap, show me the code!

components/CommentListNew.vue

这个文件再也不依赖 store,改成从 props 传递。

值得注意的是 comments 和 fetch 分别定义了 type 、default 和 validator,用以定义和验证 props。

为 Vue 引入容器组件和展现组件

containers/CommentListContainer.vue

容器组件的职责

  • 经过 computed 来获取到状态更新,传递给展现组件
  • 经过 methods 定义回调函数,回调函数内部调用 store 的 dispatch 方法,传递给展现组件

为 Vue 引入容器组件和展现组件

使用 @xunlei/vuex-connector 实现容器组件

上面演示的容器组件的代码很是简单,实际上若是直接投入生产环境,会产生一些问题。

手动实现容器组件存在的不足

代码比较繁琐

在上面的例子中,每次传递一个 state 都要定义一个 computed,每传递一个 mutation 或者 action 都须要定义一个方法,并且还要注意这个方法的参数要透传过去,同时还要处理返回值,好比异步的 action 须要返回 promise 的时候,定义的这个 method 也得把 action 的返回值返回出去。

没法透传其余 props 给展现组件

好比展现组件新增了一个 prop 叫作 type,能够传递一个评论的类型,用来区分是热门仍是最新,若是用上面的容器实现方式,首先须要在容器组件这层新增一个 prop 叫作 type 接受外部传来的参数,而后在展现组件内部一样定义一个 叫作 type 的 prop,而后才能传递下去。

须要透传的 props 必须定义两遍,增长了维护的成本。

为 Vue 引入容器组件和展现组件

为 Vue 引入容器组件和展现组件

容器组件没法统一进行优化

每个手动实现的容器组件实质上代码逻辑很是近似,可是没有通过同一层封装,若是目前实现的容器组件存在一些性能优化的地方,须要每一个容器组件都进行统一的修改。

没法控制展现组件不去获取 store

由于容器组件是经过 this.$store 获取 store 的,展现组件内部实质上也能够直接跟 store 通讯,若是没有约束,很难统一要求展现组件不得直接和 store 通讯。

使用 @xunlei/vuex-connector

@xunlei/vuex-connector 借鉴了 react redux 的 connect 方法,在 vuex 基础上进行的开发。

有如下几个特色:

代码很是简洁

下面是上面例子中手动实现的容器组件的改造版本:

comonents/ConnectCommentListContainer.vue

为 Vue 引入容器组件和展现组件

经过 connector 的 connnect 方法,传入要映射的配置,支持 mapStateToProps, mapGettersToProps, mapDispatchToProps, mapCommitToProps 这四种,每一种都是只要配置一个简单的 map 函数,或者字符串便可。

而后在返回的函数中传入要链接的展现组件便可,是否是很是的简洁。

容器组件自己也能够复用

因为借鉴了 redux 优雅的函数式风格, connector 的 connnect 方法 返回的函数其实是一个高阶组件,也就是一个能够建立组件的函数。这样带来了额外的好处,不一样的展现组件也能够复用同一个容器组件。

举个例子:

若是你写了多个版本的评论展现组件,接受的数据和更新数据的方式都是同样的,那么你就没有必要为每一个版本的评论组件都搞一个容器组件了,只要复用同一个高阶组件函数便可。

问题来了,connector 是什么?

connector 其实是一个能获取到 store 实例的链接器,能够在初始化 vuex store 的时候进行初始化。

为 Vue 引入容器组件和展现组件

一个 Vue 程序实际上只须要初始化一次便可。

支持透传其余 props 给展现组件

VuexConnector 实现的时候采用了函数式组件( functional: true )

函数式组件是无状态 (没有响应式数据),无实例 (没有 this 上下文)。

在做为包装组件时函数式组件很是有用,好比,当你须要作这些时:

  • 程序化地在多个组件中选择一个
  • 在将 children, props, data 传递给子组件以前操做它们。

另外,函数式组件只是一个函数,因此渲染开销也低不少。然而,对持久化实例的缺少也意味着函数式组件不会出如今 Vue devtools 的组件树里。

所以须要透传的 props 能够直接透传,须要经过 map 方式从 store 里进行获取的 props 直接会根据配置生成。

统一封装方便后续统一优化

VuexConnector.connect 方法将原本须要重复作的事情进行了抽象,也带来了后期进行统一优化和升级的便利。

能够控制展现组件没法直接与 store 通讯

VuexConnector 不依赖 this.$store,而是依赖初始化传入的 store 实例,容器组件能够用 connect 将展现组件与 store 进行链接。

因为不依赖 this.$store,咱们在程序入口 new Vue 的时候,就不须要传入 store 实例了。

好比,以前咱们是经过下面的方式进行初始化:

为 Vue 引入容器组件和展现组件

使用了 VuexConnector 以后,在最初 new Vue 的时候就不须要也最好不要传递 store 了,这样就避免了 this.$store 泛滥致使代码耦合的问题。

引入容器组件/展现组件模式带来的好处

可复用性

容器组件/展现组件的划分,采用了单一职责原则的设计模式,容器组件专门负责和 store 通讯,展现组件只负责展现,解除了组件的耦合,能够带来更好的可复用性。

健壮性

因为展现组件和容器组件是经过 props 这种接口来链接,能够利用 props 的校验来加强代码的可靠性,混合的组件就没有这种好处。

另外对 props 的校验能够采起一下几种方式:

Vue 组件 props 验证

能够验证 props 的类型,默承认以校验是不是如下类型:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol

若是你的 props 是类的一个实例,type 也能够是一个自定义构造器函数,使用 instanceof 检测。

若是仍是不知足需求,能够自定义验证函数:

为 Vue 引入容器组件和展现组件

TypeScript 类型系统

Vue 组件 props 验证对于对象或者其余复杂的类型校验仍是不太友好,因此不少人也推荐你们的 props 尽可能采起简单类型,不过若是你有在用 TypeScript 开发 Vue 应用,能够利用 TypeScript 静态类型检查来声明你的 props 。

为 Vue 引入容器组件和展现组件

可测试性

因为组件作的事情更少了,使得测试也会变得容易。

容器组件不用关心 UI 的展现,只关心数据和更新。

展现组件只是呈现传入的 props ,写单元测试的时候也很是容易 mock 数据层。

引入容器组件/展现组件模式带来的限制

学习和开发成本

由于容器组件/展现组件的拆分,初期会增长一些学习成本,不过当你看完这篇文章,基本上也就入门了。

在开发的时候,因为须要封装一个容器,包装一些数据和接口给展现组件,会增长一些工做量, @xunlei/vuex-connector 经过配置的方式能够减轻很多你的工做量。

另外,在展现组件内对 props 的声明也会带来少许的工做。

整体来讲,引入容器组件/展现组件模式投入产出比仍是比较值得的。

延伸阅读

代码示例

Vue Vuex 容器-展现组件模式 Demo

Demo 在线地址:

xunleif2e.github.io/vue-vuex-co…

Demo 源码:

github.com/xunleif2e/v…

@xunlei/vuex-connector

基于 Vue 生态实现的Vuex store Connector

@xunlei/vuex-connector 源码:

github.com/xunleif2e/v…

欢迎 Star

扫一扫关注迅雷前端公众号

做者:binggg

校对:珈蓝

相关文章
相关标签/搜索