Comprehensive interpretation of Vue3, setup syntax sugar, and Composition API

Time:2022-11-25
  1. At first Vue3.0 exposed variables mustreturncome out,templatecan only be used in
  2. In Vue3.2 only need inscriptlabel withsetupAttributes, the context in which the component code runs during compilation is insetup()function, withoutreturntemplateCan be used directly.
  3. This article learns the syntax of Vue3 from the perspective of Vue2, allowing you to quickly understand Vue3’s Composition Api
  4. The fourteenth section of this article is the state libraryPiniaInstallation and use instructions

1. File structure

In Vue2,<template>There can only be one root element in a tag, there is no such limitation in Vue3

<template>
  // ...
</template>

<script setup>
  // ...
</script>

<style lang="scss" scoped>
  // Support CSS variable injection v-bind(color)
</style>

Two, data

<script setup>
  import { reactive, ref, toRefs } from 'vue'

  // ref declares responsive data, used to declare basic data types
  const name = ref('Jerry')
  // Revise
  name.value = 'Tom'

  // reactive declares responsive data for declaring reference data types
  const state = reactive({
    name: 'Jerry',
    sex: 'man'
  })
  // Revise
  state.name = 'Tom'

  // Use toRefs to destructure
  const {name, sex} = toRefs(state)
  // template can directly use {{name}}, {{sex}}
</script>

Vue practical video explanation:enter study

Three, method

<template>
  // call method
  <button @click='changeName'>button</button>  
</template>

<script setup>
  import { reactive } from 'vue'

  const state = reactive({    name: 'Jery'
  }) // declare method method
  const changeName = () => {    state.name = 'Tom'
  }  
</script>

Four, computed

<script setup>
  import { computed, ref } from 'vue'

  const count = ref(1)

  // Get doubleCount from computed
  const doubleCount = computed(() => {
    return count.value * 2
  })
  // Obtain
  console.log(doubleCount.value)
</script>

Five, watch

<script setup>
  import { watch, reactive } from 'vue'

  const state = reactive({
    count: 1
  })

  // declaration method
  const changeCount = () => {
    state.count = state.count * 2
  }

  // monitor count
  watch(
    () => state.count,
    (newVal, oldVal) => {
      console.log(state.count)
      console.log(`watch listens to the data before the change: ${oldVal}`)
      console.log(`watch monitors the changed data: ${newVal}`)
    },
    {
      immediate: true, // execute immediately
      deep: true // deep monitoring
    }
  )
</script>

Six, props father to son

Subassembly

<template>
  <span>{{props.name}}</span>
  // [props.] can be omitted
  <span>{{name}}</span>
</template>

<script setup>
  // import { defineProps } from 'vue'
  // defineProps automatically available in <script setup>, no import required
  // You need to configure [defineProps: true] under [globals] in the .eslintrc.js file

  // declare props
  const props = defineProps({    name: {      type: String,      default: ''
    }  })  
</script>

parent component

Introduce subcomponents, components will be automatically registered

<template>
  <child name='Jerry'/>  
</template>

<script setup>
  // import child component
  import child from './child.vue'
</script>

Seven, emit from son to father

Subassembly

<template>
  <span>{{props.name}}</span>
  // [props.] can be omitted
  <span>{{name}}</span>
  <button @click='changeName'>rename</button>
</template>

<script setup>
  // import { defineEmits, defineProps } from 'vue'
  // defineEmits and defineProps are automatically available in <script setup> without import
  // You need to configure [defineEmits: true] and [defineProps: true] under [globals] in the .eslintrc.js file
      // declare props
  const props = defineProps({    name: {      type: String,      default: ''
    } }) // declare event
  const emit = defineEmits(['updateName']) const changeName = () => {// Execute
    emit('updateName', 'Tom')  }
</script>

parent component

<template>
  <child :name='state.name' @updateName='updateName'/>  
</template>

<script setup>
  import { reactive } from 'vue'
  // import child component
  import child from './child.vue'

  const state = reactive({    name: 'Jerry'
  }) // Method to receive triggers from child components
  const updateName = (name) => {    state.name = name  }
</script>

Eight, v-model

