Skip to content

Script Conversion Guide

This page demonstrates common and advanced Script layer conversions in the format of "Vue Input -> React Output (illustrative)".

Note: Examples are simplified comparisons; the specific import names, type names, and wrapper structures are subject to local compilation artifacts.

1. Reactive API Mapping (Core)

Vue APIOutput Adapted API
refuseVRef
reactiveuseReactive
computeduseComputed
readonlyuseReadonly
toRef / toRefsuseToVRef / useToVRefs
watchuseWatch
watchEffect / watchPostEffect / watchSyncEffectuseWatchEffect / useWatchPostEffect / useWatchSyncEffect
onMounted / onUnmounteduseMounted / useUnmounted
onBeforeUpdate / onUpdateduseBeforeUpdate / useUpdated

For more details, please refer to the Runtime Hooks Documentation

Example: ref + computed

Vue Input:

vue
<script setup lang="ts">
import { computed, ref } from 'vue';

const state = ref(1);
const double = computed(() => state.value * 2);
</script>

React Output (illustrative):

tsx
import { useComputed, useVRef } from '@vureact/runtime-core';

const state = useVRef(1);
const double = useComputed(() => state.value * 2);

2. Macros: defineProps / defineEmits / defineSlots

defineProps (Type Parameter + Runtime Syntax)

Vue Input:

vue
<script setup lang="ts">
const props = defineProps<{ id: string; enabled?: boolean }>();
</script>

React Output (illustrative):

tsx
type ICompProps = {
  id: string;
  enabled?: boolean;
};

const Comp = (props: ICompProps) => {
  // ...
};

Supplement: defineProps(['foo', 'bar']) and defineProps({ ... }) also support type inference, but the type parameter form is recommended as a priority.

defineEmits and Event Name Mapping

Vue Input:

vue
<script setup lang="ts">
const emit = defineEmits<{
  (e: 'save-item', payload: { id: string }): void;
  (e: 'update:name', value: string): void;
}>();

const submit = () => {
  emit('save-item', { id: '1' });
  emit('update:name', 'next');
};
</script>

React Output (illustrative):

tsx
type ICompProps = {
  onSaveItem?: (payload: { id: string }) => void;
  onUpdateName?: (value: string) => void;
};

const submit = useCallback(() => {
  props.onSaveItem?.({ id: '1' });
  props.onUpdateName?.('next');
}, [props.onSaveItem, props.onUpdateName]);

defineSlots and Slot Types

Vue Input:

vue
<script setup lang="ts">
const slots = defineSlots<{
  default?(): any;
  footer(props: { count: number }): any;
}>();
</script>

React Output (illustrative):

tsx
type ICompProps = {
  children?: React.ReactNode;
  footer?: (props: { count: number }) => React.ReactNode;
};

3. useTemplateRef: useRef + .current

Vue Input:

vue
<script setup lang="ts">
import { useTemplateRef } from 'vue';

const pRef = useTemplateRef<HTMLParagraphElement>('p');
</script>

React Output (illustrative):

tsx
import { useRef } from 'react';

const pRef = useRef<HTMLParagraphElement | null>(null);

Meanwhile, access to this ref in the script will be converted from .value to .current (e.g., pRef.value -> pRef.current).

4. watchEffect / Lifecycle: Supplement Dependency Parameters

Vue Input:

vue
<script setup lang="ts">
watchEffect(() => {
  console.log(state.value);
});

onUpdated(() => {
  console.log(state.value);
});
</script>

React Output (illustrative):

tsx
useWatchEffect(() => {
  console.log(state.value);
}, [state.value]);

useUpdated(() => {
  console.log(state.value);
}, [state.value]);

5. defineAsyncComponent: Mapping to React.lazy

Vue Input:

vue
<script setup lang="ts">
const AsyncPanel = defineAsyncComponent(() => import('./Panel.vue'));
</script>

React Output (illustrative):

tsx
const AsyncPanel = lazy(() => import('./Panel.jsx'));

Constraint: Only ESM dynamic import('...') syntax is supported.

6. provide/inject

Converted to Provider adaptation structure and useInject hook

Vue Input:

vue
Parent Component

<script setup lang="ts">
provide('theme', theme);
</script>

Child Component

<script setup lang="ts">
const theme = inject<string>('theme');
</script>

React Output (illustrative):

tsx
Parent Component

<Provider name={'theme'} value={theme}>
  {/* children */}
</Provider>

Child Component

const theme = useInject<string>('theme');

7. Static Hoisting and Optimization

Static Hoisting

const

For top-level constant declarations, if their initial values are literals of JavaScript basic data types (such as strings, numbers, booleans, etc.), they will be hoisted outside the component.

Vue Input:

