從0搭建Vue3元件庫(四): 如何開發一個元件

web前端進階發表於2023-03-03

本篇文章將介紹如何在元件庫中開發一個元件,其中包括

  • 如何本地實時除錯元件
  • 如何讓元件庫支援全域性引入
  • 如何在 setup 語法糖下給元件命名
  • 如何開發一個元件

目錄結構

packages目錄下新建componentsutils兩個包,其中components就是我們元件存放的位置,而utils包則是存放一些公共的方法之類的。分別在兩個檔案下執行pnpm init,並將它們的包名改為@easyest/components@easyest/utils

{
  "name": "@easyest/components",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

components目錄下新建src目錄用於存放所有元件,最終目錄結構為

image.png

當然這只是目前的結構,後面會進行調整,因為還有樣式,測試等檔案目錄

測試元件

button.vue檔案中寫一個簡單的按鈕

<template>
  <button>測試按鈕</button>
</template>

然後在button/index.ts將其匯出

import Button from "./button.vue";
export { Button };
export default Button;

因為我們後面會有很多元件的,比如 Icon,Upload,Select 等,所以我們需要在components/src/index.ts集中匯出所有元件

export * from "./button";

最後在components/index.ts匯出所有元件提供給外部使用

export * from "./src/index";

接下來我們在上篇文章中搭建的 play 專案中進行一個測試,首先在 paly 專案中本地安裝@easyest/components(元件庫包名,後續釋出可以自己修改名字)

pnpm add @easyest/components

然後再app.vue中引用Button

<template>
  <div>
    <Button />
  </div>
</template>
<script lang="ts" setup>
import { Button } from "@easyest/components";
</script>

啟動專案便可以看到 Button 元件了,並且修改 Button 元件也會有熱更新的效果

image.png

app.use 全域性掛載元件

有的時候我們使用元件的時候想要直直接使用 app.use()掛載整個元件庫,其實使用 app.use()的時候它會呼叫傳入引數的 install 方法,因此首先我們給每個元件新增一個 install 方法,然後再匯出整個元件庫,我們將 button/index.ts 改為

import _Button from "./button.vue";
import type { App, Plugin } from "vue";
type SFCWithInstall<T> = T & Plugin;
const withInstall = <T>(comp: T) => {
  (comp as SFCWithInstall<T>).install = (app: App) => {
    const name = (comp as any).name;
    //註冊元件
    app.component(name, comp as SFCWithInstall<T>);
  };
  return comp as SFCWithInstall<T>;
};
export const Button = withInstall(_Button);
export default Button;

components/index.ts 修改為

import * as components from "./src/index";
export * from "./src/index";
import { App } from "vue";

export default {
  install: (app: App) => {
    for (let c in components) {
      app.use(components[c]);
    }
  },
};

此時我們需要給button.vue一個name:ea-button好在全域性掛載的時候作為元件名使用

<template>
  <button>測試按鈕</button>
</template>
<script lang="ts">
  import { defineComponent } from "vue";
  export default defineComponent({
    name: "ea-button",
    setup() {
      return {};
    },
  });
</script>

這時候在play/main.ts中全域性掛載元件庫

import { createApp } from "vue";
import App from "./app.vue";
import easyest from "@easyest/components";
const app = createApp(App);
app.use(easyest);
app.mount("#app");

app.vue 中使用ea-button元件,然後就會發現元件庫掛載成功了

<template>
  <div>
    <ea-button />
  </div>
</template>
<script lang="ts" setup></script>

image.png

但是這個全域性元件並沒有任何屬性提示,所以我們要藉助vscode中的volar給全域性元件加上提示效果

首先安裝@vue/runtime-core

pnpm add @vue/runtime-core -D -w

在src下新建components.d.ts

import * as components from "./index";
declare module "@vue/runtime-core" {
  export interface GlobalComponents {
    EaButton: typeof components.Button;
    EaIcon: typeof components.Icon;
  }
}
export {};

image.png

此時全域性引入的元件也有了提示效果

注意:當使用者使用元件庫的時候需要讓使用者在tsconfig.json中配置types:["easyest/lib/src/components"]才會出現提示效果

"compilerOptions": {
    //...
    "types": ["easyest/lib/src/components"]
  },

使用 setup 語法

我們都知道,使用 setup 語法進行 Vue 元件的開發是非常方便的,但是會有一個問題,就是當我們使用 setup 語法時該怎麼給元件命名呢?

其實有兩種解決方法,一個是再寫一個script標籤命名,比如input.vue

<template>
  <button>測試按鈕</button>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: "ea-button"
});
</script>
<script lang="ts" setup></script>

這種方式顯然是比較奇怪的

第二種方式就是使用外掛unplugin-vue-define-options解決,在測試環境中,我們需要把它配置在 play 專案中

首先全域性安裝unplugin-vue-define-options,因為這個外掛後面打包配置也需要用到,最新版本安裝會提示錯誤,看後續作者如何解決吧,暫時用// @ts-ignore忽略

pnpm add unplugin-vue-define-options  -D -w

然後在play/vite.config.ts引入該外掛

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// @ts-ignore
import DefineOptions from "unplugin-vue-define-options/vite";
export default defineConfig({
  plugins: [vue(), DefineOptions()],
});

此時我們便可以直接使用defineOptions函式定義元件名了

<template>
  <button>測試按鈕</button>
</template>

<script lang="ts" setup>
defineOptions({ name: "ea-button" });
</script>

元件開發

我們都知道一個元件需要接受一些引數來實現不同效果,比如 Button 元件就需要接收typesizeround等屬性,這裡我們暫且只接收一個屬性type來開發一個簡單的 Button 元件。

我們可以根據傳入的不同type來賦予 Button 元件不同類名

// button.vue
<template>
  <button class="ea-button" :class="buttonStyle"><slot /></button>
</template>

<script lang="ts" setup>
import "./style/index.less";
import { computed } from "vue";
defineOptions({ name: "ea-button" });
type ButtonProps = {
  type?: string;
};
const buttonProps = defineProps<ButtonProps>();

const buttonStyle = computed(() => {
  return { [`ea-button--${buttonProps.type}`]: buttonProps.type };
});
</script>

這裡引入了樣式檔案,在 button 目錄下新建 style 資料夾來存放 Button 元件的樣式

src/button/style/index.less如下

.ea-button {
  display: inline-block;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  background: #fff;
  border: 1px solid #dcdfe6;
  color: #606266;
  -webkit-appearance: none;
  text-align: center;
  box-sizing: border-box;
  outline: none;
  margin: 0;
  transition: 0.1s;
  font-weight: 500;
  padding: 12px 20px;
  font-size: 14px;
  border-radius: 4px;
}

.ea-button.ea-button--primary {
  color: #fff;
  background-color: #409eff;
  border-color: #409eff;

  &:hover {
    background: #66b1ff;
    border-color: #66b1ff;
    color: #fff;
  }
}

此時在 app.vue 中引入 Button 元件就可以看到想要的效果了

<template>
  <div>
    <Button type="primary">主要按鈕</Button>
  </div>
</template>
<script lang="ts" setup>
import { Button } from "@easyest/components";
</script>

image.png

由於元件的開發可能涉及的內容比較多,這裡就不詳細展開,這裡只簡單介紹一下元件開發的大致思路,後續會專門對一些常用元件進行開發,歡迎點贊收藏加關注!

本文對應程式碼地址 如何開發一個元件,動動小手Star一下謝謝

關注公眾號web前端進階 檢視完整教程

相關文章