Vue 中的受控与非受控组件

熟习 React 的开发者应该对“受控组件”的概念并不陌生,实际上对于任何组件化开发框架而言,都可以实现所谓的受控与非受控,Vue 当然也不例外。并且了解受控与非受控对应的需求场景,可以让我们在设计少量基础组件时思路更加清晰,暴露出来的组件 API 也更加正当、统一。
需求
许多 UI 组件都是有状态(stateful)的,而这个状态是由组件外部控制还是组件内部维护,也就对应了受控与非受控两种模式。
例如 Tabs 组件是很常见的一种 UI 组件,它的核心状态就是记录当前 active 的 Tab,并且允许客户切换。
很多时候我们只希望 Tabs 可以正确的展现 active 的内容、并在客户操作时正常切换,不需要进行任何干预,那么就希望 只要要传入所有的 Tab 内容,不需要再做额外的配置。
但有的时候我们又希望对 Tabs 的状态有很强的控制能力,例如多个关联的 Tabs,子级 Tabs 的内容需要根据父级 Tabs 的 active Tab 动态切换,这时候就会希望 Tabs 组件可以暴露足够充分的 API,来实现业务的需求。
因而我们可以用一种通用的模式,来让任意组件的任意状态同时兼容受控与非受控两种模式,让不同需求场景下都可以使用最正当的 API。
顺便给大家推荐一个裙,它的前面是 537,中间是631,最后就是 707。想要学习前台的小伙伴可以加入我们一起学习,互相帮助。群里每天晚上都有大神免费直播上课,假如不是想学习的小伙伴就不要加啦。
简化示例
我们用一个简单的 Tabs 实现来演示这种通用的组件 API 设计模式,简化的部分包括:
用 index 来作为 Tab 的唯一标识
Tab content 只支持字符串
可以打开?online DEMO配合阅读
【https://codesandbox.io/s/k2qxy3oz6o】
API 设计
对于 Vue 组件而言,API 设计主要指的是内部的 data, computed, methods 以及对外的 props, events。在这个示例中,我们会用activeIdx作为核心状态,所有的 API 也都会围绕这个状态命名。
非受控模式
如上文所说,非受控模式指的是使用者不需要关心控制组件的状体,完全交由组件内部维护。
因而我们的 API 会包括:
1{
2props:?{
3defaultActiveIdx:?{
4type:Number,
5default:0
6}
7},
8data()?{
9return{
10localActiveIdx:this.defaultActiveIdx
11}
12},
13methods:?{
14handleActiveIdxChange(idx)?{
15this.localActiveIdx?=?idx;
16this.$emit(“active-idx-change”,?idx);
17}
18}
19}
localActiveIdx是我们用来存放 active index 的组件内 data,对于非受控模式而言,尽管不希望在外部维护状态,但是仍有可能希望在外部决定初始状态,所以我们用defaultActiveIdx这个 props 决定localActiveIdx的初始值。
之后当我们用v-for=”(tab, idx) in tabs”指令生成所有的 Tab 时,即可以通过idx === localActiveIdx的方式判断当前 Tab 能否 active,再通过@click=”handleActiveIdxChange(idx)”即可以实现对localActiveIdx的升级。
同样的,我们也可以通过展现 active Tab 的内容。
需要注意的是在handleActiveIdxChange的事件解决中,我们也 emit 了active-idx-change这一事件,这样可以方便外部在不需要管理组件状态的同时也可以与组件状态保持同步。例如我们希望将 active Tab 反映在 URL 中,即可以在外部监听active-idx-change这一事件,并将当前 index 同步到路由中,在将路由中获取到的 index 作为defaultActiveIdx传入,即可以实现 URL 和 Tabs 的同步。
顺便给大家推荐一个裙,它的前面是 537,中间是631,最后就是 707。想要学习前台的小伙伴可以加入我们一起学习,互相帮助。群里每天晚上都有大神免费直播上课,假如不是想学习的小伙伴就不要加啦。
受控模式
对于受控模式来说,我们可以了解为 active index 是外部传入的 props,由外部自行维护其状态。
因而我们只要要增加如下 props:
1props:?{
2activeIdx:Number
3}
因为我们已经有对外 emit 的事件active-idx-change,所以外部用以下方式即可以用一个 data 属性externalActiveIdx维护对应状态:
1
2:tabs=”tabs”
3:activeIdx=”externalActiveIdx”
4@active-idx-change=”this.externalActiveIdx?=?$event”
5/>
当然因为在这种模式下外部对状态有完全的控制权,所以在active-idx-change的事件解决中也可以做更为复杂的判断,例如能否允许激活目标 Tab 之类的校验。
而在 Tabs 组件内部,我们还需要做少量小的修改。在受控模式中,我们所有状态相关的解决都是直接使用localActiveIdx,而现在我们的逻辑应该变为“假如存在activeIdxprops,则使用,否则使用localActiveIdx”。
为了保证以上逻辑不会让我们的组件内部实现变得复杂、易错,我们引入一个 computed 属性:
1computed:?{
2_activeIdx()?{
3returnthis.activeIdx?||this.localActiveIdx;
4}
5}
这样我们即可以把状态相关的判断改为通过idx === _activeIdx判断一个 Tab 能否为激活状态,也通过展现 active Tab 的内容。
同样,我们在handleActiveIdxChange的方法内部也可以添加一个判断,假如存在 propsaciveIdx则不升级localActiveIdx:
1handleActiveIdxChange(idx)?{
2if(this.activeIdx?===undefined)?{
3this.localActiveIdx?=?idx;
4}
5this.$emit(“active-idx-change”,?idx);
6}
在少量更复杂的组件中,可能会频繁判断能否为受控模式并做不同的解决,这时候通过this.activeIdx这样的核心状态 props 能否传入来判断能否为受控模式是一个不错的实践。
总结
最终我们为 active index 设计的完整 API 如下:
1{
2props:?{
3activeIdx:Number,
4defaultActiveIdx:?{
5type:Number,
6default:0
7}
8},
9data()?{
10return{
11localActiveIdx:this.defaultActiveIdx
12};
13},
14computed:?{
15_activeIdx()?{
16returnthis.activeIdx?||this.localActiveIdx;
17}
18},
19methods:?{
20handleActiveIdxChange(idx)?{
21if(this.activeIdx?===undefined)?{
22this.localActiveIdx?=?idx;
23}
24this.$emit(“active-idx-change”,?idx);
25}
26}
27}
通过这种 API 设计方式,可以让我们设计的基础组件使用方式更一致,拓展性更强,不管是开发还是使用时思路也会更加简洁清晰。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Vue 中的受控与非受控组件