vue
<script setup lang="ts">
const defaultValue = 1;
const isEnabled = true;
</script>

React Output (illustrative):

tsx
const defaultValue = 1;
const isEnabled = true;

// Example Component
const Component = memo(() => {
  return <></>;
});

Optimization: Automatic Dependency Analysis

The compiler has a built-in powerful dependency analyzer that follows React rules and intelligently analyzes the dependency relationships of top-level arrow functions and top-level variable declarations.

useCallback

For top-level arrow functions, if analyzable dependencies exist in their function bodies, they will be automatically optimized

Vue Input:

vue
<script setup lang="ts">
const inc = () => {
  count.value++;
};

const fn = () => {};

const fn2 = () => {
  const value = foo.value;
  const fn4 = () => {
    value + state.bar.c--;
  };

  fn();
};
</script>

React Output (illustrative):

tsx
const inc = useCallback(() => {
  count.value++;
}, [count.value]);

// Added as useCallback because it is called by fn2
const fn = useCallback(() => {}, []);

const fn2 = useCallback(() => {
  // Trace the initial value and collect foo.value
  const value = foo.value;

  // Ignore optimization for local arrow functions
  const fn4 = () => {
    value + state.bar.c--;
  };

  // Call ordinary function
  fn();
}, [foo.value, state.bar.c, fn]);

useMemo

For top-level variable declarations with initial values, if analyzable dependencies exist in their initial value expressions, they will be automatically optimized

Vue Input:

vue
<script setup lang="ts">
const fooRef = ref(0);
const reactiveState = reactive({ foo: 'bar', bar: { c: 1 } });

const memoizedObj = {
  title: 'test',
  bar: fooRef.value,
  add: () => {
    reactiveState.bar.c++;
  },
};

const staticObj = {
  foo: 1,
  state: { bar: { c: 1 } },
};

const staticList = [1, 2, 3];

const reactiveList = [fooRef.value, 1, 2];

const mixedList = [
  { name: reactiveState.foo, age: fooRef.value },
  { name: 'A', age: 20 },
];

const nestedObj = {
  a: {
    b: {
      c: reactiveList[0],
      d: () => {
        return memoizedObj.bar;
      },
    },
    e: mixedList,
  },
};

const computeFn = () => {
  memoizedObj.add();
  return nestedObj.a.b.d();
};

const formattedValue = memoizedObj.bar.toFixed(2);
</script>

React Output (illustrative):

tsx
const memoizedObj = useMemo(
  () => ({
    title: 'test',
    bar: fooRef.value,
    add: () => {
      reactiveState.bar.c++;
    },
  }),
  [fooRef.value, reactiveState.bar.c],
);

// No dependencies
const staticObj = {
  foo: 1,
  state: {
    bar: {
      c: 1,
    },
  },
};

const reactiveList = useMemo(() => [fooRef.value, 1, 2], [fooRef.value]);

// No dependencies
const staticList = [1, 2, 3];

const mixedList = useMemo(
  () => [
    {
      name: reactiveState.foo,
      age: fooRef.value,
    },
    {
      name: 'A',
      age: 20,
    },
  ],
  [reactiveState.foo, fooRef.value],
);

const nestedObj = useMemo(
  () => ({
    a: {
      b: {
        c: reactiveList[0],
        d: () => {
          return memoizedObj.bar;
        },
      },
      e: mixedList,
    },
  }),
  [reactiveList[0], memoizedObj.bar, mixedList],
);

const computeFn = useMemo(
  () => () => {
    memoizedObj.add();
    return nestedObj.a.b.d();
  },
  [memoizedObj, nestedObj.a.b],
);

const formattedValue = useMemo(() => memoizedObj.bar.toFixed(2), [memoizedObj.bar]);

8. Routing API (when integrating with the router ecosystem)

Vue Router APIOutput Adapted API
createRoutercreateRouter
useRoute / useRouter / useLinkuseRoute / useRouter / useLink
onBeforeRouteLeave / onBeforeRouteUpdate / onBeforeRouteEnteruseBeforeRouteLeave / useBeforeRouteUpdate / useBeforeRouteEnter
createWebHistory / createWebHashHistory / createMemoryHistorycreateWebHistory / createWebHashHistory / createMemoryHistory

9. Strong Constraints and Common Failure Points

  1. Macros can only be used at the top level of SFC and must be assigned to variables
  2. Calls to be converted to Hooks must comply with React's top-level rules
  3. Dynamic/unanalyzable syntax will trigger warnings, errors, or be handled conservatively
  4. It is recommended to use stable strings for event names, avoiding dynamic emit(eventName)
  5. No manual adjustment is made to the routing configuration content (e.g., not changing the value of the component option to JSX tag syntax)

Next Steps

Released under the MIT License