Support for binding multiplev-modelv-modelYesv-model:modelValueshorthand for
Bind other fields like:v-model:name

Subassembly

<template>
  <span @click="changeInfo">My name is {{ modelValue }} and I am {{ age }} years old</span>
</template>

<script setup>
  // import { defineEmits, defineProps } from 'vue'
  // defineEmits and defineProps are automatically available in <script setup> without import
  // You need to configure [defineEmits: true] and [defineProps: true] under [globals] in the .eslintrc.js file

  defineProps({    modelValue: String,    age: Number
  }) const emit = defineEmits(['update:modelValue', 'update:age']) const changeInfo = () => { // Trigger parent component value update
    emit('update:modelValue', 'Tom')    emit('update:age', 30)  }
</script>

parent component

<template>
  // v-model: modelValue is abbreviated as v-model
  // Multiple v-models can be bound
  <child
    v-model="state.name"
    v-model:age="state.age"
  />
</template>

<script setup>
  import { reactive } from 'vue'
  // import child component
  import child from './child.vue'

  const state = reactive({    name: 'Jerry',    age: 20
  })
</script>

Nine, nextTick

<script setup>
  import { nextTick } from 'vue'

  nextTick(() => {
    // ...
  })
</script>

Ten, ref subcomponent instance and defineExpose

  • In the standard component writing method, the data of the child component is implicitly exposed to the parent component by default, but in the script-setup mode, all data is only returned to the template by default and will not be exposed outside the component, so the parent component cannot Get the data of the subcomponent directly by mounting the ref variable.
  • If you want to call the data of the sub-component, you need to expose it in the sub-component before you can get it correctly. This operation is done by defineExpose.

Subassembly

<template>
  <span>{{state.name}}</span>
</template>

<script setup>
  import { reactive, toRefs } from 'vue'
  // defineExpose does not need to be introduced
  // import { defineExpose, reactive, toRefs } from 'vue'

  // declare state
  const state = reactive({    name: 'Jerry'
  }) // Expose methods and variables to the parent component, and the parent component can get the data exposed by the child component through the ref API
  defineExpose({ // destructure state
    ...toRefs(state), // declaration method
    changeName () {      state.name = 'Tom'
    }  })
</script>

parent component

1. Get a subcomponent instance

<template>
  <child ref='childRef'/>
</template>

<script setup>
  import { ref, nextTick } from 'vue'
  // import child component
  import child from './child.vue'

  // Subcomponent ref (TypeScript syntax)
  const childRef = ref<InstanceType<typeof child>>()

  // nextTick
  nextTick(() => {
    // Get subcomponent name
    console.log(childRef.value.name)
    // Execute the child component method
    childRef.value.changeName()
  })
</script>

2. Get multiple subcomponent instances: Get subcomponent instances in v-for

This is the case only for v-forThe case where the number of cycles is fixed, because if v-fornumber of cyclesChanges after initialization will cause childRefs to be added repeatedly, and repeated child component instances will appear in childRefs

<template>
  <div v-for="item in 3" :key="item">
    <child :ref='addChildRef'/>
  </div>
</template>

<script setup>
  // omit...
    // Array of child component instances
  const childRefs = ref([]) // Add child component instances to childRefs through the addChildRef method
  const addChildRef = (el) => {    childRefs.value.push(el)  }
</script>

3. Get multiple sub-component instances: dynamic v-for gets sub-component instances

Add/modify to childRefs through subscripts. After initialization, dynamically modify the number of v-for loops, and automatically re-modify the data corresponding to the subscripts according to the subscripts

<template>
  <button @click='childNums++'></button>
  <div v-for="(item, i) in childNums" :key="item">
    // Dynamically add child component instances to childRefs through subscripts<child :ref='(el) => childRefs[i] = el'/>
  </div>
  <button @click='childNums--'></button>
</template>

<script setup>
  // omit...
    // number of child components
  const childNums = ref(1) // Array of child component instances
  const childRefs = ref([])
</script>

Ten, slot slot

Subassembly

<template>
  // anonymous slot
  <slot/>
  // named slot
  <slot name='title'/>
  // scope slot
  <slot name="footer" :scope="state" />
</template>

