<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【總結(jié)】1181- 從 0 到 1 上手 Web Components 業(yè)務(wù)組件庫(kù)開(kāi)發(fā)

          共 34795字,需瀏覽 70分鐘

           ·

          2021-12-24 21:31

          組件化是前端發(fā)展的一個(gè)重要方向,它一方面提高開(kāi)發(fā)效率,另一方面降低維護(hù)成本。主流的 Vue.js、React 及其延伸的 Ant Design、uniapp、Taro 等都是組件框架。Web Components 是一組 Web 原生 API 的總稱,允許我們創(chuàng)建可重用的自定義組件,并在我們 Web 應(yīng)用中像使用原生 HTML 標(biāo)簽一樣使用。目前已經(jīng)很多前端框架/庫(kù)支持 Web Components。

          本文將帶大家回顧 Web Components 核心 API,并從 0 到 1 實(shí)現(xiàn)一個(gè)基于 Web Components API 開(kāi)發(fā)的業(yè)務(wù)組件庫(kù)。

          最終效果:https://blog.pingan8787.com/exe-components/demo.html倉(cāng)庫(kù)地址:https://github.com/pingan8787/Learn-Web-Components

          一、回顧 Web Components

          在前端發(fā)展歷史中,從剛開(kāi)始重復(fù)業(yè)務(wù)到處復(fù)制相同代碼,到 Web Components 的出現(xiàn),我們使用原生 HTML 標(biāo)簽的自定義組件,復(fù)用組件代碼,提高開(kāi)發(fā)效率。通過(guò) Web Components 創(chuàng)建的組件,幾乎可以使用在任何前端框架中。

          1. 核心 API 回顧

          Web Components 由 3 個(gè)核心 API 組成:

          • 「Custom elements(自定義元素)」:用來(lái)讓我們定義「自定義元素」及其「行為」,對(duì)外提供組件的標(biāo)簽;
          • 「Shadow DOM(影子 DOM)」:用來(lái)封裝組件內(nèi)部的結(jié)構(gòu),避免與外部沖突;
          • 「HTML templates(HTML 模版)」:包括 <template><slot> 元素,讓我們可以定義各種組件的 HTML 模版,然后被復(fù)用到其他地方,使用過(guò) Vue/React 等框架的同學(xué)應(yīng)該會(huì)很熟悉。

          另外,還有 HTML imports,但目前已廢棄,所以不具體介紹,其作用是用來(lái)控制組件的依賴加載。

          2. 入門示例

          接下來(lái)通過(guò)下面簡(jiǎn)單示例快速了解一下「如何創(chuàng)建一個(gè)簡(jiǎn)單 Web Components 組件」。

          • 使用組件
          <!DOCTYPE html>
          <html lang="en">
          <head>
              <script src="./index.js" defer></script>
          </head>
          <body>
              <h1>custom-element-start</h1>
              <custom-element-start></custom-element-start>
          </body>
          </html>
          • 定義組件
          /**
           * 使用 CustomElementRegistry.define() 方法用來(lái)注冊(cè)一個(gè) custom element
           * 參數(shù)如下:
           * - 元素名稱,符合 DOMString 規(guī)范,名稱不能是單個(gè)單詞,且必須用短橫線隔開(kāi)
           * - 元素行為,必須是一個(gè)類
           * - 繼承元素,可選配置,一個(gè)包含 extends 屬性的配置對(duì)象,指定創(chuàng)建的元素繼承自哪個(gè)內(nèi)置元素,可以繼承任何內(nèi)置元素。
           */


          class CustomElementStart extends HTMLElement {
              constructor(){
                  super();
                  this.render();
              }
              render(){
                  const shadow = this.attachShadow({mode'open'});
                  const text = document.createElement("span");
                  text.textContent = 'Hi Custom Element!';
                  text.style = 'color: red';
                  shadow.append(text);
              }
          }

          customElements.define('custom-element-start', CustomElementStart)

          上面代碼主要做 3 件事:

          1. 實(shí)現(xiàn)組件類

          通過(guò)實(shí)現(xiàn) CustomElementStart 類來(lái)定義組件。

          1. 定義組件

          將組件的標(biāo)簽和組件類作為參數(shù),通過(guò) customElements.define 方法定義組件。

          1. 使用組件

          導(dǎo)入組件后,跟使用普通 HTML 標(biāo)簽一樣直接使用自定義組件 <custom-element-start></custom-element-start>

          隨后瀏覽器訪問(wèn) index.html 可以看到下面內(nèi)容:

          3. 兼容性介紹

          在 MDN | Web Components 章節(jié)中介紹了其兼容性情況:

          • Firefox(版本63)、Chrome和Opera都默認(rèn)支持Web組件。
          • Safari支持許多web組件特性,但比上述瀏覽器少。
          • Edge正在開(kāi)發(fā)一個(gè)實(shí)現(xiàn)。

          關(guān)于兼容性,可以看下圖:圖片來(lái)源:https://www.webcomponents.org/

          這個(gè)網(wǎng)站里面,有很多關(guān)于 Web Components 的優(yōu)秀項(xiàng)目可以學(xué)習(xí)。

          4. 小結(jié)

          這節(jié)主要通過(guò)一個(gè)簡(jiǎn)單示例,簡(jiǎn)單回顧基礎(chǔ)知識(shí),詳細(xì)可以閱讀文檔:

          • 使用 custom elements
          • 使用 shadow DOM
          • 使用 templates and slots
          image.png

          二、EXE-Components 組件庫(kù)分析設(shè)計(jì)

          1. 背景介紹

          假設(shè)我們需要實(shí)現(xiàn)一個(gè) EXE-Components 組件庫(kù),該組件庫(kù)的組件分 2 大類:

          1. components 類型

          「通用簡(jiǎn)單組件」為主,如exe-avatar頭像組件、 exe-button按鈕組件等;

          1. modules 類型

          「復(fù)雜、組合組件」為主,如exe-user-avatar用戶頭像組件(含用戶信息)、exe-attachement-list附件列表組件等等。

          詳細(xì)可以看下圖:

          接下來(lái)我們會(huì)基于上圖進(jìn)行 EXE-Components 組件庫(kù)設(shè)計(jì)和開(kāi)發(fā)。

          2. 組件庫(kù)設(shè)計(jì)

          在設(shè)計(jì)組件庫(kù)的時(shí)候,主要需要考慮以下幾點(diǎn):

          1. 組件命名、參數(shù)命名等規(guī)范,方便組件后續(xù)維護(hù);
          2. 組件參數(shù)定義;
          3. 組件樣式隔離;

          當(dāng)然,這幾個(gè)是最基礎(chǔ)需要考慮的點(diǎn),隨著實(shí)際業(yè)務(wù)的復(fù)雜,還需要考慮更多,比如:工程化相關(guān)、組件解耦、組件主題等等。

          針對(duì)前面提到這 3 點(diǎn),這邊約定幾個(gè)命名規(guī)范:

          1. 組件名稱以 exe-功能名稱 進(jìn)行命名,如 exe-avatar表示頭像組件;
          2. 屬性參數(shù)名稱以 e-參數(shù)名稱 進(jìn)行命名,如 e-src 表示 src 地址屬性;
          3. 事件參數(shù)名稱以 on-事件類型 進(jìn)行命名,如 on-click表示點(diǎn)擊事件;

          3. 組件庫(kù)組件設(shè)計(jì)

          這邊我們主要設(shè)計(jì) exe-avatar 、exe-buttonexe-user-avatar三個(gè)組件,前兩個(gè)為簡(jiǎn)單組件,后一個(gè)為復(fù)雜組件,其內(nèi)部使用了前兩個(gè)組件進(jìn)行組合。這邊先定義這三個(gè)組件支持的屬性:

          這邊屬性命名看著會(huì)比較復(fù)雜,大家可以按照自己和團(tuán)隊(duì)的習(xí)慣進(jìn)行命名。

          這樣我們思路就清晰很多,實(shí)現(xiàn)對(duì)應(yīng)組件即可。

          三、EXE-Components 組件庫(kù)準(zhǔn)備工作

          本文示例最終將對(duì)實(shí)現(xiàn)的組件進(jìn)行「組合使用」,實(shí)現(xiàn)下面「「用戶列表」」效果:體驗(yàn)地址:https://blog.pingan8787.com/exe-components/demo.html

          1. 統(tǒng)一開(kāi)發(fā)規(guī)范

          首先我們先統(tǒng)一開(kāi)發(fā)規(guī)范,包括:

          1. 目錄規(guī)范
          image.png
          1. 定義組件規(guī)范
          image.png
          1. 組件開(kāi)發(fā)模版

          組件開(kāi)發(fā)模版分 index.js「組件入口文件」template.js 「組件 HTML 模版文件」

          // index.js 模版
          const defaultConfig = {
              // 組件默認(rèn)配置
          }

          const Selector = "exe-avatar"// 組件標(biāo)簽名

          export default class EXEAvatar extends HTMLElement {
              shadowRoot = null;
              config = defaultConfig;

              constructor(){
                  super();
                  this.render(); // 統(tǒng)一處理組件初始化邏輯
              }

              render() {
                  this.shadowRoot = this.attachShadow({mode'closed'});
                  this.shadowRoot.innerHTML = renderTemplate(this.config);
              }
          }

          // 定義組件
          if (!customElements.get(Selector)) {
              customElements.define(Selector, EXEAvatar)
          }
          // template.js 模版

          export default config => {
              // 統(tǒng)一讀取配置
              const { avatarWidth, avatarRadius, avatarSrc } = config;
              return `
                  <style>
                      /* CSS 內(nèi)容 */
                  </style>
                  <div class="exe-avatar">
                      /* HTML 內(nèi)容 */
                  </div>
              `

          }

          2. 開(kāi)發(fā)環(huán)境搭建和工程化處理

          為了方便使用 EXE-Components 組件庫(kù),更接近實(shí)際組件庫(kù)的使用,我們需要將組件庫(kù)打包成一個(gè) UMD 類型的 js 文件。這邊我們使用 rollup 進(jìn)行構(gòu)建,最終打包成 exe-components.js 的文件,使用方式如下:

          <script src="./exe-components.js"></script>

          接下來(lái)通過(guò) npm init -y生成 package.json文件,然后全局安裝 rollup 和 http-server(用來(lái)啟動(dòng)本地服務(wù)器,方便調(diào)試):

          npm init -y
          npm install --global rollup http-server

          然后在 package.jsonscript 下添加 "dev""build"腳本:

          {
           // ...
            "scripts": {
              "dev""http-server -c-1 -p 1400",
              "build""rollup index.js --file exe-components.js --format iife"
            },
          }

          其中:

          • "dev" 命令:通過(guò) http-server 啟動(dòng)靜態(tài)服務(wù)器,作為開(kāi)發(fā)環(huán)境使用。添加 -c-1 參數(shù)用來(lái)禁用緩存,避免刷新頁(yè)面還會(huì)有緩存,詳細(xì)可以看 http-server 文檔;
          • "build"命令:將 index.js 作為 rollup 打包的入口文件,輸出 exe-components.js 文件,并且是 iife 類型的文件。

          這樣就完成簡(jiǎn)單的本地開(kāi)發(fā)和組件庫(kù)構(gòu)建的工程化配置,接下來(lái)就可以進(jìn)行開(kāi)發(fā)了。

          四、EXE-Components 組件庫(kù)開(kāi)發(fā)

          1. 組件庫(kù)入口文件配置

          前面 package.json 文件中配置的 "build" 命令,會(huì)使用根目錄下 index.js 作為入口文件,并且為了方便 components 通用基礎(chǔ)組件和 modules 通用復(fù)雜組件的引入,我們創(chuàng)建 3 個(gè) index.js,創(chuàng)建后目錄結(jié)構(gòu)如下:三個(gè)入口文件內(nèi)容分別如下:

          // EXE-Components/index.js
          import './components/index.js';
          import './modules/index.js';

          // EXE-Components/components/index.js
          import './exe-avatar/index.js';
          import './exe-button/index.js';

          // EXE-Components/modules/index.js
          import './exe-attachment-list/index.js.js';
          import './exe-comment-footer/index.js.js';
          import './exe-post-list/index.js.js';
          import './exe-user-avatar/index.js';

          2. 開(kāi)發(fā) exe-avatar 組件 index.js 文件

          通過(guò)前面的分析,我們可以知道 exe-avatar組件需要支持參數(shù):

          • e-avatar-src:頭像圖片地址,例如:./testAssets/images/avatar-1.png
          • e-avatar-width:頭像寬度,默認(rèn)和高度一致,例如:52px
          • e-button-radius:頭像圓角,例如:22px,默認(rèn):50%
          • on-avatar-click:頭像點(diǎn)擊事件,默認(rèn)無(wú)

          接著按照之前的模版,開(kāi)發(fā)入口文件 index.js

          // EXE-Components/components/exe-avatar/index.js
          import renderTemplate from './template.js';
          import { Shared, Utils } from '../../utils/index.js';

          const { getAttributes } = Shared;
          const { isStr, runFun } = Utils;

          const defaultConfig = {
              avatarWidth"40px",
              avatarRadius"50%",
              avatarSrc"./assets/images/default_avatar.png",
              onAvatarClicknull,
          }

          const Selector = "exe-avatar";

          export default class EXEAvatar extends HTMLElement {
              shadowRoot = null;
              config = defaultConfig;

              constructor(){
                  super();
                  this.render();
              }

              render() {
                  this.shadowRoot = this.attachShadow({mode'closed'});
                  this.shadowRoot.innerHTML = renderTemplate(this.config);// 生成 HTML 模版內(nèi)容
              }

            // 生命周期:當(dāng) custom element首次被插入文檔DOM時(shí),被調(diào)用。
              connectedCallback() {
                  this.updateStyle();
                  this.initEventListen();
              }

              updateStyle() {
                  this.config = {...defaultConfig, ...getAttributes(this)};
                  this.shadowRoot.innerHTML = renderTemplate(this.config); // 生成 HTML 模版內(nèi)容
              }

              initEventListen() {
                  const { onAvatarClick } = this.config;
                  if(isStr(onAvatarClick)){ // 判斷是否為字符串
                      this.addEventListener('click', e => runFun(e, onAvatarClick));
                  }
              }
          }

          if (!customElements.get(Selector)) {
              customElements.define(Selector, EXEAvatar)
          }

          其中有幾個(gè)方法是抽取出來(lái)的公用方法,大概介紹下其作用,具體可以看源碼:

          • renderTemplate 方法

          來(lái)自 template.js 暴露的方法,傳入配置 config,來(lái)生成 HTML 模版。

          • getAttributes 方法

          傳入一個(gè) HTMLElement 元素,返回該元素上所有屬性鍵值對(duì),其中會(huì)對(duì) e-on- 開(kāi)頭的屬性,分別處理成普通屬性和事件屬性,示例如下:

          // input
          <exe-avatar
              e-avatar-src="./testAssets/images/avatar-1.png"
              e-avatar-width="52px"
              e-avatar-radius="22px"
              on-avatar-click="avatarClick()"
          ></exe-avatar>
            
          /
          / output
          {
            avatarSrc: "./
          testAssets/images/avatar-1.png",
            avatarWidth: "
          52px",
            avatarRadius: "
          22px",
            avatarClick: "
          avatarClick()"
          }
          • runFun方法

          由于通過(guò)屬性傳遞進(jìn)來(lái)的方法,是個(gè)字符串,所以進(jìn)行封裝,傳入 event 和事件名稱作為參數(shù),調(diào)用該方法,示例和上一步一樣,會(huì)執(zhí)行 avatarClick() 方法。

          另外,Web Components 生命周期可以詳細(xì)看文檔:使用生命周期回調(diào)函數(shù)。

          3. 開(kāi)發(fā) exe-avatar 組件 template.js 文件

          該文件暴露一個(gè)方法,返回組件 HTML 模版:

          // EXE-Components/components/exe-avatar/template.js
          export default config => {
            const { avatarWidth, avatarRadius, avatarSrc } = config;
            return `
              <style>
                .exe-avatar {
                  width: ${avatarWidth};
                  height: ${avatarWidth};
                  display: inline-block;
                  cursor: pointer;
                }
                .exe-avatar .img {
                  width: 100%;
                  height: 100%;
                  border-radius: ${avatarRadius};
                  border: 1px solid #efe7e7;
                }
              </style>
              <div class="exe-avatar">
                <img class="img" src="${avatarSrc}" />
              </div>
            `

          }

          最終實(shí)現(xiàn)效果如下:

          開(kāi)發(fā)完第一個(gè)組件,我們可以簡(jiǎn)單總結(jié)一下創(chuàng)建和使用組件的步驟:

          4. 開(kāi)發(fā) exe-button 組件

          按照前面 exe-avatar組件開(kāi)發(fā)思路,可以很快實(shí)現(xiàn) exe-button 組件。需要支持下面參數(shù):

          • e-button-radius:按鈕圓角,例如:8px
          • e-button-type:按鈕類型,例如:default, primary, text, dashed
          • e-button-text:按鈕文本,默認(rèn):打開(kāi)
          • on-button-click:按鈕點(diǎn)擊事件,默認(rèn)無(wú)
          // EXE-Components/components/exe-button/index.js
          import renderTemplate from './template.js';
          import { Shared, Utils } from '../../utils/index.js';

          const { getAttributes } = Shared;
          const { isStr, runFun } = Utils;
          const defaultConfig = {
              buttonRadius"6px",
              buttonPrimary"default",
              buttonText"打開(kāi)",
              disableButtonfalse,
              onButtonClicknull,
          }

          const Selector = "exe-button";

          export default class EXEButton extends HTMLElement {
              // 指定觀察到的屬性變化,attributeChangedCallback 會(huì)起作用
              static get observedAttributes() { 
                  return ['e-button-type','e-button-text''buttonType''buttonText']
              }

              shadowRoot = null;
              config = defaultConfig;

              constructor(){
                  super();
                  this.render();
              }

              render() {
                  this.shadowRoot = this.attachShadow({mode'closed'});
              }

              connectedCallback() {
                  this.updateStyle();
                  this.initEventListen();
              }

              attributeChangedCallback (name, oldValue, newValue) {
                  // console.log('屬性變化', name)
              }

              updateStyle() {
                  this.config = {...defaultConfig, ...getAttributes(this)};
                  this.shadowRoot.innerHTML = renderTemplate(this.config);
              }

              initEventListen() {
                  const { onButtonClick } = this.config;
                  if(isStr(onButtonClick)){
                      const canClick = !this.disabled && !this.loading
                      this.addEventListener('click', e => canClick && runFun(e, onButtonClick));
                  }
              }

              get disabled () {
                  return this.getAttribute('disabled') !== null;
              }

              get type () {
                  return this.getAttribute('type') !== null;
              }

              get loading () {
                  return this.getAttribute('loading') !== null;
              }
          }

          if (!customElements.get(Selector)) {
              customElements.define(Selector, EXEButton)
          }

          模版定義如下:

          // EXE-Components/components/exe-button/tempalte.js
          // 按鈕邊框類型
          const borderStyle = { solid'solid'dashed'dashed' };

          // 按鈕類型
          const buttonTypeMap = {
              default: { textColor'#222'bgColor'#FFF'borderColor'#222'},
              primary: { textColor'#FFF'bgColor'#5FCE79'borderColor'#5FCE79'},
              text: { textColor'#222'bgColor'#FFF'borderColor'#FFF'},
          }

          export default config => {
              const { buttonRadius, buttonText, buttonType } = config;

              const borderStyleCSS = buttonType 
                  && borderStyle[buttonType] 
                  ? borderStyle[buttonType] 
                  : borderStyle['solid'];

              const backgroundCSS = buttonType 
                  && buttonTypeMap[buttonType] 
                  ? buttonTypeMap[buttonType] 
                  : buttonTypeMap['default'];

              return `
                  <style>
                      .exe-button {
                          border: 1px ${borderStyleCSS} ${backgroundCSS.borderColor};
                          color: ${backgroundCSS.textColor};
                          background-color: ${backgroundCSS.bgColor};
                          font-size: 12px;
                          text-align: center;
                          padding: 4px 10px;
                          border-radius: ${buttonRadius};
                          cursor: pointer;
                          display: inline-block;
                          height: 28px;
                      }
                      :host([disabled]) .exe-button{ 
                          cursor: not-allowed; 
                          pointer-events: all; 
                          border: 1px solid #D6D6D6;
                          color: #ABABAB;
                          background-color: #EEE;
                      }
                      :host([loading]) .exe-button{ 
                          cursor: not-allowed; 
                          pointer-events: all; 
                          border: 1px solid #D6D6D6;
                          color: #ABABAB;
                          background-color: #F9F9F9;
                      }
                  </style>
                  <button class="exe-button">${buttonText}</button>
              `

          }

          最終效果如下:

          5. 開(kāi)發(fā) exe-user-avatar 組件

          該組件是將前面 exe-avatar 組件和 exe-button 組件進(jìn)行組合,不僅需要支持「點(diǎn)擊事件」,還需要支持「插槽 slot 功能」。由于是做組合,所以開(kāi)發(fā)起來(lái)比較簡(jiǎn)單~先看看入口文件:

          // EXE-Components/modules/exe-user-avatar/index.js

          import renderTemplate from './template.js';
          import { Shared, Utils } from '../../utils/index.js';

          const { getAttributes } = Shared;
          const { isStr, runFun } = Utils;

          const defaultConfig = {
              userName"",
              subName"",
              disableButtonfalse,
              onAvatarClicknull,
              onButtonClicknull,
          }

          export default class EXEUserAvatar extends HTMLElement {
              shadowRoot = null;
              config = defaultConfig;

              constructor() {
                  super();
                  this.render();
              }

              render() {
                  this.shadowRoot = this.attachShadow({mode'open'});
              }

              connectedCallback() {
                  this.updateStyle();
                  this.initEventListen();
              }

              initEventListen() {
                  const { onAvatarClick } = this.config;
                  if(isStr(onAvatarClick)){
                      this.addEventListener('click', e => runFun(e, onAvatarClick));
                  }
              }

              updateStyle() {
                  this.config = {...defaultConfig, ...getAttributes(this)};
                  this.shadowRoot.innerHTML = renderTemplate(this.config);
              }
          }

          if (!customElements.get('exe-user-avatar')) {
              customElements.define('exe-user-avatar', EXEUserAvatar)
          }

          主要內(nèi)容在 template.js 中:

          // EXE-Components/modules/exe-user-avatar/template.js

          import { Shared } from '../../utils/index.js';

          const { renderAttrStr } = Shared;

          export default config => {
              const { 
                  userName, avatarWidth, avatarRadius, buttonRadius, 
                  avatarSrc, buttonType = 'primary', subName, buttonText, disableButton,
                  onAvatarClick, onButtonClick
              } = config;
              return `
                  <style>
                      :host{
                          color: "green";
                          font-size: "30px";
                      }
                      .exe-user-avatar {
                          display: flex;
                          margin: 4px 0;
                      }
                      .exe-user-avatar-text {
                          font-size: 14px;
                          flex: 1;
                      }
                      .exe-user-avatar-text .text {
                          color: #666;
                      }
                      .exe-user-avatar-text .text span {
                          display: -webkit-box;
                          -webkit-box-orient: vertical;
                          -webkit-line-clamp: 1;
                          overflow: hidden;
                      }
                      exe-avatar {
                          margin-right: 12px;
                          width: ${avatarWidth};
                      }
                      exe-button {
                          width: 60px;
                          display: flex;
                          justify-content: end;
                      }
                  </style>
                  <div class="exe-user-avatar">
                      <exe-avatar
                          ${renderAttrStr({
                              'e-avatar-width': avatarWidth,
                              'e-avatar-radius': avatarRadius,
                              'e-avatar-src': avatarSrc,
                          }
          )}
                      ></exe-avatar>
                      <div class="exe-user-avatar-text">
                          <div class="name">
                              <span class="name-text">${userName}</span>
                              <span class="user-attach">
                                  <slot name="name-slot"></slot>
                              </span>
                          </div>
                          <div class="text">
                              <span class="name">${subName}<slot name="sub-name-slot"></slot></span>
                          </div>
                      </div>
                      ${
                          !disableButton && 
                          `<exe-button
                              ${renderAttrStr({
                                  'e-button-radius' : buttonRadius,
                                  'e-button-type' : buttonType,
                                  'e-button-text' : buttonText,
                                  'on-avatar-click' : onAvatarClick,
                                  'on-button-click' : onButtonClick,
                              }
          )}
                          ></exe-button>`

                      }


                  </div>
              `

          }

          其中 renderAttrStr 方法接收一個(gè)屬性對(duì)象,返回其鍵值對(duì)字符串:

          // input
          {
            'e-avatar-width'100,
            'e-avatar-radius'50,
            'e-avatar-src''./testAssets/images/avatar-1.png',
          }
            
          // output
          "e-avatar-width='100' e-avatar-radius='50' e-avatar-src='./testAssets/images/avatar-1.png' "

          最終效果如下:

          6. 實(shí)現(xiàn)一個(gè)用戶列表業(yè)務(wù)

          接下來(lái)我們通過(guò)一個(gè)實(shí)際業(yè)務(wù),來(lái)看看我們組件的效果:

          其實(shí)實(shí)現(xiàn)也很簡(jiǎn)單,根據(jù)給定數(shù)據(jù),然后循環(huán)使用組件即可,假設(shè)有以下用戶數(shù)據(jù):

          const users = [
            {"name":"前端早早聊","desc":"幫 5000 個(gè)前端先跑 @ 前端早早聊","level":6,"avatar":"qdzzl.jpg","home":"https://juejin.cn/user/712139234347565"}
            {"name":"來(lái)自拉夫德魯?shù)拇a農(nóng)","desc":"誰(shuí)都不救我,誰(shuí)都救不了我,就像我救不了任何人一樣","level":2,"avatar":"lzlfdldmn.jpg","home":"https://juejin.cn/user/994371074524862"}
            {"name":"黑色的楓","desc":"永遠(yuǎn)懷著一顆學(xué)徒的心。。。","level":3,"avatar":"hsdf.jpg","home":"https://juejin.cn/user/2365804756348103"}
            {"name":"captain_p","desc":"目的地很美好,路上的風(fēng)景也很好。今天增長(zhǎng)見(jiàn)識(shí)了嗎","level":2,"avatar":"cap.jpg","home":"https://juejin.cn/user/2532902235026439"}
            {"name":"CUGGZ","desc":"文章聯(lián)系微信授權(quán)轉(zhuǎn)載。微信:CUG-GZ,添加好友一起學(xué)習(xí)~","level":5,"avatar":"cuggz.jpg","home":"https://juejin.cn/user/3544481220801815"}
            {"name":"政采云前端團(tuán)隊(duì)","desc":"政采云前端 ZooTeam 團(tuán)隊(duì),不摻水的原創(chuàng)。 團(tuán)隊(duì)站點(diǎn):https://zoo.team","level":6,"avatar":"zcy.jpg","home":"https://juejin.cn/user/3456520257288974"}
          ]

          我們就可以通過(guò)簡(jiǎn)單 for 循環(huán)拼接 HTML 片段,然后添加到頁(yè)面某個(gè)元素中:

          // 測(cè)試生成用戶列表模版
          const usersTemp = () => {
              let temp = '', code = '';
              users.forEach(item => {
                  const {name, desc, level, avatar, home} = item;
                  temp += 
          `
          <exe-user-avatar 
              e-user-name="${name}"
              e-sub-name="${desc}"
              e-avatar-src="./testAssets/images/users/${avatar}"
              e-avatar-width="36px"
              e-button-type="primary"
              e-button-text="關(guān)注"
              on-avatar-click="toUserHome('${home}')"
              on-button-click="toUserFollow('${name}')"
          >
          ${
              level >= 0 && `<span slot="name-slot">
                  <span class="medal-item">(Lv${level})</span>
              </span>`
          }

          </exe-user-avatar>
          `

          })
              return temp;
          }

          document.querySelector('#app').innerHTML = usersTemp;

          到這邊我們就實(shí)現(xiàn)了一個(gè)用戶列表的業(yè)務(wù),當(dāng)然實(shí)際業(yè)務(wù)可能會(huì)更加復(fù)雜,需要再優(yōu)化。

          五、總結(jié)

          本文首先簡(jiǎn)單回顧 Web Components 核心 API,然后對(duì)組件庫(kù)需求進(jìn)行分析設(shè)計(jì),再進(jìn)行環(huán)境搭建和開(kāi)發(fā),內(nèi)容比較多,可能沒(méi)有每一點(diǎn)都講到,還請(qǐng)大家看看我倉(cāng)庫(kù)的源碼,有什么問(wèn)題歡迎和我討論。寫本文的幾個(gè)核心目的:

          1. 當(dāng)我們接到一個(gè)新任務(wù)的時(shí)候,需要從分析設(shè)計(jì)開(kāi)始,再到開(kāi)發(fā),而不是盲目一上來(lái)就開(kāi)始開(kāi)發(fā);
          2. 帶大家一起看看如何用 Web Components 開(kāi)發(fā)簡(jiǎn)單的業(yè)務(wù)組件庫(kù);
          3. 體驗(yàn)一下 Web Components 開(kāi)發(fā)組件庫(kù)有什么缺點(diǎn)(就是要寫的東西太多了)。

          最后看完本文,大家是否覺(jué)得用  Web Components 開(kāi)發(fā)組件庫(kù),實(shí)在有點(diǎn)復(fù)雜?要寫的太多了。沒(méi)關(guān)系,下一篇我將帶大家一起使用 Stencil 框架開(kāi)發(fā) Web Components 標(biāo)準(zhǔn)的組件庫(kù),畢竟整個(gè) ionic 已經(jīng)是使用 Stencil 重構(gòu),Web Components 大勢(shì)所趨~!

          拓展閱讀

          • WEBCOMPONENTS.ORG Discuss & share web components
          • Web Components as Technology
          • Stenciljs - Build. Customize. Distribute. Adopt.

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計(jì)模式 重溫系列(9篇全)
          4. 正則 / 框架 / 算法等 重溫系列(16篇全)
          5. Webpack4 入門(上)|| Webpack4 入門(下)
          6. MobX 入門(上) ||  MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點(diǎn)擊“閱讀原文”查看 130+ 篇原創(chuàng)文章

          瀏覽 64
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线91福利 | 国产各种高潮视频在线播放 | 美女精品网站 | 国产成人毛片 | 中文字幕av免费在线 |