Understanding Vue3 <script setup>

frontierdev

Jingtian Zhang

Posted on March 11, 2022

Understanding Vue3 <script setup>

the <script setup> is a compile-time syntactic sugar for using Composition API inside SFC. It is recommended by Vue docs.

you can still use Vue2 components in your 3.0 project, there might be some incompatibility regarding changes in Vue3 APIs, but overall it should work fine, just do not mix them up!

in 3.0, we can define variables using reactivity APIs in order to track changes, APIs including ref(), reactive()

SFC template

<template>
  <div> hello world: {{ count }} times </div>
  <button @click="count++">click me</button>
</template>

<script setup>
import {ref, computed, watch, onMounted, reactive} from "vue";

// mounted hook
onMounted(() => {console.log("mounted here")});

const count = ref(0);

watch(count, (newVal, oldVal) => {
  alert(newVal)
}) 
</script>
<style></style>
Enter fullscreen mode Exit fullscreen mode

well it may be confusing for some Vue2 users, but what happens here is that in <script setup> you have to return everything, otherwise functions or variables cannot be used in template

this is also one reason we cannot see this here anymore, if you do onMounted(() => console.log('this:', this)) it will print this:undefined. Since we do not have scope here anymore and everything is returned, this is not needed anymore.

Also, we do not need to define components here, components are auto registered, can use components directly inside template

Reactivity API: ref, reactive

ref() and reactive() allow us to directly create reactive state, computed state, and watchers.

  • using ref() to track basic data types like Boolean, Number, String
  • using reactive() to track more complicated data types like Array

note that ref is used to create a reactive value for all types, its just if you want to track deep/nested reactive props (object, array, etc), reactive works

methods

in 2, we define methods like this:

methods : {
  add(a, b){
    return a + b;
  }
Enter fullscreen mode Exit fullscreen mode

methods is the easiest part to rewrite, it basically becomes functions in 3:

function add(a, b){
  return a + b
}
Enter fullscreen mode Exit fullscreen mode

computed

data(){
  return {
    count: 0
  }
},
computed: {
  newCount(){
    return this.count + 1
  }
}
Enter fullscreen mode Exit fullscreen mode

rewriting in 3 and becomes:

import {ref, computed} from "vue"; 

const count = ref(1);
const newCount = computed(() => {
  return count + 1
})
Enter fullscreen mode Exit fullscreen mode

defineProps and defineEmits

1. defineProps

<script setup>
  import { defineProps } from "vue";
  const props = defineProps({
    title: String
  })
const { title } = props;
</script>
Enter fullscreen mode Exit fullscreen mode

2. defineEmits

<script setup>
  import { defineEmits } from 'vue'
  const emit = defineEmits(['change', 'delete'])
</script>
Enter fullscreen mode Exit fullscreen mode

watch

the following is how to show watch for a props value called count in a SFC

watch(() => props.count, (newVal, oldVal) => {
  if (newVal !== oldVal){
    console.log('value changes')
  }
}
Enter fullscreen mode Exit fullscreen mode

slots and attrs

<script setup>
  import { useAttrs, useSlots } from 'vue'

  const attrs = useAttrs()
  const slots = useSlots()
</script>
Enter fullscreen mode Exit fullscreen mode

Vuex

in 2.0, we can use vuex's provided mapState, mapMutation directly, while in 3.0 we need to wrap them in our own methods

in 2.0:

<template>
  <div>
    {{ count }}
    {{ countIsOdd }}
    {{ countIsEven }}
  </div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
  computed: {
    ...mapGetters(['count', 'countIsOdd', 'countIsEven'])
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

in 3.0:

<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'

const store = useStore()
const count = computed(() => store.getters.count)
const countIsOdd = computed(() => store.getters.countIsOdd)
const countIsEven = computed(() => store.getters.countIsEven)
</script>
Enter fullscreen mode Exit fullscreen mode

to avoid redundancy, we can also define an external file, in this case I make a file called map-state.js:

import { computed } from 'vue'
import { useStore } from 'vuex'

const mapGetters = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store.getters).map(
      getter => [getter, computed(() => store.getters[getter])]
    )
  )
}

export { mapGetters }
Enter fullscreen mode Exit fullscreen mode

and it can be used like this:

<template>
  <div>
    {{ count }}
    {{ countIsOdd }}
    {{ countIsEven }}
  </div>
</template>
<script setup>
import { mapGetters } from '../map-state'
const { count, countIsOdd, countIsEven } = mapGetters()
</script>
Enter fullscreen mode Exit fullscreen mode

of course the map-state.js file can be further extended:

import { computed } from 'vue'
import { useStore } from 'vuex'
const mapState = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store.state).map(
      key => [key, computed(() => store.state[key])]
    )
  )
}
const mapGetters = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store.getters).map(
      getter => [getter, computed(() => store.getters[getter])]
    )
  )
}
const mapMutations = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store._mutations).map(
      mutation => [mutation, value => store.commit(mutation, value)]
    )
  )
}
const mapActions = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store._actions).map(
      action => [action, value => store.dispatch(action, value)]
    )
  )
}
export { mapState, mapGetters, mapMutations, mapActions }
Enter fullscreen mode Exit fullscreen mode

Global configuration

the difference also occurs when you are trying to use a plugin or hang a global component, for example, using $message or $dialog in js.

creating an Amplitude (data tracking tool with js sdk) plugin:

/* Amplitude.js */
import amplitude from 'amplitude-js';

export default {
    install: (Vue, { apiKey, userId }) => {
        amplitude.getInstance().init(apiKey, userId, {
            includeUtm: true,
            includeReferrer: true,
            deviceIdFromUrlParam: true
        });

// in 2.0 it was Vue.prototype.$amplitude = amplitude;
Vue.config.globalProperties.$amplitude = amplitude;
    }
};

Enter fullscreen mode Exit fullscreen mode

and use that in main.js:

/* main.js */
import AmplitudePlugin from './plugins/amplitude';
const app = createApp(App);
// in 2.0 it was Vue.use(......)
app.use(AmplitudePlugin, {
    apiKey: process.env.VUE_APP_AMPLITUDE_API_KEY,
    userId: userInfo?.id
});
Enter fullscreen mode Exit fullscreen mode

for a message component, after you created one and registered that globally, for example:

// import all local fundamental components you build for your project, things like message, button, drawer, etc --> not business components
import * as components from './components';

export default {
    install: app => {
        Object.keys(components).forEach(key => {
            app.component(key, components[key]);
            if (key === 'DPMessage') {

// register your $message method globally
        app.config.globalProperties.$message = components[key];
            }
        });
    }
};
Enter fullscreen mode Exit fullscreen mode

of course you need to use it:

<template>
  <div><button @click="showSuccessMessage">click me to show success message</button>
</template>
<script setup>
const { proxy } = getCurrentInstance();

const showErrorMessage = () => {
//better to have a '?' here, just in case Vue does not find the method
    proxy?.$message({ type: 'error', text: 'hey this is sam test' });
};
</script>
Enter fullscreen mode Exit fullscreen mode

while other useful plugins like axios can go globally or not, depends on you

💖 💪 🙅 🚩
frontierdev
Jingtian Zhang

Posted on March 11, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related