<script setup>
  import { useSlots, reactive } from 'vue'
  const state = reactive({ name: 'Zhang San', age: '25 years old'
  })    const slots = useSlots()  // anonymous slot使用情况
  const defaultSlot = reactive(slots.default && slots.default().length)  console.log(defaultSlot) // 1
  // named slot使用情况
  const titleSlot = reactive(slots.title && slots.title().length)  console.log(titleSlot) // 3
</script>

parent component

<template>
  <child>
    // Anonymous slot <span>I am the default slot</span>
    // named slot<template #title>
      <h1>i am a named slot</h1>
      <h1>i am a named slot</h1>
      <h1>i am a named slot</h1>
    </template>
    // scope slot<template #footer="{ scope }">
      <footer>Scope slots -- name: {{ scope.name }}, age {{ scope.age }}</footer>
    </template>
  </child> 
</template>

<script setup>
  // import child component
  import child from './child.vue'
</script>

12. Routing useRoute and useRouter

<script setup>
  import { useRoute, useRouter } from 'vue-router'

  // must first declare the call
  const route = useRoute()
  const router = useRouter()

  // routing information
  console.log(route.query)

  // routing jump
  router.push('/newPage')
</script>

Thirteen, route navigation guard

<script setup>
  import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

  // Add a navigation guard that triggers when the current component is about to leave.
  onBeforeRouteLeave((to, from, next) => {
    next()
  })

  // Add a navigation guard that triggers when the current component is updated.
  // Called when the current route changes, but the component is reused.
  onBeforeRouteUpdate((to, from, next) => {
    next()
  })
</script>

Fourteen, store

Vuex

*Vuex in Vue3 no longer provides auxiliary function writing

<script setup>
  import { useStore } from 'vuex'
  import { key } from '../store/index'

  // must first declare the call
  const store = useStore(key)

  // Get the state of Vuex
  store.state.xxx

  // method to trigger actions
  store.commit('fnName')

  // method to trigger actions
  store.dispatch('fnName')

  // Get Getters
  store.getters.xxx
</script>

Pinia

*full hugPiniaBar!
On November 24, 2021, Yuda announced on Twitter:PiniaOfficially becoming Vue’s official state library, which meansPiniathat isVuex 5PiniaThe advantages:

  • Both Composition Api and Options api syntax are supported;
  • Remove mutations, only state, getters and actions;
  • Nested modules are not supported, replaced by combined store;
  • Better Typescript support;
  • Clear, explicit code splitting;

Install

# use npm
npm install pinia

# use yarn
yarn add pinia

main.js import

import App from './App.vue'
import { createApp } from 'vue'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

Configure store.js

import { defineStore } from 'pinia'

// After defineStore is called, a function is returned, and the function is called to obtain the Store entity
export const useStore = defineStore({
  // id: required, unique in all Stores
  id: 'globalState',
  // state: the function that returns the object
  state: () => ({
    count: 1,
    data: {
      name: 'Jerry',
      sex: 'man'
    }
  }),
  // The first parameter of the getter is state, which is the current state, or you can use this to get the state
  // The getter can also access other getters, or other Stores
  getters: {
    // Get the state through state
    doubleCount: (state) => state.count * 2,
    // Get the state through this (note that this points to)
    tripleCount() {
      return this.count * 3
    }
  },
  actions: {
    updateData (newData, count) {
      // Use this to modify directly
      this.data = { ...newData }
      this.count = count

      // Use $patch to modify multiple values
      this.$patch({
        data: { ...newData },
        count
      })
    }
  }
})

use store

<template>
  // Get the state of the store
  <p>Name: {{store.data.name}}</p>
  <p>Sex: {{store.data.sex}}</p>

  // call actions method / modify store
  <button @click='update'>modify user information</button>

  // get getter
  <p>Get getter: {{store.doubleCount}}</p>
</template>

<script setup>
  import { useStore } from '@store/store.js'
  const store = useStore() function update () { // modify the state through the method defined by actions
    store.updateData({ name: 'Tom', sex: 'female' }) // modify directly through store
    store.data = { name: 'Tom', sex: 'female' } // change multiple states at the same time
    store.$patch((state) => {state.data = {name: 'Tom', sex: 'female'} state.count = 2
    })  }
