你好,我是 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
发表评论