Jingtian Zhang
Posted on March 11, 2022
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>
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 likeBoolean
,Number
,String
- using
reactive()
to track more complicated data types likeArray
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;
}
methods is the easiest part to rewrite, it basically becomes functions in 3:
function add(a, b){
return a + b
}
computed
data(){
return {
count: 0
}
},
computed: {
newCount(){
return this.count + 1
}
}
rewriting in 3 and becomes:
import {ref, computed} from "vue";
const count = ref(1);
const newCount = computed(() => {
return count + 1
})
defineProps and defineEmits
1. defineProps
<script setup>
import { defineProps } from "vue";
const props = defineProps({
title: String
})
const { title } = props;
</script>
2. defineEmits
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['change', 'delete'])
</script>
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')
}
}
slots and attrs
<script setup>
import { useAttrs, useSlots } from 'vue'
const attrs = useAttrs()
const slots = useSlots()
</script>
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>
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>
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 }
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>
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 }
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;
}
};
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
});
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];
}
});
}
};
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>
while other useful plugins like axios
can go globally or not, depends on you
Posted on March 11, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.