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 相关文章推荐
经典的带阴影的可拖动的浮动层
Jun 26 Javascript
jQuery 学习第五课 Ajax 使用说明
May 17 Javascript
javascript用户注册提示效果的简单实例
Aug 17 Javascript
JavaScript实现班级随机点名小应用需求的具体分析
May 12 Javascript
2014年最火的Node.JS后端框架推荐
Oct 27 Javascript
JS修改iframe页面背景颜色的方法
Apr 01 Javascript
JavaScript实现点击单选按钮改变输入框中文本域内容的方法
Aug 12 Javascript
基于Jquery实现焦点图淡出淡入效果
Nov 30 Javascript
全面解析Bootstrap中nav、collapse的使用方法
May 22 Javascript
Bootstrap弹出框(modal)垂直居中的问题及解决方案详解
Jun 12 Javascript
详解BootStrap中Affix控件的使用及保持布局的美观的方法
Jul 08 Javascript
jQuery使用hide()、toggle()函数实现相机品牌展示隐藏功能
Jan 29 jQuery
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
php 应用程序安全防范技术研究
2009/09/25 PHP
php cookie的操作实现代码(登录)
2010/12/29 PHP
Laravel 5框架学习之路由、控制器和视图简介
2015/04/07 PHP
ThinkPHP进程计数类Process用法实例详解
2015/09/25 PHP
PHP实现的curl批量请求操作示例
2018/06/06 PHP
十分钟打造AutoComplete自动完成效果代码
2009/12/26 Javascript
JQuery页面图片切换和新闻列表滚动效果的具体实现
2013/09/26 Javascript
JS页面延迟执行一些方法(整理)
2013/11/11 Javascript
深入理解JavaScript高级之词法作用域和作用域链
2013/12/10 Javascript
jQuery简单实现网页选项卡特效
2014/11/24 Javascript
jquery对象和DOM对象的任意相互转换
2016/02/21 Javascript
详谈JS中实现种子随机数及作用
2016/07/19 Javascript
使用jQuery的load方法设计动态加载及解决被加载页面js失效问题
2017/03/01 Javascript
微信小程序实现多个按钮toggle功能的实例
2017/06/13 Javascript
JavaScript hasOwnProperty() 函数实例详解
2017/08/04 Javascript
node.js支持多用户web终端实现及安全方案
2017/11/29 Javascript
浅谈vuejs实现数据驱动视图原理
2018/02/23 Javascript
微信小程序实现手指触摸画板
2018/07/09 Javascript
解决Layui中templet中a的onclick参数传递的问题
2019/09/20 Javascript
Vue Router 实现动态路由和常见问题及解决方法
2020/03/06 Javascript
深入学习Python中的装饰器使用
2016/06/20 Python
详解python中的装饰器
2018/07/10 Python
Python找出微信上删除你好友的人脚本写法
2018/11/01 Python
pandas dataframe添加表格框线输出的方法
2019/02/08 Python
python 定时器,轮询定时器的实例
2019/02/20 Python
Python批量将图片灰度化的实现代码
2020/04/11 Python
CSS3 实现的加载动画
2020/12/07 HTML / CSS
HTML5新增的标签和属性归纳总结
2018/05/02 HTML / CSS
波兰最大的宠物用品网上商店:FERA.PL
2019/08/11 全球购物
Whistles官网:英国女装品牌
2020/08/14 全球购物
财政局长自荐信范文
2013/12/22 职场文书
结婚典礼证婚词
2014/01/11 职场文书
2014年迎新年活动方案
2014/02/19 职场文书
法院干警四风问题个人对照检查材料思想汇报
2014/10/07 职场文书
2016猴年开门红标语口号
2015/12/26 职场文书
MySQL count(*)统计总数问题汇总
2022/09/23 MySQL