Skip to content

Instantly share code, notes, and snippets.

@bendytree
Created October 23, 2024 14:49
Show Gist options
  • Save bendytree/fc76b7d7547c777b59b5bdac6a2a9659 to your computer and use it in GitHub Desktop.
Save bendytree/fc76b7d7547c777b59b5bdac6a2a9659 to your computer and use it in GitHub Desktop.

Migrating from Vue2 to Vue3

Instructions

I'm migrating an old codebase from Vue 2.6 with @vue/composition-api to Vue 3.5.12. I will provide my Vue 2 single file component below. Please respond with the updated code. Your response will be saved to replace the old file (so do not include comments or markdown formatting).

Concerns

The codebase is written in Vue, Typescript 5, and Less CSS. If you have concerns during your conversion then include a comment like this:

// TODO: (reason for concern)

New Setup Syntax

The Vue2 code returned a default export with a setup function that takes in props and returns the variables for the template:

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

export default defineComponent({
  setup(props) {
    const count = ref(0);
    return { count };
  },
});
</script>
<script lang="ts" setup>
import { ref } from 'vue';

const count = ref(0);
</script>

New Props

Now props are defined using defineProps with the typescript generic param...

<script lang="ts">
export default defineComponent({
  props: {
    message: String,
    config: Object,
    manager: Object as () => ISomethingManager,
  },
  setup(props) {
    
  },
});
</script>
<script lang="ts" setup>
import { ref } from 'vue';

const props = defineProps<{
  message: string,
  config: any,
  manager: ISomethingManager,
}>();
</script>

Slot Contents

Now slot contents are accessed via useSlots.

<script lang="ts">
export default defineComponent({
  setup() {
    const elContent = getCurrentInstance()?.slots.default?.()?.[0]?.el;
  },
});
</script>
<script lang="ts" setup>
import { useSlots } from 'vue';

const slots = useSlots();
const elContent = slots.default?.()?.[0]?.el;
</script>

Exports

Technically, Vue 3 does not allow exports within the setup script. I will fix these issues later, so do not move exports into a separate script tag.

reactive(...) concerns

In Vue 2 reactive(...) mutates the original object. In Vue 3 reactive(...) returns a new object (so the original object must no longer be used).

If the target object for reactive comes from the same scope then simply call reactive on the initial declaration.

Important: If the target is from a different scope (such as a prop, function param, etc) then I need to review it SO ADD A // TODO: ... comment!

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

export default defineComponent({
  props: { manager: Object },
  setup(props) {
    const obj = { foo: 'bar' };
    reactive(obj);
    
    const settings = reactive(manager.settings);
    
    return { obj, settings };
  },
});
</script>
<script lang="ts" setup>
import { reactive } from 'vue';

const props = defineProps<{ manager: any }>();

// TODO: review `props.settings`
const settings = reactive(props.settings);

const obj = reactive({ foo: 'bar' });
</script>

defineComponent({ page, ... })

In Vue2 we sometimes used a special version of defineComponent which received a page parameter. In Vue3 we no longer use defineComponent and the page parameter should be removed.

<script lang="ts">
import { defineComponent } from 'src/router/app-page';
import { wwwPublicSchoolsPage } from "./page.routes";

export default defineComponent({
  page: wwwPublicSchoolsPage,
  setup(props){
    return { count: ref(0) };
  },
});
</script>
<script lang="ts" setup>
const count = ref(0);
</script>

SFC Globals

Vue 2:

Vue.prototype.dayjs = dayjs;

Vue 3:

app.config.globalProperties.dayjs = dayjs;

Internal usage to .$children, .proxy, instance, etc

Vue 2 usages of an instance's $children, proxy, etc need a review from me, so just add a "CONCERN" comment.

Other Migration Tips

As you know, there are some other considerations when migrating vue 2 to vue 3.

  • Anytime an ILayoutSettings or IFaReportParams object is declared, please wrap it with Vue's reactive(...)
  • Global API changes: Adapt to the new global mounting and configuration syntax, as Vue.createApp() replaces Vue.use() and Vue.component().
  • Lifecycle hooks: Update component lifecycle hooks, as some have been renamed (e.g., beforeDestroy to beforeUnmount).
  • v-model changes: Modify v-model usage, as it now uses modelValue prop and update:modelValue event by default.
  • Render function API: Update render functions to use the new, more JavaScript-centric syntax without h function arguments.
  • Component filters are no longer available. Replace filters with computed properties.
  • Emits option: Use the new defineEmits option to explicitly declare emitted events.
  • v-for on a template tag should have the :key on the template tag and not on the child
  • Transition classes: Rename transition classes from v- prefix to vue- for consistency.
  • Custom directives API: Modify custom directives to use the new directive hooks API.
  • Slots syntax unification: Update scoped slots syntax to use v-slot consistently, as the syntax has been unified.
  • $listeners removal: Remove usage of $listeners, as it has been merged into $attrs and useAttrs.
  • Global and internal APIs: Adjust usage of Vue.extend, Vue.set, and Vue.delete, as they have been removed or modified.
  • Do not modify code unless it is related to the migration. For example, no need to fill in missing Typescript types unless it relates to the migration.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment