Vue 中的受控与非受控组件的实现
受控组件
什么是受控组件?
其值由React控制的输入表单元素称为“受控组件”。
受控组件有两个特点:1.设置value值,value由state控制,2.value值一般在onChange事件中通过setState进行修改
什么时候使用受控组件?
需要对组件的value值进行修改时,使用受控组件。比如:页面中有一个按钮,每点击一次按钮受控组件的值加1.
非受控组件
什么是非受控组件?
表单数据由DOM处理的组件非受控组件。
非受控组件有两个特点:1.不设置value值,2.通过ref获取dom节点然后再取value值
this.usernameElem=input}/>
取值方法:this.usernameElem.value
什么时候使用非受控组件?
任何时候都不需要改变组件的value值,这时候可以使用非受控组件。
Vue中的受控与非受控组件
熟悉React的开发者应该对“受控组件”的概念并不陌生,实际上对于任何组件化开发框架而言,都可以实现所谓的受控与非受控,Vue当然也不例外。并且理解受控与非受控对应的需求场景,可以让我们在设计一些基础组件时思路更加清晰,暴露出来的组件API也更加合理、统一。
需求
许多UI组件都是有状态(stateful)的,而这个状态是由组件外部控制还是组件内部维护,也就对应了受控与非受控两种模式。
例如Tabs组件是很常见的一种UI组件,它的核心状态就是记录当前active的Tab,并且允许用户切换。
很多时候我们只希望Tabs可以正确的展示active的内容、并在用户操作时正常切换,不需要进行任何干预,那么就希望只需要传入所有的Tab内容,不需要再做额外的配置。
但有的时候我们又希望对Tabs的状态有很强的控制能力,例如多个关联的Tabs,子级Tabs的内容需要根据父级Tabs的activeTab动态切换,这时候就会希望Tabs组件可以暴露足够充分的API,来实现业务的需求。
因此我们可以用一种通用的模式,来让任意组件的任意状态同时兼容受控与非受控两种模式,让不同需求场景下都可以使用最合理的API。
简化示例
我们用一个简单的Tabs实现来演示这种通用的组件API设计模式,简化的部分包括:
- 用index来作为Tab的唯一标识
- Tabcontent只支持字符串
可以打开onlineDEMO配合阅读
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是我们用来存放activeindex的组件内data,对于非受控模式而言,虽然不希望在外部维护状态,但是仍有可能希望在外部决定初始状态,所以我们用defaultActiveIdx这个props决定localActiveIdx的初始值。
之后当我们用v-for="(tab,idx)intabs"指令生成所有的Tab时,就可以通过idx===localActiveIdx的方式判断当前Tab是否active,再通过@click="handleActiveIdxChange(idx)"就可以实现对localActiveIdx的更新。
同样的,我们也可以通过{{tabs[localActiveIdx].content}}展示activeTab的内容。
需要注意的是在handleActiveIdxChange的事件处理中,我们也emit了active-idx-change这一事件,这样可以方便外部在不需要管理组件状态的同时也可以与组件状态保持同步。例如我们希望将activeTab反映在URL中,就可以在外部监听active-idx-change这一事件,并将当前index同步到路由中,在将路由中获取到的index作为defaultActiveIdx传入,就可以实现URL和Tabs的同步。
受控模式
对于受控模式来说,我们可以理解为activeindex是外部传入的props,由外部自行维护其状态。
因此我们只需要添加如下props:
props:{ activeIdx:Number }
由于我们已经有对外emit的事件active-idx-change,所以外部用以下方式就可以用一个data属性externalActiveIdx维护对应状态:
当然由于在这种模式下外部对状态有完全的控制权,所以在active-idx-change的事件处理中也可以做更为复杂的判断,例如是否允许激活目标Tab之类的校验。
而在Tabs组件内部,我们还需要做一些小的修改。在受控模式中,我们所有状态相关的处理都是直接使用localActiveIdx,而现在我们的逻辑应该变为“如果存在activeIdxprops,则使用,否则使用localActiveIdx”。
为了保证以上逻辑不会让我们的组件内部实现变得复杂、易错,我们引入一个computed属性:
computed:{ _activeIdx(){ returnthis.activeIdx||this.localActiveIdx; } }
这样我们就可以把状态相关的判断改为通过idx===_activeIdx判断一个Tab是否为激活状态,也通过{{tabs[_activeIdx].content}}展示activeTab的内容。
同样,我们在handleActiveIdxChange的方法内部也可以增加一个判断,如果存在propsaciveIdx则不更新localActiveIdx:
handleActiveIdxChange(idx){ if(this.activeIdx===undefined){ this.localActiveIdx=idx; } this.$emit("active-idx-change",idx); }
在一些更复杂的组件中,可能会频繁判断是否为受控模式并做不同的处理,这时候通过this.activeIdx这样的核心状态props是否传入来判断是否为受控模式是一个不错的实践。
总结
最终我们为activeindex设计的完整API如下:
{ props:{ activeIdx:Number, defaultActiveIdx:{ type:Number, default:0 } }, data(){ return{ localActiveIdx:this.defaultActiveIdx }; }, computed:{ _activeIdx(){ returnthis.activeIdx||this.localActiveIdx; } }, methods:{ handleActiveIdxChange(idx){ if(this.activeIdx===undefined){ this.localActiveIdx=idx; } this.$emit("active-idx-change",idx); } } }
通过这种API设计方式,可以让我们设计的基础组件使用方式更一致,拓展性更强,不论是开发还是使用时思路也会更加简洁清晰。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。