</script>

<style lang="scss" scoped>
</style>

Other methods

replace the entire state
$stateallows you to pass thestoreset the properties of the new object to replace thestorethe entirestate

const store = useStore()
store.$state = {
  name: 'Bob',
  sex: 'man'
}

reset status
transferstoreUp$reset()method to reset the state to its initial value

const store = useStore()
store.$reset()

15. Life cycle

A component’s lifecycle hooks are accessed by prefixing the lifecycle hook with “on”.

The following table contains how to call lifecycle hooks inside Option API and setup()

Option API in setup
beforeCreate unnecessary
created unnecessary
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated

16. Prototype binding and use in components

main.js

import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)

// get prototype
const prototype = app.config.globalProperties

// bind parameters
prototype.name = 'Jerry'

Used within the component

<script setup>
  import { getCurrentInstance } from 'vue'

  // get prototype
  const { proxy } = getCurrentInstance()

  // output
  console.log(proxy.name)
</script>

Seventeen, v-bind() CSS variable injection

<template>
  <span>Jerry</span>
</template>

<script setup>
  import { ref, reactive } from 'vue'
  // prop receiving style
  const props = defineProps({    border: {      type: String,      default: '1px solid yellow'
    } }) // Constant declaration style
  const background = 'red'
    // Responsive data declaration style
  const color = ref('blue')  const style = reactive({    opacity: '0.8'
  })
</script>

<style lang="scss" scoped>
  span { // style declared using constants background: v-bind(background); // style declared using responsive data color: v-bind(color); opacity: v-bind('style.opacity'); // Use the style border received by prop: v-bind('props.border'); }
</style>

Eighteen, provide and inject

parent component

<template>
  <child/>
</template>

<script setup>
  import { ref, watch, provide } from 'vue'
  // import child component
  import child from './child.vue'

  let name = ref('Jerry') // declare provide
  provide('provideState', {    name,    changeName: () => {      name.value = 'Tom'
    } }) // Listen for name changes
  watch(name, () => {console.log(' name becomes ${name} ') setTimeout(() => {console.log(name.value) // Tom
    }, 1000)  })
</script>

Subassembly

<script setup>
  import { inject } from 'vue'
  // injection, the second parameter is the default value
  const provideState = inject('provideState', {})

  // Child component triggers name change
  provideState.changeName()
</script>

19. Custom commands

Vue3 is somewhat different from Vue2’s custom declaration method

const app = createApp({})

// make v-demo available in all components
app.directive('demo', {
  // Called before the attribute of the bound element or before the event listener is applied
  created(el, binding, vnode, prevVnode) {},
  // Called before the element is inserted into the DOM
  beforeMount(el, binding, vnode, prevVnode) {},
  // in the parent component of the bound element
  // and all his own child nodes are mounted and called
  mounted(el, binding, vnode, prevVnode) {},
  // Called before the parent component of the bound element is updated
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // in the parent component of the bound element
  // Called after all child nodes of himself have been updated
  updated(el, binding, vnode, prevVnode) {},
  // Called before the parent component of the bound element is unloaded
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // Called after the parent component of the bound element is unloaded
  unmounted(el, binding, vnode, prevVnode) {}
})

For example, implement a default cipher text ID number, click to display the command

app.directive('ciphertext', {
  created: (el: any) => {
    console.log(el, 1111)
    el.style.cursor = 'pointer'
    const value = el.innerText
    if (!value || value === 'null' || value === '--') {
      el.innerText = '--'
    } else {
      el.setAttribute('title', 'Click to view')
      el.innerText = hideText(value)
      el.addEventListener('click', () => {
        if (el.innerText.indexOf('*') > -1) {
          el.innerText = value
        } else {
          el.innerText = hideText(value)
        }
      })
    }
  }
})

<span v-ciphertext>{{idNumber}}</span>

20. Support for await

You can use await directly without using async. In this case, the setup of the component will automatically become async setup.

<script setup>
  const post = await fetch('/api').then(() => {})
</script>

21. Define the name of the component

with separate<script>block to define

<script>
  export default {
    name: 'ComponentName',
  }
</script>