Element 2 组件源码剖析之Avatar头像

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战javascript

0x00 简介

组件Avatar用来表明用户或事物,支持图片、图标或字符等类型。css

本文将深刻分析组件 Avatar 源码,剖析其实现原理,耐心读完,相信会对您有所帮助。 组件文档 Avatarhtml

packages/avatar/src/icon.vue 文件是组件源码实现。 github源码 main.vuevue

0x01 组件源码

从源码可知组件没有使用template 来建立 HTML,而是使用具备 JavaScript 的彻底编程的能力的渲染函数,它比模板更接近编译器。java

<script>
export default {
  name: 'ElAvatar',
  // 组件属性prop
  props: {
    // ...
  },
  // 数据属性
  data() {
    // ...
  },
  // 计算属性
  computed: {
    // 组件动态 class
    avatarClass() {
       // ...
    }
  },
  methods: {
    // 加载失败处理方法
    handleError() {
      // ...
    },
    // 生成图标、图片或者字符等不一样类型元素VNode
    renderAvatar() {
      // ...
    }
  },
  // 渲染虚拟DOM
  render() {
    // ...
  } 
};
</script> 
复制代码

attributes 属性

组件定义了8个 propgit

// 组件属性prop
  props: {
    size: {
      type: [Number, String],
      validator(val) {
        if (typeof val === 'string') {
          return ['large', 'medium', 'small'].includes(val);
        }
        return typeof val === 'number';
      }
    },
    shape: {
      type: String,
      default: 'circle',
      validator(val) {
        return ['circle', 'square'].includes(val);
      }
    },
    icon: String,
    src: String,
    alt: String,
    srcSet: String,
    error: Function,
    fit: {
      type: String,
      default: 'cover'
    }
  },
复制代码

各属性详情描述以下:github

参数 说明 类型 可选值 默认值
icon 设置头像的图标类型,参考 Icon 组件 string
size 设置头像的大小 number/string number / large / medium / small large
shape 设置头像的形状 string circle / square circle
src 图片头像的资源地址 string
srcSet 以逗号分隔的一个或多个字符串列表代表一系列用户代理使用的可能的图像 string
alt 描述图像的替换文本 string
fit 当展现类型为图片的时候,设置图片如何适应容器框 string fill / contain / cover /none / scale-down cover

icon

由于组件图标内部使用 <i class='iconNanme' /> 形式,因此属性 icon 格式应为 el-icon-[iconName]web

size

当组件未设置 size属性时,组件prop size 值为 undefined, 由于组件样式 el-avatar 定义了 widthheightline-height 等属性, 跟样式 el-avatar--large 中的定义相同,因此等效于 size 设置了 large编程

size字符串值不匹配下列字符串中的一个 large / medium / small,此时 size值为传入字符串内容,添加一个未定义的class,组件大小为默认。gulp

<el-avatar size="x-large"> </el-avatar>
//生成DOM  el-avatar--x-large 未在样式中定义,组件大小使用 .el-avatar 规则
<span class="el-avatar el-avatar--x-large el-avatar--circle"></span>
复制代码

shape

shape设置了默认值 circle, 若字符串值不匹配下列字符串中的一个 circle / square,此时 shape值为传入字符串内容,添加一个未定义的class,组件大小为默认。

<el-avatar shape="dot"></el-avatar>
//生成DOM  el-avatar--dot 未在样式中定义,组件样式使用 .el-avatar 规则
<span class="el-avatar el-avatar--dot"></span>
复制代码

srcSet

<Image> 元素的 srcset属性的值是一个字符串,用来定义一个或多个图像候选地址,以 ,分割,每一个候选地址将在特定条件下得以使用。候选地址包含图片 URL 和一个可选的宽度描述符和像素密度描述符,该候选地址用来在特定条件下替代原始地址成为src 的属性。

fit

只有使用图片展现类型时, 使用 fit 属性定义才会生效,会在 <Image> 元素中添加内联样式 {object-fit:fill},各属性值描述:

  • contain 被替换的内容将被缩放,以在填充元素的内容框时保持其宽高比。 整个对象在填充盒子的同时保留其长宽比,所以若是宽高比与框的宽高比不匹配,该对象将被添加“黑边”。
  • cover 被替换的内容在保持其宽高比的同时填充元素的整个内容框。若是对象的宽高比与内容框不相匹配,该对象将被剪裁以适应内容框。
  • fill 被替换的内容正好填充元素的内容框。整个对象将彻底填充此框。若是对象的宽高比与内容框不相匹配,那么该对象将被拉伸以适应内容框。
  • none 被替换的内容将保持其原有的尺寸。
  • scale-down 内容的尺寸与 none 或 contain 中的一个相同,取决于它们两个之间谁获得的对象尺寸会更小一些。

计算属性

计算属性 avatarClass 根据大小、图标、形状等设置动态添加样式,生成组件的样式列表。

// 组件动态 class
avatarClass() {
  const { size, icon, shape } = this;
  // 默认组件容器样式
  let classList = ['el-avatar'];
  // size 指定large / medium / small
  if (size && typeof size === 'string') {
    classList.push(`el-avatar--${size}`);
  }
  // icon 设置
  if (icon) {
    classList.push('el-avatar--icon');
  }
  // shape 默认 circle
  if (shape) {
    classList.push(`el-avatar--${shape}`);
  }
  // 拼接
  return classList.join(' ');
}
复制代码

由代码可知上文提到 size shape 属性值只要定义了,就会生成对应class,没有进行无效检查。

error 事件(应为属性)

当组件使用图片类型时,若图像加载过程当中发生错误时被触发 onError 事件, <img onError={this.handleError} /> , 即调用 handleError 方法。

