👏👏👏厉害了 Vue Vine !Vue 组件还能这样写!!!

3,936 阅读6分钟

前言

哈喽大家好!我是 嘟老板。前段时间闲来无事,刷刷视频,偶然间看到了关于 Vue Vine 的介绍,感觉还挺有意思的,就花点时间研究了一下,写篇文章分享分享。如果你还不知道 Vue Vine 是啥,建议来看一看。

阅读本文您将收获:

  1. 了解 Vue Vine 基本介绍,如 Vine 组件函数,props,宏 等。
  2. 了解 Vue Vine 组件开发过程,从 0 到 1 写个 dome 组件。
  3. 了解 Vue Vine 基本实现原理及核心对象、函数等。
  4. 等等...

介绍

什么是 Vue Vine

Vue Vine 提供一种新的 Vue 组件定义形式,允许我们在一个文件内定义多个组件,且可继续沿用 <template> 模板的视图定义方式。

Vue Vine 规定以 .vine.ts 为扩展名,称作 VFC,完美支持 TypeScript 语法。

Vue Vine 目前仅支持 Vue3 + vite + TypeScript 环境,可与常规的 Vue3 组件结合使用。

如何使用 Vue Vine

基础语法介绍

更多详情见 官网

定义组件

Vue Vine 以函数的形式定义组件,返回一个 vine 标记的模板字符串。

function MyComponent() {
  return vine`<div>Hello World</div>`
}

注意:
在 vine 模板字符串中禁止使用表达式插值,如:

function MyComponent() {
  const userName = ref('Vine')
  // IDE 中无法正常显示模板部分的高亮
  return vine`<span>{{ `hello ${userName}` }}</span>`
}
props

Vue Vine 提供两种方式定义 props:

  • 组件函数定义形参 props,且为第一个参数,并为其编写 TypeScript 对象字面量类型,包含要定义的 props
import { SomeExternalType } from './path/to/somewhere'

function MyComponent(props: {
  foo: SomeExternalType
  bar?: number // 可选属性
  baz: boolean
}) { ... }
  • vineProp 宏定义。

    必须指定 prop 的类型,或提供一个默认值用于推导类型。

    const foo = vineProp<string>()
    

    vineProp 第一个参数是 prop 验证器,可选。如验证 title prop 是否已 # 号开头。

    const title = vineProp<string>(value => value.startsWith('#'))
    

    vineProp 提供 vineProp.optional 用于定义可选 prop

    const foo = vineProp.optional<string>()
    

    vineProp 提供 vineProp.withDefault 用于定义 prop 默认值。

    const foo = vineProp.withDefault('bar')
    

vineProp 上面已经讲过了,这里不重复了,说说其他的。

  • vineEmits

    VuedefineEmits 宏用法一致。

    const emits = vineEmits<{
      update: [foo: string, bar: number]
    }>()
    
    emits('update', 'foo', 1)
    
  • vineExpose

    VuedefineExpose 宏用法一致。

    vineExpose({
        foo: 'foo'
    })
    
  • vineSlots

    VuedefineSlots 宏用法一致。

const slots = vineSlots<{
  default(props: { msg: string }): any
}>()
  • vineOptions

    支持定义以下 Vue 组件选项:

    • name:组件名称。
    • inheritAttrs:是否启用组件的默认属性穿透行为,同 VueinheritAttrs 属性。
    vineOptions({
      name: 'MyComponent',
      inheritAttrs: false
    })
    
  • vineStyle

    用于定义组件样式。替代 Vue SFC<style> 部分。

    vineStyle.scoped 用于定义局部样式,同 <style scoped></style>

    vineStyle.scoped(`
        .title {
          font-size: 20px;
          font-weight: 700;
          margin-bottom: 6px;
        }
    `)
    

    还可以通过以下方式,指定不同的 css 处理语言。

    vineStyle(scss`
      .foo {
        color: red;
        .bar {
          background: yellow;
        }
      }
    `)
    

测试项目

  1. vite 创建测试项目 vue-vine-explorer
npm create vite@latest vue-vine-explorer
  1. 切换至 vue-vine-explorer 目录,安装依赖。
cd vue-vine-explorer
pnpm i
  1. 安装 Vue Vine
