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 關注專欄

此文章已收錄到專欄中 👇,能夠直接關注。

相關文章
相關標籤/搜索