Skip to content

[开源推荐] VuReact - 写 Vue,生成可维护的 React #3260

@smirk9581

Description

@smirk9581

项目地址

https://github.com/vureact-js/core

类别

JS

项目标题

一个把 Vue 3 单文件组件编译成 React 的工具链

项目描述

VuReact 是一个把 Vue 3 <script setup> 组件编译成纯 React 组件的工具,能同时处理 SFC、脚本和样式文件。它适合想迁移到 React、又不想一次性重写 Vue 项目的团队,或者想用 Vue 写并产出 React 的用户,也适合想了解 Vue -> React 渐进迁移、混合开发和学习两套框架语义映射的初学者。

亮点

  • 不只是转换 <script setup>,还能把 Vue 3 单文件组件里的模板、脚本和样式一起编译成 React 代码。
  • 不是运行时“套壳”方案,输出的是可继续维护的纯 React 组件和样式产物。
  • 支持 definePropsdefineEmitsdefineExpose、响应式 API、scoped 样式等常用 Vue 语义,适合真实项目逐步迁移。
  • 会自动分析依赖并注入 memouseCallbackuseMemo 等优化代码,减少手工改写成本。
  • 提供 buildwatch 两种模式,既能批量编译项目,也能边改边看结果。

示例代码

输入(Vue 3 SFC):

<template>
  <section class="counter-card">
    <h2>{{ props.title || title }}</h2>
    <p>Count: {{ count }}</p>
    <button @click="increment">+1</button>
    <button @click="methods.decrease">-1</button>
  </section>
</template>

<script setup lang="ts">
  // @vr-name: Counter (注:用于告诉编译器,该生成什么组件名)
  import { computed, ref } from 'vue';

  // 也可以使用宏定义组件名
  defineOptions({ name: 'Counter' });

  // 定义 props
  const props = defineProps<{ title?: string }>();

  // 定义 emits
  const emits = defineEmits<{
    (e: 'change'): void;
    (e: 'update', value: number): number;
  }>();

  const step = ref(1);
  const count = ref(0);
  const title = computed(() => `Counter x${step.value}`);

  const increment = () => {
    count.value += step.value;
    emits('update', count.value);
  };

  const methods = {
    decrease() {
      count.value -= step.value;
    },
  };
</script>

<style lang="less" scoped>
  @border-color: #ddd;
  @border-radius: 8px;
  @padding-base: 12px;

  .counter-card {
    border: 1px solid @border-color;
    border-radius: @border-radius;
    padding: @padding-base;
  }
</style>

输出(React TSX):

import { memo, useCallback, useMemo } from 'react';
import { useComputed, useVRef } from '@vureact/runtime-core';
import './Counter-a1b2c3.css';

// 根据 defineProps 和 defineEmits 推导
type ICounterType = {
  title?: string;
  onChange: () => void;
  onUpdate: (value: number) => number;
};

// memo 包裹组件
const Counter = memo((props: ICounterType) => {
  // ref/computed 转换成了对等的适配 API
  const step = useVRef(1);
  const count = useVRef(0);
  const title = useComputed(() => `Counter x${step.value}`);

  // 自动分析顶层箭头函数依赖,并追加 useCallback 优化
  const increment = useCallback(() => {
    count.value += step.value;
    props.onUpdate?.(count.value); // emits 转换
  }, [count.value, step.value, props.onUpdate]);

  // 自动分析顶层对象中的依赖,并追加 useMemo 优化
  const methods = useMemo(
    () => ({
      decrease() {
        count.value -= step.value;
      },
    }),
    [count.value, step.value],
  );

  return (
    <>
      <section className="counter-card" data-css-a1b2c3>
        <h2 data-css-a1b2c3>{props.title || title.value}</h2>
        <p data-css-a1b2c3>Count: {count.value}</p>
        <button onClick={increment} data-css-a1b2c3>
          +1
        </button>
        <button onClick={methods.decrease} data-css-a1b2c3>
          -1
        </button>
      </section>
    </>
  );
});

export default Counter;

输出(React 作用域 CSS):

/* Counter-a1b2c3.css */
.counter-card[data-css-a1b2c3] {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 12px;
}

👉 完整教程请访问:VuReact - 快速开始

截图或演示视频

hero_demo_3MB.mp4

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions