VuReact is a compiler toolchain for migrating from Vue to React — and for writing React with Vue 3 syntax.
In this article, we will look at how Vue 3's defineExpose()
macro is mapped into React.
Before We Start #
To keep the examples easy to read, this article follows two simple conventions:
- All Vue and React snippets focus on core logic only, with full component wrappers and unrelated configuration omitted.
- The discussion assumes you are already familiar with the API shape and core behavior of Vue 3
defineExpose()
.
Compilation Mapping #
Vue defineExpose()
-> React forwardRef()
useImperativeHandle()
defineExpose()
is the macro used inside Vue 3 <script setup>
to expose internal state or methods from a child component to its parent.
VuReact compiles that pattern into React's forwardRef()
plus useImperativeHandle()
, allowing the parent component to access the exposed object through a ref.
- Vue
<script setup lang="ts">
import { ref, defineExpose } from 'vue';
defineProps<{ title: string }>();
const count = ref(0);
const increment = () => count.value++;
defineExpose({
count,
increment,
});
</script>
- Compiled React
import { forwardRef, useCallback, useImperativeHandle, memo } from 'react';
import { useVRef } from '@vureact/runtime-core';
type IComponentProps = { title: string };
const Component = memo(
forwardRef<any, IComponentProps>((props, expose) => {
const count = useVRef(0);
const increment = useCallback(() => {
count.value++;
}, [count.value]);
useImperativeHandle(expose, () => ({
count,
increment,
}));
return <div>{count.value}</div>;
}),
);
export default Component;
As the example shows, Vue defineExpose()
is compiled into React's forwardRef()
and useImperativeHandle()
combination.
VuReact preserves the structure of the exposed object, and exposed refs still use .value
, which keeps the interaction model close to Vue.
Parent access: Vue ref
- expose -> React
ref.current
In Vue, parent components access exposed child values through ref
and expose
. In React, VuReact maps that pattern to useRef()
plus ref.current
.
- Vue parent
<template>
<Component ref="childRef" />
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const childRef = ref();
onMounted(() => {
childRef.value?.count.value; // 0
childRef.value?.increment();
childRef.value?.count.value; // 1
});
</script>
- Compiled React parent
const Parent = () => {
const childRef = useRef();
useMounted(() => {
childRef.current?.count.value; // 0
childRef.current?.increment();
childRef.current?.count.value; // 1
});
return <Component ref={childRef} />;
};
VuReact keeps the parent access path aligned with the original Vue intent, so exposed child refs and methods remain straightforward to use.