Understanding Vue3 <script setup>
Tags: vue, webdev
Published: 2022-03-11 12:50:35

the `<script setup>` is a compile-time syntactic sugar for using Composition API inside SFC. It is recommended by Vue [docs](https://vuejs.org/api/sfc-script-setup.html). 

_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

```javascript 
<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 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: 

```javascript
methods : {
  add(a, b){
    return a + b;
  }
```

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

```javascript 
function add(a, b){
  return a + b
}
```

## computed

```javascript 
data(){
  return {
    count: 0
  }
},
computed: {
  newCount(){
    return this.count + 1
  }
}
```

rewriting in 3 and becomes: 

```javascript 
import {ref, computed} from "vue"; 

const count = ref(1);
const newCount = computed(() => {
  return count + 1
})
```

## defineProps and defineEmits

**1. defineProps**
```javascript 
<script setup>
  import { defineProps } from "vue";
  const props = defineProps({
    title: String
  })
const { title } = props;
</script>
```

**2. defineEmits**
```javascript 
<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

```javascript
watch(() => props.count, (newVal, oldVal) => {
  if (newVal !== oldVal){
    console.log('value changes')
  }
}
```


## slots and attrs

```javascript 
<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: 

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

in 3.0:

```javascript
<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`: 

```javascript 
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:

```javascript 
<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: 

```javascript 
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: 

```javascript 
/* 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`:

```javascript 
/* 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: 

```javascript 
// 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: 


```javascript
<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