pnpm add vue-vine -D
  1. vite.config.ts 中添加 vite 插件。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { VineVitePlugin as vueVine } from 'vue-vine/vite'

export default defineConfig({
  plugins: [vue(), vueVine()],
})
  1. tsconfig.json 中引入 macros 类型,避免 TS 类型检查报错。
{ 
    "compilerOptions": { 
        /* ... */
        "types": ["vue-vine/macros"] 
    } 
}
  1. (可选)提升开发体验,建议安装 VSCode 插件 - vue vine

image.png

  1. 创建测试组件

删除模板工程的预置代码,在 components 下新增 test-vine.vine.ts 文件,用来定义 vine 组件

import { computed, ref } from "vue";
export function TestComp1() {
  const title = vineProp<string>((value) => value.startsWith("#"));
  const author = vineProp.withDefault("Anonymous");

  const bgColor = ref("red");

  const emits = vineEmits<{
    toggleBgColor: [color: string];
  }>();

  const toggleBgColor = () => {
    bgColor.value = bgColor.value === "red" ? "blue" : "red";
    emits("toggleBgColor", bgColor.value);
  };

  vineStyle.scoped(`
    .title {
      font-size: 20px;
      font-weight: 700;
      margin-bottom: 6px;
    }
    .playground {
      padding: 6px;
      background-color: v-bind(bgColor);
      border-radius: 6px;
      width: 100%;
      text-align: left;
    }
  `);

  return vine`
    <header>
      <div class="playground">
        <div class="title">
          {{ title }}
        </div>
        <div>{{ author }} </div>
        <button @click="toggleBgColor()">
          点击切换背景色
        </button>
      </div>
    </header>
 `;
}

export function TestComp2(props: { text: string }) {
  const footerText = computed(() => `footer: ${props.text}`);

  vineStyle.scoped(`
    .footer {
      width: 100%;
      height: 65px;
      position: fixed;
      bottom: 0;
      left: 0;
      font-size: 20px;
      font-weight: 700;
      text-align: center;
      line-height: 65px;
      color: black;
      background-color: white;
    }
  `);

  return vine`
    <div class="footer">
      {{ footerText }}
    </div>
  `;
}
  1. 渲染测试组件,验证效果
<script setup lang="ts">
import { TestComp1, TestComp2 } from './components/vine-test.vine'

function handleToggleBgColor(color) {
  console.log(`测试组件1切换背景色为${color}`)
}
</script>

<template>
  <TestComp1 title="#测试组件1" author="dulaoban" @toggle-bg-color="handleToggleBgColor" />
  <TestComp2 text="测试组件2" />
</template>

Aug-22-2024 11-26-20.gif

周边生态

Vue Vine 生态还在持续建设中...,现有的生态包括:

Vite 插件 - vue-vine/vite

Vite 配置文件 vite.config.ts 中应用:

import { VineVitePlugin } from 'vue-vine/vite'

export default defineConfig({
  plugins: [
    // ...其他插件
    VineVitePlugin()
  ],
})

VScode 插件 - vue vine

提供Vue Vine 语法高亮和语言特性。

Eslint parser - @vue-vine/eslint-parser

自定义 Eslint 解析器,帮助 Eslint 识别 Vue Vine 代码结构。

安装:pnpm i -D @vue-vine/eslint-parser

TypeScript 检查器 - vue-vine-tsc

检查 Vue Vine 文件 .vine.ts 类型,与 vue-tsc 兼容,也可以用它来检查 .vue 文件。

安装:pnpm i -D vue-vine-tsc

可在 npm script 中使用:

{
  "scripts": {
    "build": "vue-vine-tsc -b && vite build",
  }
}

TypeScript 声明文件 - vue-vine/macros

帮助使用宏时获得智能提示。

TypeScript 配置文件 tsconfig.json 中配置类型:

{
  "compilerOptions": {
    "types": ["vue-vine/macros"]
  }
}

CLI - create-vue-vine

帮助我们快速创建 Vine 模板工程。

# 未全局安装 create-vue-vine
pnpx create-vue-vine project-name

# 已全局安装 create-vue-vine
create-vue-vine project-name

截至发文,本人测试 CLI 还不可用,估计还在建设中...

