stencilJs學習之構建 Drawer 元件

2023-08-31 18:00:27

前言

在之前的學習中,我們已經掌握了 stencilJs 中的一些核心概念和基礎知識,如裝飾器 PropStateEventListenMethodComponent 以及生命週期方法。這些知識是構建複雜元件和應用的基礎,而抽屜元件是一個很好的範例,能夠綜合運用這些知識,讓我們更深入地理解它們的作用和用法。

為什麼選擇抽屜元件

為什麼選擇抽屜元件作為綜合練習呢?因為抽屜元件是現代 Web 應用中常見的 UI 元素,具有以下特點:

  1. 互動性強:抽屜元件允許使用者在不離開當前頁面的情況下進行額外操作,因此它需要響應使用者的互動行為,如開啟、關閉等。
  2. 多狀態管理:抽屜可以有多種狀態,比如開啟、關閉、正在拖拽等,這就需要使用 State 裝飾器來管理和控制元件內部的狀態。
  3. 屬性傳遞:抽屜可能需要一些使用者自定義的屬性,如標題、內容、位置等。這就需要使用 Prop 裝飾器來接收外部傳遞的資料。
  4. 自定義事件:抽屜的開啟和關閉需要觸發自定義事件,以便其他元件或應用能夠響應狀態變化。
  5. 方法呼叫:使用者可能需要通過呼叫方法來控制抽屜的行為,例如通過點選按鈕來開啟或關閉抽屜,這就需要使用 Method 裝飾器來定義公開方法。
  6. 生命週期方法:抽屜在不同的生命週期階段可能需要執行特定的邏輯,例如元件初始化、渲染、解除安裝等。這就需要使用生命週期方法來實現這些邏輯。
  7. 可複用性:抽屜是一個通用的 UI 元素,在不同的場景中都可能被使用,因此需要設計良好的元件結構和介面,以實現高度的可複用性。

通過實際構建一個抽屜元件,我們能夠在綜合應用的背景下,更深入地理解這些概念的作用和相互關係。同時,這也為我們未來在實際專案中構建更復雜的元件和應用奠定了堅實的基礎。抽屜元件的案例將幫助我們更好地運用 stencilJs 的知識,從而成為更有信心和能力的前端開發者。

實現抽屜元件

建立一個專案

使用以下的命令建立一個 Stencil 專案

#使用 npm
npm init stencil
#使用 yarn
yarn create stencil
#使用 pnpm
pnpm create stencil

建立成功,終端顯示如下

建立一個元件

Stencil 專案內建一個生成元件命令 generate,使用下面的命令生成一個元件

#使用 npm
npm run generate
#使用 yarn
yarn generate
#使用 pnpm
pnpm run generate

執行之後會讓使用者輸入一個元件的名字(以-作為連字元),輸入之後按確認鍵會讓使用者選擇要生成的檔案,選擇之後按回車就能生成一個元件了。你可以在 src/components 目錄下看到 ce-drawer, 如下圖

實現元件

首先,建立元件的 HTML 結構:

import { Host, h } from '@stencil/core';

@Component({
  tag: 'ce-drawer',
  styleUrl: 'ce-drawer.css',
  shadow: true,
})
export class CeDrawer {

  renderHeader() {
    if (this.showHeader) {
      return (
        <div class="ivy-drawer-header">
          <slot name="header">{this.header}</slot>
        </div>
      );
    } else {
      return null;
    }
  }

  render() {
    return (
      <Host>
        <div class="ivy-mask"></div>
        <div class="ivy-drawer">
          {this.renderHeader()}
          <div class="ivy-drawer-body">
            <slot></slot>
          </div>
        </div>
      </Host>
    );
  }
}

接下來,宣告 prop


import { Component, Event, EventEmitter, Host, Method, Prop, Watch, h } from '@stencil/core';

@Component({
  tag: 'ce-drawer',
  styleUrl: 'ce-drawer.css',
  shadow: true,
})
export class CeDrawer {
    @Prop({
      attribute: 'show',
      mutable: true,
      reflect: true,
    })
    visible: Boolean = false;
    @Prop() width: string = '36%';

    @Prop({
        attribute: 'show-header',
        mutable: true,
        reflect: true,
    })
    showHeader: boolean = false;

    @Prop({
        attribute: 'header',
    })
    header: string = '';

    @Prop({
        attribute: 'mask-closable',
        mutable: true,
        reflect: true,
    })
    maskClosable: boolean = true;

    @Prop({
        attribute: 'placement',
        mutable: true,
        reflect: true,
    })
    placement: string = 'right';

    /**監聽傳入的 placement 是否符合要求*/
    @Watch('placement')
    validateName(val: string) {
        const flag = ['left', 'right', 'top', 'bottom'].includes(val);
        if (!flag) {
          throw new Error('placement 必須是 left/right/top/bottom 其中之一');
        }
    }

    renderHeader() {
        if (this.showHeader) {
          return (
            <div class="ivy-drawer-header">
              <slot name="header">{this.header}</slot>
            </div>
          );
        } else {
          return null;
        }
    }

    render() {
        return (
          <Host show={this.visible}>
            <div class="ivy-mask" onClick={this.maskClose.bind(this)}></div>
            <div
                class="ivy-drawer"
                style={{ width: ['left', 'right'].includes(this.placement) ? this.width : '100%', height: ['top', 'bottom'].includes(this.placement) ? this.width : '100%' }}
            >
                {this.renderHeader()}

                <div class="ivy-drawer-body">
                    <slot></slot>
                </div>
            </div>
          </Host>
        );
    }
}

接著,宣告自定義事件和遮罩層點選事件:

// ...
maskClose() {
    if (this.maskClosable) {
      this.visible = false;
    }
}

@Event() closed: EventEmitter;
closeHandler() {
    this.closed.emit();
}

最後,宣告外部可用的輔助方法,例如顯示/關閉 Drawer 元件:

// ...

@Method()
async open() {
    this.visible = true;
}
@Method()
async close() {
    this.closeHandler();
    this.visible = false;
}

原始碼

完整程式碼