在之前的學習中,我們已經掌握了 stencilJs
中的一些核心概念和基礎知識,如裝飾器 Prop
、State
、Event
、Listen
、Method
、Component
以及生命週期方法。這些知識是構建複雜元件和應用的基礎,而抽屜元件是一個很好的範例,能夠綜合運用這些知識,讓我們更深入地理解它們的作用和用法。
為什麼選擇抽屜元件作為綜合練習呢?因為抽屜元件是現代 Web 應用中常見的 UI 元素,具有以下特點:
State
裝飾器來管理和控制元件內部的狀態。Prop
裝飾器來接收外部傳遞的資料。Method
裝飾器來定義公開方法。通過實際構建一個抽屜元件,我們能夠在綜合應用的背景下,更深入地理解這些概念的作用和相互關係。同時,這也為我們未來在實際專案中構建更復雜的元件和應用奠定了堅實的基礎。抽屜元件的案例將幫助我們更好地運用 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;
}