[Element Plus 源码解析] Breadcrumb 面包屑

2,289 阅读1分钟

一、组件介绍

官网链接:Breadcrumb 组件 | Element (gitee.io)

Breadcrumb组件用于显示当前页面的路径,快速返回之前的任意页面;该组件需要与Breadcrumb Item组件搭配使用。

面包屑这个名称来源于童话故事:

当汉赛尔和格莱特穿过森林时,不小心迷路了,但是他们发现在沿途走过的地方都撒下了面包屑,让这些面包屑来帮助他们找到回家的路。所以,面包屑导航的作用是告诉访问者他们目前在网站中的位置以及如何返回。

1.1 Breadcrumb 属性

  • separator: string类型,路径间的分割符号,默认是'/';
  • separator-class: string类型,路径分割符的class,用于自定义图标分隔符,传入separator-class会使separator属性失效;

1.2 Breadcrumb Item 属性

  • to: string|object类型;路由跳转对象,同vue-router中的to属性;
  • replace: boolean类型,在使用 to 进行路由跳转时,是否使用replace模式,默认值是false

二、源码分析

2.1:Breadcrumb 源码

<template>
  <div
    ref="breadcrumb"
    class="el-breadcrumb"
    aria-label="Breadcrumb"
    role="navigation"
  >
    <slot></slot>
  </div>
</template>

<script lang="ts">
import { defineComponent, provide, ref, onMounted } from 'vue'
import type { IBreadcrumbProps } from './breadcrumb'

export default defineComponent({
  name: 'ElBreadcrumb',
  props: {
    separator: {
      type: String,
      default: '/',
    },
    separatorClass: {
      type: String,
      default: '',
    },
  },
  setup(props) {
    const breadcrumb = ref(null)
    // 通过provide向子孙组件提供信息
    provide<IBreadcrumbProps>('breadcrumb', props)
    
    // mounted的执行顺序是:子组件先执行,父组件后执行
    onMounted(() => {
      // 查询当前breadcrumb下的所有breadcrumb-_item
      const items = breadcrumb.value.querySelectorAll('.el-breadcrumb__item')
      if (items.length) {
        // 给最后一个breadcrumb-_item设置aria-current属性
        items[items.length - 1].setAttribute('aria-current', 'page')
      }
    })

    return {
      breadcrumb,
    }
  },
})
</script>

2.2 Breadcrumb Item 源码

<template>
  <span class="el-breadcrumb__item">
    <span
      ref="link"
      :class="['el-breadcrumb__inner', to ? 'is-link' : '']"
      role="link"
    >
      // 默认插槽
      <slot></slot>
    </span>
    // 传入separatorClass的情况
    <i
      v-if="separatorClass"
      class="el-breadcrumb__separator"
      :class="separatorClass"
    ></i>
    // 使用separator的情况
    <span v-else class="el-breadcrumb__separator" role="presentation">{{
      separator
    }}</span>
  </span>
</template>

<script lang="ts">
import {
  defineComponent,
  inject,
  ref,
  onMounted,
  getCurrentInstance,
} from 'vue'
import type { IBreadcrumbProps } from './breadcrumb'
import type { PropType } from 'vue'

export default defineComponent({
  name: 'ElBreadcrumbItem',
  props: {
    to: {
      type: [String, Object] as PropType<string | Record<string, unknown>>,
      default: '',
    },
    replace: {
      type: Boolean,
      default: false,
    },
  },
  setup(props) {
    const link = ref(null)
    // 通过inject接受breadcrumb提供的数据
    const parent = inject<IBreadcrumbProps>('breadcrumb')
    // 取得当前vue实例
    const instance = getCurrentInstance()
    // 获取vue-router实例
    const router = instance.appContext.config.globalProperties.$router

    onMounted(() => {
      link.value.setAttribute('role', 'link')
      // 路由跳转
      link.value.addEventListener('click', () => {
        if (!props.to || !router) return
        props.replace ? router.replace(props.to) : router.push(props.to)
      })
    })

    return {
      link,
      // 父组件上的separator属性,通过provide/inject,在子组件上生效
      separator: parent?.separator,
      separatorClass: parent?.separatorClass,
    }
  },
})
</script>

2.3 总结

  1. 通过provide/inject,将父组件上的数据传递到子组件;
  2. 使用getCurrentInstance() api获取组件实例,并通过instance.appContext.config.globalProperties获取全局配置的Vue属性值