实现原理

Vue Vine 项目组成

主要分为以下几部分:

编译器(complier

Vue Vine 编译器将用 Vue Vine 语法编写的代码转换为标准的 Vue 组件。包括解析组件结构、属性、事件,并生成相应的渲染函数。

语言服务器(language-server

借助语言服务器,Vue Vine 支持代码补全、类型检查和语法高亮等功能。使得编写代码时能够获得更好的开发体验。

Vite 插件(vite-plugin

Vue Vine 集成 Vite 插件,便于构建和热重载。能够在开发过程中快速查看更改效果,提高开发效率。

TypeScript 支持

Vue Vine 充分支持 TypeScript,可以利用类型系统来减少错误,提高代码的可维护性。

Eslint 解析器(eslint-parser

Vue Vine 借助 Eslint 解析器,可解析代码、检测错误和支持自定义规则,提高代码质量和一致性。

其他,如 CLI、文档、测试等

编译器介绍

Vue Vine 语法的正确运行,离不开编译器。

想必小伙伴们已经了解了 Vue Vine 的核心思路 - 通过编译器,将 Vue Vine 定义的函数组件转换成 Vue 渲染函数,后面的事情就是 Vue 帮忙干了。

Vue Vine 编译器处理过程大致如下:

image.png

以以下 app 入口组件为例,简单说明下:

export function App() {
  return vine`<router-view></router-view>`
}

看看程序过程中,每一步都是怎样的效果:

  1. 首先创建必要的上下文对象,包括 编译上下文(createComplierCtxVine 文件上下文(createVineFileCtx,上下文对象中包含编译过程中需要的状态和配置等。

其中,编译上下文 对象:

image.png

Vine 文件上下文 对象:

image.png

其中,

  • root 属性存储 babel 解析后的 ast 结构。
  • fileMagicCode 属性存储编译器转换后的源代码字符串信息。
  • vineCompFns 属性存储 Vine 组件函数。
  • styleDefine 属性存储组件内定义的样式。
  • ...
  1. 解析 TypeScript 文件,借助 babel 的能力(babelParse),将代码转换为 ast

    以下是组件定义函数转为 ast 后的结构:

image.png

  1. 验证(doValidateVIne)和分析(doAnalyuzeVine,验证组件定义函数是否符合 Vue Vine 的规范;分析 AST,包括导入、函数组件及相关宏等。

  2. 转换(transformFile,核心逻辑,对 .vine.ts 文件进行转换处理,包括导入排序合并、模板编译、组件函数转换,宏定义属性/css 变量/插槽/... 的处理等等,并将 Vine 组件函数转换为 IIFE(立即执行函数),以创建独立的作用域,最终将转换后的源代码更新到 vineFileCtx.fileMagicCode。其中模板编译借助了 Vue 提供的 @vue/compiler-dom 能力,完美继承 Vue 模板组件的静态优化性能。

image.png

  1. 编译样式(compileVineStyle,借助 postcss 及相关插件对组件定义的样式进行编译。
function compileVineStyle(compilerCtx: VineCompilerCtx, params) {
    // ...
    const result = postcss(postcssPlugins).process(source, postCSSOptions)
    // ...
}
  1. 生成 Vue 渲染函数,由 Vue 处理,Vue Vine 输出包含 生成 Vue 渲染函数所需的所有组件和逻辑 的源码字符串。

结语

不得不说,Vue VineVue 组件定义开了一扇窗,允许我们以另一种可行的方式开发组件。

个人拙见, Vue Vine 短时间内很难大范围应用,毕竟 Vue 原本的 SFC单文件组件)深入人心;若偏爱函数式组件,Vue 也支持 render 函数和 JSX,要说 Vue Vine 的优势在哪,大概就是既支持函数式开发,又能借助 Vue 框架对于 SFC 的优化能力。

每一个框架或者设计的出现,必然有其受众,我想 Vue Vine 可能是为那些想要用 Vue 语法,又想要 React 体验的开发者量身打造的。

如您对文章内容有任何疑问或想深入讨论,欢迎评论区留下您的问题和见解。

技术简而不凡,创新生生不息。我是 嘟老板,咱们下期再会。


往期干货