高级 Vue 组件模式 (8)

08 使用 Control Props

目标

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

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

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

  • toggle 组件的开关状态应该彻底由 prop 属性 on 的值决定
  • 当没有 on 属性时,toggle 组件的开关状态降级为内部管理

额外地,咱们还将实现一个小需求,toggle 组件的开关状态至多切换四次,若是超过四次,则需点击重置后,才可以从新对开关切换状态进行切换。this

实现

断定组件是否受控

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

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

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

更改 on 的声明方式

因为要知足目标中说起的第二点,关于 prop 属性 on 的声明,咱们要作出一些调整,以下:component

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

就是简单地将默认值,由 false 改成了 undefined,这么作的缘由是由于,按照以前的写法,若是 on 未由父组件传入,则默认值为 false,那么 toggle 组件会认为父组件实际传入了一个值为 falseon 属性,所以会将其内部的开关状态控制为,而非降级为内部管理开关状态。blog

实现状态解析逻辑

以前的实现中,经过 scope-slot 注入插槽的状态彻底取决于组件内部 status 的值,咱们须要改变状态的注入逻辑。当组件受控时,其开关状态应该与 prop 属性保持一致,反之,则和原来同样。所以编写一个叫作 controlledStatus 的计算属性:事件

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

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

<slot :status="controlledStatus" :toggle="toggle" :reset="reset"></slot>

相应地,除了开关状态的注入逻辑,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 组件被改造后,实现这个需求就很容易了。关于实现的代码,这里就不进行罗列了,有兴趣能够经过在线代码连接进行查看,十分简单,这里仅简单附上一个最终的动态效果图:

clipboard.png

你能够经过下面的连接来看看这个组件的实现代码以及演示:

总结

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

目录

github gist

关注公众号 全栈101,只谈技术,不谈人生
clipboard.png
相关文章
相关标签/搜索