你好,我是 Kagol,個人公眾號:前端開源星球
。
上個月和 TinyVue 的小夥伴們一起參加了 VueConf 24 大會,有幸認識沈青川大佬,並瞭解了他的 Vue Vine 專案,Vue Vine 讓你可以在一個檔案中透過函式方式定義多個 Vue 元件,同時可以使用所有 Vue 的模板特性。
聽起來是不是很酷!
之前我寫過 SFC,也寫過 JSX 的 Vue 元件,兩者各有缺點。
- SFC 顧名思義單檔案元件,只能在一個檔案中定義一個元件,如果有幾個相關的元件想放一起,對不起,不行!你只能建立一個資料夾,把一堆相關元件一個一個檔案放裡面。
- JSX 雖然能透過函式方式定義元件,並且可以在一個檔案中定義多個相關的元件,但是沒法享受 Vue 模板語法,以及模板編譯相關的最佳化。
Vue Vine 透過把兩者的優點集合在一起,創造了一種全新的 Vue 元件書寫方式。
我們來一起體驗下吧!
搭建 Vue Vine 環境
假設你已經有一個 Vite + Vue3 專案。
只需要以下步驟,就可以搭建 Vue Vine 環境:
- 安裝 vue-vine 依賴:
npm i -D vue-vine
- 在
vite.config.ts
中匯入VineVitePlugin
外掛
import { VineVitePlugin } from 'vue-vine/vite'
export default defineConfig({
plugins: [
// ...其他外掛
VineVitePlugin()
],
})
- 安裝 VSCode 擴充套件:Vue Vine
- 在
tsconfig.json
中配置 macro 型別
{
"compilerOptions": {
"types": ["vue-vine/macros"]
}
}
愉快地體驗下 Vue Vine 吧
我們建立一個 MyComponent.vine.ts
檔案,寫入以下內容:
export function MyComponent() {
return vine`
<div>Hello World</div>
`
}
然後在 App.vue 中引入這個元件。
<script setup lang="ts">
import { MyComponent } from './components/MyComponent.vine'
</script>
<template>
<MyComponent />
</template>
可以看到顯示了一個 Hello World
。
再定義一個元件,並引入 TinyVue 的元件試試。
MyComponent.vine.ts
檔案,寫入以下內容:
+ import { TinyButton, TinyAlert } from '@opentiny/vue'
export function MyComponent() {
return vine`
<div>Hello World</div>
`
}
+ export function ComponentDemo() {
+ return vine`
+ <tiny-button type="primary">確定</tiny-button>
+ <tiny-alert description="這是一段描述"></tiny-alert>
+ `
+ }
在 App.vue 中引入這個元件。
<script setup lang="ts">
- import { MyComponent } from './components/MyComponent.vine'
+ import { MyComponent, ComponentDemo } from './components/MyComponent.vine'
</script>
<template>
<MyComponent />
+ <ComponentDemo />
</template>
用 Vue Vine 方式寫一個簡單的分頁元件
之前在我的部落格寫過一篇文章:手把手教你使用 Vue / React / Angular 三大框架開發 Pagination 分頁元件
我們現在用 Vue Vine 方式重寫一遍。
建立 Pagination.vine.ts
檔案,寫入以下內容:
import { ref } from 'vue'
// 演示元件 props 定義
export function Pagination(props: {
defaultCurrent: number,
defaultPageSize: number,
total: number,
}) {
// 演示 emit 事件定義
const emit = vineEmits<{
change: [current: number]
}>()
// 當前頁碼
const current = ref(props.defaultCurrent)
// 總頁碼
const totalPage = ref(Math.ceil(props.total / props.defaultPageSize))
// 設定當前頁碼
const setPage = (page: number) => {
if (page < 1) return
if (page > totalPage.value) return
current.value = page
emit('change', current.value)
}
return vine`
<div class="x-pagination">
<Button class="btn-prev" @click="setPage(current - 1)"><</Button>
{{ current }}
<Button class="btn-next" @click="setPage(current + 1)">></Button>
</div>
`
}
// 自定義 Button 元件(演示 <slot></slot> 插槽)
export function Button() {
const emit = vineEmits<{
click: []
}>()
return vine`
<button type="button" @click="emit('click')"><slot></slot></button>
`
}
再定義一個 List 列表元件,用來模擬分頁資料。
List.vine.ts
import { ref, watch } from 'vue'
export function List(props: {
dataSource: {
id: number
name: string
}[]
}) {
const lists = ref(props.dataSource)
watch(() => props.dataSource, (newVal) => {
lists.value = newVal
})
return vine`
<ul>
<li v-for="list in lists" :key="list.id">
{{ list.name }}
</li>
</ul>
`
}
在 App.vue 中使用 Pagination 和 List 元件。
<script setup lang="ts">
+ import { ref } from 'vue'
+ import chunk from 'lodash-es/chunk'
import { MyComponent, ComponentDemo } from './components/MyComponent.vine'
+ import { Pagination } from './Pagination.vine'
+ import { List } from './List.vine'
+
+ // 資料來源
+ const lists = [
+ { id: 1, name: 'Curtis' },
+ { id: 2, name: 'Cutler' },
+ { id: 3, name: 'Cynthia' },
+ { id: 4, name: 'Cyril' },
+ { id: 5, name: 'Cyrus' },
+ { id: 6, name: 'Dagmar' },
+ { id: 7, name: 'Dahl' },
+ { id: 8, name: 'Dahlia' },
+ { id: 9, name: 'Dailey' },
+ { id: 10, name: 'Daine' },
+ ]
+
+ // 列表當前展示的資料
+ const dataList = ref<{
+ id: number
+ name: string
+ }[]>([])
+
+ const defaultCurrent = 1
+ const defaultPageSize = 3
+ const total = lists.length
+
+ // 設定當前列表資料
+ const setList = (current: number, pageSize: number) => {
+ dataList.value = chunk(lists, pageSize)[current - 1]
+ }
+
+ setList(defaultCurrent, defaultPageSize)
+
+ const onChange = (current: number) => {
+ setList(current, defaultPageSize)
+ }
</script>
<template>
<MyComponent />
<ComponentDemo />
+ <List :data-source="dataList" />
+ <Pagination :default-current="defaultCurrent" :default-page-size="defaultPageSize" :total="total" @change="onChange" />
</template>
效果如下:
這裡有幾個需要注意的點:
- 定義元件 props 的方式,元件函式只有一個唯一的 props 引數,可以定義 props 的型別,和定義 TypeScript 型別一樣
export function Pagination(props: {
defaultCurrent: number,
defaultPageSize: number,
total: number,
}) {
...
}
- 定義 emit 的方式,透過 vineEmits 宏而不是 defineEmits 宏進行定義
const emit = vineEmits<{
change: [current: number]
}>()
emit('change', current.value)
更多用法參考 Vue Vine 官網:https://vue-vine.dev/
你覺得 Vue Vine 風格寫 Vue 元件體驗如何呢?歡迎在評論區留言討論。
聯絡我們
GitHub:https://github.com/opentiny/tiny-vue(歡迎 Star ⭐)
官網:https://opentiny.design/tiny-vue
B站:https://space.bilibili.com/15284299
個人部落格:https://kagol.github.io/blogs
小助手微信:opentiny-official
公眾號:OpenTiny