博客 / 詳情

返回

Vue Vine:帶給你全新的 Vue 書寫體驗!

本文由體驗技術團隊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

image.png

  • 在 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

image.png

再定義一個組件,並引入 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>

image.png

用 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)">&lt;</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

小助手微信:opentiny-official

公眾號:OpenTiny

(温馨提示:OpenTiny CCF開源創新大賽也在持續報名中,歡迎大家一起報名參賽,贏取10W獎金)

user avatar fyuanlove 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.