资讯专栏INFORMATION COLUMN

高级 Vue 组件模式 (8)

Lemon_95 / 944人阅读

摘要:在一些业务场景,我们期望父组件对于子组件的状态,拥有绝对的控制权。而对于前者则相反,由于组件内部会有自己的状态,它内部的渲染逻辑由父组件所传与其内部状态共同决定。当组件受控时,其开关状态应该与属性保持一致,反之,则和原来一样。

08 使用 Control Props 目标

在第七篇文章中,我们对 toggle 组件进行了重构,使父组件能够传入开关状态的初始值,同时还可以传入自定义的状态重置逻辑。虽然父组件拥有了改变 toggle 组件内部状态的途径,但是如果进一步思考的话,父组件并没有绝对的控制权。在一些业务场景,我们期望父组件对于子组件的状态,拥有绝对的控制权。

熟悉 React 的读者一定不会对智能组件(Smart Component)和木偶组件(Dump Component)感到陌生。对于后者,其父组件一定对其拥有绝对控制权,因为它内部没有状态,渲染逻辑完全取决于父组件所传 props 的值。而对于前者则相反,由于组件内部会有自己的状态,它内部的渲染逻辑由父组件所传 props 与其内部状态共同决定。

这篇文章将着重解决这个问题,如果能够使一个智能组件的状态变得可控,即:

toggle 组件的开关状态应该完全由 prop 属性 on 的值决定

当没有 on 属性时,toggle 组件的开关状态降级为内部管理

额外地,我们还将实现一个小需求,toggle 组件的开关状态至多切换四次,如果超过四次,则需点击重置后,才能够重新对开关切换状态进行切换。

实现 判定组件是否受控

由于 toggle 组件为一个智能组件,我们需要提供一个判定它是否受控的方式。很简单,由目标中的第一点可知,当父组件传入了 on 属性后,toggle 处于被控制的状态,否则则没有,于是可以利用 Vue 组件的 computed 特性,声明一个 isOnControlled 计算属性,如下:

computed: {
  isOnControlled() {
    return this.on !== undefined;
  }
}

其内部逻辑很简单,就是判定 prop 属性 on 的值是否为 undefined,如果是,则未被父组件控制,反之,则被父组件控制。

更改 on 的声明方式

由于要满足目标中提及的第二点,关于 prop 属性 on 的声明,我们要做出一些调整,如下:

on: {
  type: Boolean,
  default: undefined
},

就是简单地将默认值,由 false 改为了 undefined,这么做的原因是因为,按照之前的写法,如果 on 未由父组件传入,则默认值为 false,那么 toggle 组件会认为父组件实际传入了一个值为 falseon 属性,因此会将其内部的开关状态控制为,而非降级为内部管理开关状态。

实现状态解析逻辑

之前的实现中,通过 scope-slot 注入插槽的状态完全取决于组件内部 status 的值,我们需要改变状态的注入逻辑。当组件受控时,其开关状态应该与 prop 属性保持一致,反之,则和原来一样。因此编写一个叫做 controlledStatus 的计算属性:

controlledStatus() {
  return this.isOnControlled ? { on: this.on } : this.status;
}

这里利用了之前声明的 isOnControlled 属性来判断当前组件是否处于受控状态。之后相应地把模板中开关状态的注入逻辑也进行更改:

相应地,除了开关状态的注入逻辑,toggle 方法和 reset 方法的注入逻辑也需要更改,至于为什么,就交由读者自行思考得出答案吧,这里简单罗列实现代码,以供参考:

// toggle 方法
toggle() {
  if (this.isOnControlled) {
    this.$emit("toggle", !this.on);
  } else {
    this.status.on = !this.status.on;
    this.$emit("toggle", this.status.on);
  }
}

// reset 方法
reset() {
  if (this.isOnControlled) {
    Promise.resolve(this.onReset(!this.on)).then(on => {
      this.$emit("reset", on);
    });
  } else {
    Promise.resolve(this.onReset(this.status.on)).then(on => {
      this.status.on = on || false;
      this.$emit("reset", this.status.on);
    });
  }
}

总体上的思路是,如果组件受控,则传入回调方法中的开关状态参数,是在触发相应事件后,由 prop 属性 on 得出的组件在下一时刻,应当处于的状态。

这么说可能有点绕,换句话说就是,当组件状态发生更改时,如果当前的 on 属性为 true(开关状态为开),则组件本该处于关的状态,但由于组件受控,则它内部不能直接将开关状态更改为关,而是依旧保持为开,但是它会将 false(开关状态为关)作为参数传入触发事件,这将告知父组件,当前组件的下一个状态为关,至于父组件是否同意将其状态更改为关则有父组件决定。

如果组件不受控,开关状态由组件内部自行管理,那和之前的实现逻辑是一模一样的,保留之前的代码即可。

成果

toggle 组件被改造后,实现这个需求就很容易了。关于实现的代码,这里就不进行罗列了,有兴趣可以通过在线代码链接进行查看,十分简单,这里仅简单附上一个最终的动态效果图:

你可以通过下面的链接来看看这个组件的实现代码以及演示:

sandbox: 在线演示

github: part-8

总结

关于 Controlled Component 和 Uncontrolled Component 的概念,我第一次是在 React 中关于表单的介绍中接触到的。实际工作中,大部分对于状态可控的需求也都存在于表单组件中,之所以存在这样的需求,是因为表单系统往往是复杂的,将其实现为智能组件,往往内部状态过于复杂,而如果实现为木偶组件,代码结构或者实现逻辑又过于繁琐,这时如果可以借鉴这种模式的话,往往可以达到事半功倍的效果。

目录

github gist

关注公众号 全栈101,只谈技术,不谈人生

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98766.html

相关文章

  • 高级 Vue 组件模式 (1)

    摘要:写在前头去年,曾经阅读过一系列关于高级组件模式的文章,今年上半年,又抽空陆陆续续地翻译了一系列关于高级组件模式的文章,碰巧最近接手了一个公司项目,前端这块的技术栈是。同时这个组件还拥有一个属性,用来初始化的状态值。 写在前头 去年,曾经阅读过一系列关于高级 react 组件模式的文章,今年上半年,又抽空陆陆续续地翻译了一系列关于高级 angular 组件模式的文章,碰巧最近接手了一个公...

    lanffy 评论0 收藏0
  • 前端面试题总结(js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)

    摘要:并总结经典面试题集各种算法和插件前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快速搭建项目。 本文是关注微信小程序的开发和面试问题,由基础到困难循序渐进,适合面试和开发小程序。并总结vue React html css js 经典面试题 集各种算法和插件、前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快...

    pumpkin9 评论0 收藏0
  • 前端面试题总结(js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)

    摘要:并总结经典面试题集各种算法和插件前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快速搭建项目。 本文是关注微信小程序的开发和面试问题,由基础到困难循序渐进,适合面试和开发小程序。并总结vue React html css js 经典面试题 集各种算法和插件、前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快...

    Carson 评论0 收藏0
  • 前端面试题总结(js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)

    摘要:并总结经典面试题集各种算法和插件前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快速搭建项目。 本文是关注微信小程序的开发和面试问题,由基础到困难循序渐进,适合面试和开发小程序。并总结vue React html css js 经典面试题 集各种算法和插件、前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快...

    muzhuyu 评论0 收藏0

发表评论

0条评论

Lemon_95

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<