Vue 中的受控与非受控组件

时间: 2018-12-17阅读: 1047标签: vue

vue 中的受控与非受控组件

熟悉 react 的开发者应该对“受控组件”的概念并不陌生,实际上对于任何组件化开发框架而言,都可以实现所谓的受控与非受控,vue 当然也不例外。并且理解受控与非受控对应的需求场景,可以让我们在设计一些基础组件时思路更加清晰,暴露出来的组件 API 也更加合理、统一。


需求

许多 UI 组件都是有状态(stateful)的,而这个状态是由组件外部控制还是组件内部维护,也就对应了受控与非受控两种模式。

例如 Tabs 组件是很常见的一种 UI 组件,它的核心状态就是记录当前 active 的 Tab,并且允许用户切换。

很多时候我们只希望 Tabs 可以正确的展示 active 的内容、并在用户操作时正常切换,不需要进行任何干预,那么就希望 只需要传入所有的 Tab 内容,不需要再做额外的配置。

但有的时候我们又希望对 Tabs 的状态有很强的控制能力,例如多个关联的 Tabs,子级 Tabs 的内容需要根据父级 Tabs 的 active Tab 动态切换,这时候就会希望 Tabs 组件可以暴露足够充分的 API,来实现业务的需求。

因此我们可以用一种通用的模式,来让任意组件的任意状态同时兼容受控与非受控两种模式,让不同需求场景下都可以使用最合理的 API。


简化示例

我们用一个简单的 Tabs 实现来演示这种通用的组件 API 设计模式,简化的部分包括:

  • 用 index 来作为 Tab 的唯一标识
  • Tab content 只支持字符串

可以打开 online DEMO 配合阅读


API 设计

对于 Vue 组件而言,API 设计主要指的是内部的 data, computed, methods 以及对外的 props, events。在这个示例中,我们会用 activeIdx 作为核心状态,所有的 API 也都会围绕这个状态命名。


非受控模式

如上文所说,非受控模式指的是使用者不需要关心控制组件的状体,完全交由组件内部维护。

因此我们的 API 会包括:

{
  props: {
    defaultActiveIdx: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      localActiveIdx: this.defaultActiveIdx
    }
  },
  methods: {
    handleActiveIdxChange(idx) {
      this.localActiveIdx = idx;
      this.$emit("active-idx-change", idx);
    }
  }
}

localActiveIdx 是我们用来存放 active index 的组件内 data,对于非受控模式而言,虽然不希望在外部维护状态,但是仍有可能希望在外部决定初始状态,所以我们用 defaultActiveIdx 这个 props 决定 localActiveIdx 的初始值。

之后当我们用 v-for="(tab, idx) in tabs" 指令生成所有的 Tab 时,就可以通过 idx === localActiveIdx 的方式判断当前 Tab 是否 active,再通过 @click="handleActiveIdxChange(idx)" 就可以实现对 localActiveIdx 的更新。

同样的,我们也可以通过 {{ tabs[localActiveIdx].content }} 展示 active Tab 的内容。

需要注意的是在 handleActiveIdxChange 的事件处理中,我们也 emit 了 active-idx-change 这一事件,这样可以方便外部在不需要管理组件状态的同时也可以与组件状态保持同步。例如我们希望将 active Tab 反映在 URL 中,就可以在外部监听 active-idx-change 这一事件,并将当前 index 同步到路由中,在将路由中获取到的 index 作为 defaultActiveIdx 传入,就可以实现 URL 和 Tabs 的同步。


受控模式

对于受控模式来说,我们可以理解为 active index 是外部传入的 props,由外部自行维护其状态。

因此我们只需要添加如下 props:

props: {
  activeIdx: Number
}

由于我们已经有对外 emit 的事件 active-idx-change,所以外部用以下方式就可以用一个 data 属性 externalActiveIdx维护对应状态:

<tabs
  :tabs="tabs"
  :activeIdx="externalActiveIdx"
  @active-idx-change="this.externalActiveIdx = $event"
/>

当然由于在这种模式下外部对状态有完全的控制权,所以在 active-idx-change 的事件处理中也可以做更为复杂的判断,例如是否允许激活目标 Tab 之类的校验。

而在 Tabs 组件内部,我们还需要做一些小的修改。在受控模式中,我们所有状态相关的处理都是直接使用 localActiveIdx,而现在我们的逻辑应该变为“如果存在 activeIdx props,则使用,否则使用 localActiveIdx”。