// 数据属性
  data() {
    return {
      isImageExist: true  // 图片是否存在
    };
  },  
  methods: {
    // 加载失败处理方法
    handleError() {
      const { error } = this;
      const errorFlag = error ? error() : undefined; // 错误标记
      // 错误标记不为 false 时,断定图片不存在
      if (errorFlag !== false) {
        this.isImageExist = false;
      } 
    },
  }
复制代码

根据 handleError() 方法定义可知,若 error 属性(类型为 Function)未定义, errorFlag 值将始终为 undefined,则更新 isImageExistfalse,隐藏 <Image> 元素。展现该组件下包裹的子组件--展现一张备用图片。

<el-avatar :size="60" src="https://empty" >
  <img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png" />
</el-avatar>
复制代码

官方提供 图片加载失败的 fallback 行为 示例代码。事件 error 没有任何效果,将@error="errorHandler" 改为 :error="errorHandler",而后修改 errorHandler() 方法的返回值为 false;

<template>
  <div class="demo-type">
--    <el-avatar :size="60" src="https://empty" @error="errorHandler">
++    <el-avatar :size="60" src="https://empty" :error="errorHandler">
      <img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"/>
    </el-avatar>
  </div>
</template>
<script> export default { methods: { errorHandler() { -- return true ++ return false } } } </script>
复制代码

此时 error 等于 errorHandlererrorFlag 值为 false, isImageExist 还是 true , <Image>显示裂图效果,效果对好比下。

image.png

渲染函数 & JSX

组件使用渲染函数来渲染虚拟DOM。由于使用 JSX语法,代码更加深刻底层,能够更好的控制交互细节。

vue 中 h 做为 createElement 的别名。在含有 JSX 的任何方法自动注入 const h = this.$createElementrender() 等效于 render(h)

组件将建立一个 <span> 元素VNode,动态添加 class,设置组件大小。调用方法 renderAvatar() 生成图标、图片或者字符等不一样类型元素VNode。

// 渲染虚拟DOM
render() {
    const { avatarClass, size } = this;
    // size 为数值时 设置内联样式 覆盖默认尺寸
    const sizeStyle = typeof size === 'number' ? {
      height: `${size}px`,
      width: `${size}px`,
      lineHeight: `${size}px`
    } : {};
    
    return (
      <span class={ avatarClass } style={ sizeStyle }> { // 生成图标、图片或者字符等不一样类型元素VNode this.renderAvatar() } </span>
    );
}
复制代码

renderAvatar() 按照 图片类型<Image>、图标、插槽(自定义头像展现内容)等类型前后顺序进行判断,只会建立一种类型的元素内容。

// 生成图标、图片或者字符等不一样类型元素VNode
renderAvatar() {
  const { icon, src, alt, isImageExist, srcSet, fit } = this;
  // 优先建立图片类型
  if (isImageExist && src) {
    return <img src={src} onError={this.handleError} alt={alt} srcSet={srcSet} style={{ 'object-fit': fit }}/>;
  }
  // 其次图标类型
  if (icon) {
    return (<i class={icon} />);
  }
  // 最后插槽自定义
  return this.$slots.default;
}
复制代码

0x02 组件样式

src/avatar.scss

组件样式源码 packages\theme-chalk\src\avatar.scss 使用混合指令 bm 嵌套生成组件样式。

// 生成 .el-avatar
@include b(avatar) {
  // ... 
  
  // 生成 .el-avatar > img
  >img {
    // ...
  }
  
  // 生成 .el-avatar--circle
  @include m(circle) {
    // ...
  }
  
  // 生成 .el-avatar--square
  @include m(square) {
    // ...
  }
  
  // 生成 .el-avatar--icon
  @include m(icon) {
    // ...
  }
  
  // 生成 .el-avatar--large
  @include m(large) {
   // ...
  }
  
  // 生成 .el-avatar--medium
  @include m(medium) {
    // ...
  }
  
  // 生成 .el-avatar--small
  @include m(small) {
    // ...
  }
}
复制代码

上文中讲到 size属性默认值large时,提到el-avatar 定义了 width、 height、 line-height 等属性, 跟样式 el-avatar--large 中的定义相同。

// .el-avatar
@include b(avatar) {
    // ...
    width: $--avatar-large-size;
    height: $--avatar-large-size;
    line-height: $--avatar-large-size;
    font-size: $--avatar-text-font-size;
}
// .el-avatar--large
@include m(large) {
    width: $--avatar-large-size;
    height: $--avatar-large-size;
    line-height: $--avatar-large-size;
}
复制代码

lib/avatar.scss

前文可知使用 gulpfile.js编译 scss 文件转换为CSS,通过浏览器兼容、格式压缩,最后生成 packages\theme-chalk\lib\avatar.scss,内容格式以下。

.el-avatar {
  // ...
  width: 40px;
  height: 40px;
  line-height: 40px;
  font-size: 14px;
}
.el-avatar > img {
   // ...
}
.el-avatar--circle {
   // ...
}
.el-avatar--square {
   // ...
}
.el-avatar--icon {
  // ...
}
.el-avatar--large {
  width: 40px;
  height: 40px;
  line-height: 40px;
}
.el-avatar--medium {
  // ...
}
.el-avatar--small {
  // ...
}
复制代码

0x03 📚参考

“渲染函数 & JSX”,vuejs.org
“srcset”,MDN
“object-fit”,MDN
“Babel Preset JSX”
“vuejs/babel-plugin-transform-vue-jsx”

0x04 关注专栏

此文章已收录到专栏中 👇,能够直接关注。