Vue 中的受控与非受控组件的实现


Posted in Javascript onDecember 17, 2018

受控组件

什么是受控组件?

其值由React控制的输入表单元素称为“受控组件”。

受控组件有两个特点:1. 设置value值,value由state控制,2. value值一般在onChange事件中通过setState进行修改

什么时候使用受控组件?

需要对组件的value值进行修改时,使用受控组件。比如:页面中有一个按钮,每点击一次按钮受控组件的值加1.

非受控组件

什么是非受控组件?

表单数据由 DOM 处理的组件非受控组件。

非受控组件有两个特点:1. 不设置value值,2. 通过ref获取dom节点然后再取value值

<input type="text" placeholder="请输入姓名" name='username' ref={(input) => this.usernameElem = input}/>

取值方法:this.usernameElem.value

什么时候使用非受控组件?

任何时候都不需要改变组件的value值,这时候可以使用非受控组件。

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 设计方式,可以让我们设计的基础组件使用方式更一致,拓展性更强,不论是开发还是使用时思路也会更加简洁清晰。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript XML数据显示为HTML一例
Dec 23 Javascript
用jQuery中的ajax分页实现代码
Sep 20 Javascript
jQuery学习笔记之控制页面实现代码
Feb 27 Javascript
Jquery动态替换div内容及动态展示的方法
Jan 23 Javascript
vue,angular,avalon这三种MVVM框架优缺点
Apr 27 Javascript
js修改onclick动作的四种方法(推荐)
Aug 18 Javascript
KVM虚拟化技术之使用Qemu-kvm创建和管理虚拟机的方法
Oct 05 Javascript
基于jQuery实现顶部导航栏功能
Dec 27 Javascript
Bootstrap fileinput 上传新文件移除时触发服务器同步删除的配置
Oct 08 Javascript
微信小程序实现留言板
Oct 31 Javascript
Element-UI中Upload上传文件前端缓存处理示例
Feb 21 Javascript
前端JavaScript大管家 package.json
Nov 02 Javascript
js实现移动端轮播图
Dec 21 #Javascript
微信小程序登录按钮遮罩浮层效果的实现方法
Dec 16 #Javascript
微信小程序 JS动态修改样式的实现方法
Dec 16 #Javascript
Echart折线图手柄触发事件示例详解
Dec 16 #Javascript
vue使用pdfjs显示PDF可复制的实现方法
Dec 14 #Javascript
antd Upload 文件上传的示例代码
Dec 14 #Javascript
Vue源码解析之Template转化为AST的实现方法
Dec 14 #Javascript
You might like
自动跳转中英文页面
2006/10/09 PHP
PHP中“简单工厂模式”实例代码讲解
2012/09/04 PHP
php中smarty区域循环的方法
2015/06/11 PHP
Laravel 5使用Laravel Excel实现Excel/CSV文件导入导出的功能详解
2017/10/11 PHP
php 实现银联商务H5支付的示例代码
2019/10/12 PHP
JS方法调用括号的问题探讨
2014/01/24 Javascript
JS控制弹出悬浮窗口(一览画面)的实例代码
2016/05/30 Javascript
jQuery 出现Cannot read property ‘msie’ of undefined错误的解决方法
2016/11/23 Javascript
javascript按顺序加载运行js方法
2017/12/01 Javascript
关于HTTP传输中gzip压缩的秘密探索分析
2018/01/12 Javascript
vue 路由页面之间实现用手指进行滑动的方法
2018/02/23 Javascript
vue利用axios来完成数据的交互
2018/03/23 Javascript
浅析Vue 生命周期
2018/06/21 Javascript
QQ跳转支付宝并自动领红包脚本(最新)
2018/06/22 Javascript
JavaScript数组基于交换的排序示例【冒泡排序】
2018/07/21 Javascript
vue项目打包部署到服务器的方法示例
2018/08/27 Javascript
js笔试题-接收get请求参数
2019/06/15 Javascript
JS中的算法与数据结构之字典(Dictionary)实例详解
2019/08/20 Javascript
vue中组件通信详解(父子组件, 爷孙组件, 兄弟组件)
2020/07/27 Javascript
JS highcharts动态柱状图原理及实现
2020/10/16 Javascript
[01:57]2018年度DOTA2最具潜力解说-完美盛典
2018/12/16 DOTA
Python面向对象编程基础解析(二)
2017/10/26 Python
Python制作微信好友背景墙教程(附完整代码)
2019/07/17 Python
python实现删除列表中某个元素的3种方法
2020/01/15 Python
python连接mongodb集群方法详解
2020/02/13 Python
Django Admin 上传文件到七牛云的示例代码
2020/06/20 Python
python绘制高斯曲线
2021/02/19 Python
诗狄娜化妆品官方网站:Stila Cosmetics
2016/12/21 全球购物
幼儿园托班开学寄语
2014/01/18 职场文书
课内比教学心得体会
2014/09/09 职场文书
甲乙双方合作协议书
2014/10/13 职场文书
大学生见习总结报告
2015/06/24 职场文书
yyds什么意思?90后已经听不懂00后讲话了……
2022/02/03 杂记
python接口测试返回数据为字典取值方式
2022/02/12 Python
基于Python实现将列表数据生成折线图
2022/03/23 Python
JavaScript中的LHS和RHS分析详情
2022/04/06 Javascript