Element 2 组件源码剖析之Steps 步骤条(上)-组件状态初始化
简介
步骤条组件Steps是引导用户按照流程完成任务的分步导航条。当任务复杂或者存在先后关系时,将其分解成一系列步骤,从而简化任务,一半步骤不得少于 2 步。
本文将分析其源码实现,耐心读完,相信会对您有所帮助。🔗 组件文档 Steps 🔗 gitee源码
更多组件剖析详见 👉 📚 Element 2 源码剖析组件总览 。
组件源码
步骤条功能提供了两个组件:顶层组件el-steps和 子组件el-step。
各组件的 prop 声明,各属性功能说明详见官方文档 Steps#attributes 。
顶层组件 steps.vue
顶层组件基本上就是一个容器,包含着子组件el-step用到的共享状态。在el-step中直接通过$parent获取父实例,改变和同步其共享状态。 
模板渲染成一个类名el-steps的简单div元素,同时使用匿名插槽渲染步骤元素。
- 声明props用于外部传入的属性。
- 属性 steps用于保存当前实例下子组件el-step的实例数组。
- 属性 stepOffset用于设置步骤元素的间隔。
- 定义了侦听器,监听属性 - active用于触发的自定义- change事件,监听属性- steps更新每个子组件实例步骤索引。- // packages\steps\src\steps.vue <template> <div class="el-steps" :class="[ !simple && 'el-steps--' + direction, simple && 'el-steps--simple' ]"> <slot></slot> </div> </template> <script> export default { // props ... data() { return { steps: [], stepOffset: 0 }; }, watch: { active(newVal, oldVal) { this.$emit('change', newVal, oldVal); }, steps(steps) { steps.forEach((child, index) => { child.index = index; }); } } }; </script>
步骤组件状态初始化
为了更好理解步骤组件一些状态初始化操作, 接下结合父子组件的生命周期流程进行讲解。
父子组件实例在创建时经历一系列的初始化步骤:父beforeCreate -> 父created -> 父beforeMount ->子beforeCreate -> 子created -> 子beforeMount -> 子mounted-> 父mounted。首先,顶层组件el-steps初始化实例、 解析props、data()和侦听器等选项设置完毕。
其次,子组件el-step初始化实例、 解析props、data() 、计算属性、方法和侦听器等选项设置完毕。定义了beforeCreate钩子函数,当子组件实例初始化完成之后立即将该实例添加至父组件的属性steps数组中。
// packages\steps\src\step.vue
beforeCreate() {
  this.$parent.steps.push(this);
},再次,定义了mounted钩子函数,当子组件el-step被挂载之后,使用命令式的 $watch() 创建侦听器。用于监听属性index更改,执行侦听回调一次后,调用方法unwatch()就会停止该侦听器。
// packages\steps\src\step.vue
data() {
  return {
    index: -1 // 实例数组steps中索引值
  };
},
mounted() { 
  const unwatch = this.$watch('index', val => {
    // 省略 ... 
    // 停止该侦听器
    unwatch();
  });
}然后,顶层组件的属性steps数组长度大于0,会执行侦听回调,更新每个子组件实例的属性 index值,即实例在数组steps中索引值。
// packages\steps\src\steps.vue
watch: { 
  steps(steps) { 
    steps.forEach((child, index) => { 
      child.index = index;
    });
  }
}然后,触发子组件的index的属性侦听回调,此时会在子组件创建侦全局状态active、processStatus的侦听器,用于更新各个子组件的状态显示。用于触发回调方法updateStatus,参数是当前激活步骤的index。
// packages\steps\src\step.vue
const unwatch = this.$watch('index', val => { 
  this.$watch('$parent.active', this.updateStatus, { immediate: true });
  this.$watch('$parent.processStatus', () => {
    const activeIndex = this.$parent.active;
    this.updateStatus(activeIndex);
  }, { immediate: true });
  // 停止该侦听器
  unwatch();
});最后,执行方法 updateStatus,初始化子组件状态。因为定义了immediate: true,在侦听器创建时立即方法,所以会调用两次方法。 
子组件有三个内部状态(隐式) 激活/已完成/未激活,使用属性internalStatus表示内部状态值(显式)。
- 已完成当前激活步骤索引大于此步骤所在数组索引值,- internalStatus值为属性- finishStatus值。
- 激活当前激活步骤索引等于步骤所在数组索引值,若非首元素,则上一步骤元素的状态值不能为- error,也就是- error会中断步骤流程。- internalStatus值为属性- processStatus值。
- 未激活当前激活步骤索引小于此步骤所在数组索引值,- internalStatus值为- wait。
根据当前子组件的索引,获取它的上一元素prevChild,调用prevChild的方法prevChildcalcProgress计算步骤间轴线样式,此方法稍后会详细介绍。
// packages\steps\src\step.vue
updateStatus(val) {
  // 获取上一步骤
  const prevChild = this.$parent.$children[this.index - 1];
  if (val > this.index) {
    // 当前激活步骤索引大于此步骤索引值,该步骤状态为已完成
    this.internalStatus = this.$parent.finishStatus;
  } else if (val === this.index && this.prevStatus !== 'error') {
    // 上一步骤的状态是 'error'的化,之后步骤不会被激活
    this.internalStatus = this.$parent.processStatus;
  } else {
    // 未激活的状态为 'wait'
    this.internalStatus = 'wait';
  }
  // 存在上一步骤  步骤间轴线样式计算
  if (prevChild) prevChild.calcProgress(this.internalStatus); 
},步骤的状态
上面介绍了整个初始化的流程,也许大家会有疑惑,动态更新的属性internalStatus值作用是什么?
步骤组件提供了属性status值用于设置当前步骤的状态值;如果未设置, 就会根据属性internalStatus确定状态值。 
计算属性currentStatus返回当前步骤的状态值,计算属性 prevStatus 返回上一步骤的状态值。
props: 
  // ...
  status: String // 设置当前步骤的状态,不设置则根据 steps 确定状态
}, 
computed: {
  // 当前步骤的状态
  currentStatus() {
    return this.status || this.internalStatus;
  },
  // 上一步骤的状态
  prevStatus() {
    const prevStep = this.$parent.steps[this.index - 1];
    return prevStep ? prevStep.currentStatus : 'wait';
  },
}📚参考&关联阅读
关注专栏
如果本文对您有所帮助请关注➕、 点赞👍、 收藏⭐!您的认可就是对我的最大支持!
转载自:https://segmentfault.com/a/1190000042358451




