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
Vue 中的受控与非受控组件的更多相关文章
- 七天接手react项目 —— 生命周期&受控和非受控组件&Dom 元素&Diffing 算法
生命周期&受控和非受控组件&Dom 元素&Diffing 算法 生命周期 首先回忆一下 vue 中的生命周期: vue 对外提供了生命周期的钩子函数,允许我们在 vue 的各个 ...
- 浅谈React受控与非受控组件
背景 React内部分别使用了props, state来区分组件的属性和状态.props用来定义组件外部传进来的属性, 属于那种经过外部定义之后, 组件内部就无法改变.而state维持组件内部的状态更 ...
- React学习之受控和非受控组件
受控组件是通过事件完成对元素value的控制,反之就是非受控组件. 1.受控组件的value通过onChange事件来改变,非受控不需要通过事件来改变value. 2.受控组件通过事件通过setSta ...
- react 表单受控和非受控
参见:https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/ 非受控: onSubmit = ()=>{ const val ...
- vue中的v-model原理,与组件自定义v-model
VUE中的v-model可以实现双向绑定,但是原理是什么呢?往下看看吧 根据官方文档的解释,v-model其实是一个语法糖,它会自动的在元素或者组件上面解析为 :value="" ...
- vue中如何编写可复用的组件?
原文地址 Vue.js 是一套构建用户界面的渐进式框架.我们可以使用简单的 API 来实现响应式的数据绑定和组合的视图组件. 从维护视图到维护数据,Vue.js 让我们快速地开发应用.但随着业务代码日 ...
- js调DLL类库中的方法实现(非com组件形式)
1.首先,创建一个Web空项目 2.添加一个html或aspx页面 3.页面代码如所示: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Tr ...
- Web项目中获取SpringBean——在非Spring组件中获取SpringBean
最近在做项目的时候我发现一个问题:Spring的IOC容器不能在Web中被引用(或者说不能被任意地引用).我们在配置文件中让Spring自 动装配,但并没有留住ApplicationContext的实 ...
- Vue中router路由异步加载组件-优化性能
何时使用异步加载组件 当首页app.js文件太大时,可以拆分组件异步加载,如果app.js文件很小时,不建议使用异步加载组件,因为异步加载组件时每次都要发送一个HTTP请求,这样的代价远比首页一次性加 ...
随机推荐
- Snmp的学习总结(一)
摘自:http://blog.csdn.net/shanzhizi/article/details/11606767 目录(?)[-] SNMP的5种协议数据单元 SNMP的运行过程 11 Get ...
- FreeMarker数据模板引擎全面教程mark
http://blog.csdn.net/fhx007/article/details/7902040/#comments 以下内容全部是网上收集: FreeMarker的模板文件并不比HTML页面复 ...
- Laravel 5.1 报错:[App\Http\Requests\Request] is not instantiable
Laravel 5.1 报错:[App\Http\Requests\Request] is not instantiable 错误提示: Whoops, looks like something we ...
- jquery实现表单验证,所以的验证通过后方可提交
<html> <head> <meta http-equiv="content-type" content="tex ...
- Eclipse、IDEA安装JavaFX
简介 JavaFX插件 e(fx)eclipse是一组工具和必要的库,它们帮助您执行JavaFX编程,确保您已经作为插件将其安装在eclipse上了. JavaFX Scene Builder Jav ...
- 解决Eclipse中SVN版本信息不显示的问题
eclipse 中使用 svn 插件,原本正常,未作任何更改,最近几天突然eclipse 中查看文件时,文件后面的 版本号 . 文件的状态图标 等等都不见了.以为有插件冲突,卸载了好多其他的相关的插 ...
- spark学习(六)Java版RDD基本的基本操作
1.map算子 private static void map() { //创建SparkConf SparkConf conf = new SparkConf() .setAppName(" ...
- Nginx阻止DDoS攻击的教程收集(转)(待实践)
DDoS估计是一个非常头痛的问题. 分布式拒绝服务攻击(DDoS)指的是通过多台机器向一个服务或者网站发送大量看似合法的数据包使其网络阻塞.资源耗尽从而不能为正常用户提供正常服务的攻击手段.随着互联网 ...
- Linux中修改docker镜像源及安装docker
1.首先备份系统自带yum源配置文件/etc/yum.repos.d/CentOS-Base.repo mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.re ...
- TraceTool 跟踪工具的瑞士军刀(C++版使用)
TraceTool查看器能够显示多种类型的来源(从提供的框架.日志文件.事件日志.或者OutputDebugString方法).一个简单而强大的client框架发送简单的跟踪,分组跟踪.类和对象浏览器 ...