来试试用 react 的写法写 vue

前言

我在 vue2 里面用 react 的写法写了很久的 vue,现在想想有没有机会在 vue3 里面复刻一下,于是试了试,确实可以,经过一些魔法和限制,我们可以在 vue 里面享受到和 react 一样优秀的 typescript 体验,并且有着 vue 保底的性能优势。

这种写法最大的优势在于:

  • 编译足够简单,你可以选用任意支持 jsx 构建的编译器,比如 swc、esbuild、tsc 等等
  • 类型系统足够简单
  • 概念简单清晰
  • 编写的代码是完全的 typescript,没有任何编译器魔法

劣势在于:

  • 性能,你无法使用任何 vue-compiler 带来的优化,如果具有强烈性能要求的场景,需要你手工优化
  • 屏蔽了绝大部分 vue sfc 的语法糖,你只能使用完全符合 typescript 中定义的语法

项目地址位于: github.com/iceprosurfa... ,喜欢这个项目的话可以帮忙点个 star ~

如果你只是希望体验一下功能的话可以直接前往 REPL 体验, REPL 尚未支持样式,你可以选择使用 unocss 代替(以内置,使用 class 提供支持)。

安装

如果是一个全新的项目,那么你可以使用 vite 默认的 vue 配置,随后使用 npm 安装即可:

bash 复制代码
npm install @vueireact/core

下面你只需要简单配置一下 tsconfig 即可使用:

json 复制代码
{ 
  "compilerOptions": { 
    "jsx": "react-jsx", 
    "jsxImportSource": "@vueireact/core" 
  }
}

随后你可以选择移除 vue plugin,因为这个系统是基于 runtime 实现的,并不需要 vue compiler

ts 复制代码
// vite.config.ts
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [], // 从这里移除 vue()
})

使用 & 概念

如何声明一个组件

在 vueireact 中,我们的组件只包含两个组成部分,函数的 setup 声明区域 和 返回值 render function :

ts 复制代码
function App (
  // 这里是 props
  props,
  // context 目前只支持了 expose
  context
) {
  // 相当于 setup

  // 这里返回的 render
  return () => ...
}

// 如果需要将组件暴露给 vue 体系使用,那么你需要使用 toVue 或是 toVues
const components = toVues({
  HelloWorld
})
// 或是这样
const HelloWorld = toVue(HelloWorld)

我们以 vue 的 hello world 为例子:

tsx 复制代码
import { ref } from 'vue'
function HelloWorld () {
  const message = ref('Hello World!')
  return () => <h1>{ message.value }</h1>
}

function App() {
  return () => <HelloWrold />
}

这样就完成了一个 hello world 的组件声明和使用了。

一个更加进阶一点的例子

下面我们举一个更加进阶一点的例子,比如一个带插槽渲染的泛型组件:

tsx 复制代码
import { ref } from 'vue'
import { toVue } from '@vueireact/core'
// 你可以像正常的函数一样,随意的声明泛型
function GenericComponent<T>(props: {
  list: T[]
  onListChange: (list: T[]) => void
  // 你可以声明具名插槽要求传入的类型匹配你的要求
  children: {
    item: (item: T, index: number) => JSX.Element;
  }
}) {
  return () => (
    <div>
      <h1>{
        props.list.map((item, index) => (props.children.item(item, index)))
      }</h1>
    </div>
  )
}

function App() {
  const list = ref([1, 2, 3]);
  function addOneToListItem(index: number) {
    const current = list.value[index]
    list.value.splice(index, 1, current + 1)

  }
  function addItem() {
    list.value.push(list.value.length + 1)
  }
  return () => (
    <div>
      {/* 这里组件会自动识别泛型,并约束,如果需要手工标注则可以这样写: <GenericComponent<string> */}
      <GenericComponent
        list={list.value}
        onListChange={(v) => list.value = v}
      >
        {
          {
            item: (item, index) => <div key={`item` + index} onClick={() => addOneToListItem(index)}>{item}</div>
          }
        }
      </GenericComponent>
      <button onClick={addItem}>Add</button>
    </div>
  )
}

ref

调用组件方法可以通过 defineExpose 以及 useRef 配合来使用,他们是强类型匹配的,可以有效减少错误。

tsx 复制代码
import { toVue, defineExpose, useRef } from '@vueireact/core'
function RefComponent(props: {},
  ctx: {
    // 使用 expose 定义会存在哪些方法
    expose: {
      refresh: () => void
    }
  }
) {
  defineExpose(ctx, {
    // 定义需要暴露的方法,不建议对外暴露属性,如果需要暴露的应该使用 getFunction 或将依赖倒置到顶层
    // defineExpose 内部使用 expose 来对外暴露属性,其效果和 vue 的 expose 完全一致,此方法仅为语法糖
    refresh: () => alert('ctx')
  })
  return () => (<div/>)
}

function App() {
  const refComponent = useRef(RefComponent)
  function refresh() {
    refComponent.value?.refresh();
    // 点击后会 alert 
  }
  return () => (
    <div>
      <RefComponent ref={refComponent}></RefComponent>
      <button onClick={refresh} >refresh</button>
    </div>
  )
}

export default toVue(App)

结语

在 vue 中使用 react 的写法是完全可行的,至少我在 vue2 中使用并上线了超过 20万行代码,但是 vue2 版本的实现方式和 vue3 版本有比较大差异,vue3 版本的代码尚未经过检验,请谨慎使用。

其他功能可以查看 官方文档

相关推荐
A黄俊辉A3 天前
uniapp webview中实现 app和内嵌的H5双向通信
vue.js·json
honkun63 天前
vue 表格组件 vxe-table 配置 ajax 请求自动加载数据与表单查询
vue.js·vxe-table
kybs19913 天前
全球灾害数据分析可视化 毕业设计-附源码66794
vue.js·spring boot·mysql·安全·django·c#·asp.net
雪芽蓝域zzs3 天前
第26章:ECharts 自定义主题 + 页面一键切换主题(深色 / 浅色两套)
vue.js·echars
雪芽蓝域zzs3 天前
第22章:ECharts 图表联动(多图表交互联动)
vue.js·echars
daols883 天前
vue 表格组件 vxe-table 实现虚拟滚动与无需滚动加载
vue.js·vxe-table
雪芽蓝域zzs3 天前
第18章:ECharts 折线图 + 标记点、标记线,自定义 tooltip
vue.js·echars
雪芽蓝域zzs3 天前
第24章:ECharts 地图组件(基础行政区地图,若依 Vue3)
vue.js·echars
计算机毕设定制辅导-无忧学长3 天前
《基于Vue的流浪动物救助中心管理系统的设计与实现》
java·vue.js·spring boot·流浪动物救助中心管理系统
雪芽蓝域zzs3 天前
第25章:地图下钻交互(点击省份,切换到对应省份城市地图)
vue.js·echars