为了保证以上逻辑不会让我们的组件内部实现变得复杂、易错,我们引入一个 computed 属性:

computed: {
  _activeIdx() {
    return this.activeIdx || this.localActiveIdx;
  }
}

这样我们就可以把状态相关的判断改为通过 idx === _activeIdx 判断一个 Tab 是否为激活状态,也通过 {{ tabs[_activeIdx].content }} 展示 active Tab 的内容。

同样,我们在 handleActiveIdxChange 的方法内部也可以增加一个判断,如果存在 props aciveIdx 则不更新localActiveIdx:

handleActiveIdxChange(idx) {
  if (this.activeIdx === undefined) {
    this.localActiveIdx = idx;
  }
  this.$emit("active-idx-change", idx);
}

在一些更复杂的组件中,可能会频繁判断是否为受控模式并做不同的处理,这时候通过 this.activeIdx 这样的核心状态 props 是否传入来判断是否为受控模式是一个不错的实践。


总结

最终我们为 active index 设计的完整 API 如下:

{
  props: {
    activeIdx: Number,
    defaultActiveIdx: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      localActiveIdx: this.defaultActiveIdx
    };
  },
  computed: {
    _activeIdx() {
      return this.activeIdx || this.localActiveIdx;
    }
  },
  methods: {
    handleActiveIdxChange(idx) {
      if (this.activeIdx === undefined) {
        this.localActiveIdx = idx;
      }
      this.$emit("active-idx-change", idx);
    }
  }
}

通过这种 API 设计方式,可以让我们设计的基础组件使用方式更一致,拓展性更强,不论是开发还是使用时思路也会更加简洁清晰。

原文链接:https://segmentfault.com/a/1190000017395145  

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

链接: http://www.fly63.com/article/detial/1621

Vue2 中三种逻辑复用方法

Mixin对象能够使用组件的任何属性(data,mounted,created,update等),而且当组件使用mixin时,这个mixin的所有信息也会混合到这个组件里

Vue响应式开发,深入理解Vue.js响应式原理

深入理解Vue.js响应式原理。Vue教程有关的视频都讲到,我习惯响应式开发,在更早的Angular1时代,我们叫它:数据绑定(Data Binding)。你只需要在Vue实例的 data() 块中定义一些数据,并绑定到HTML

Vue高版本中一些新特性的使用

Vue高版本中一些新特性的使用:深度作用选择器、组件配置项inheritAttrs、组件实例属性$attrs和$listeners、组件选项 provide/inject、作用域插槽 slot-scope、Vue的错误捕获

Vue项目打包常见问题整理

Vue 项目在开发时运行正常,打包发布后却出现各种报错,这里整理一下遇到的问题,以备忘。js 路径问题、img 路径问题、favicon.ico 问题、IE9+ 兼容性问题、禁止生成 .map 文件

尤雨溪:Vue Function-based API RFC【转】

将 2.x 中与组件逻辑相关的选项以 API 函数的形式重新设计。组件 API 设计所面对的核心问题之一就是如何组织逻辑,以及如何在多个组件之间抽取和复用逻辑。

vue-router响应路由参数的变化

当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用

[译] Vue.js 和 Alpine.js 比较

Vue.js 是当前世界上最流行的 JavaScript 框架之一,发布于 2014 年,第三个版本即将发布(译著:翻译时已经发布),不会有大的变化。

vue之nextTick全面解析

vue是非常流行的框架,他结合了angular和react的优点,从而形成了一个轻量级的易上手的具有双向数据绑定特性的mvvm框架。本人比较喜欢用之。在我们用vue时,我们经常用到一个方法是this.$nextTick

Vue2.0用户权限控制解决方案

Vue-Access-Control是一套基于Vue/Vue-Router/axios 实现的前端用户权限控制解决方案,通过对路由、视图、请求三个层面的控制,使开发者可以实现任意颗粒度的用户权限控制。

vue框架开发出现页面空白、白屏的解决方法总汇

使用Vue-cli脚手架开发单页应用时出现页面空白的情况:1.npm run build打包页面空白,2. iOS的Safari下无法打开网页,3.升级vue2+部分手机访问出现页面空白,4.升级vue2+IP访问页面空白,5.Vue在IE下显示空白问题,6.Vue只在iOS 10出现白屏问题

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!